5

I have a certain intent (NDEF_DISCOVERED), some of which I cannot handle correctly, so I want to redirect those to android's default nfc handler.

So i take the intent, setComponent(null), and then startActivity(intent)

But.. it always comes back to my app in an infinite loop of intent throwing.

Is there a way I can send off an intent to anyone but my app? Or send it to android's default nfc handler?

EDIT: So I used vikram's answer to query the packagemanager for possible activities to handle my intent, then looped thru and found the activity with the highest priority (who isn't me) and sent an explicit intent to them.

ginsunuva
  • 73
  • 6
  • May be this helpful for you http://stackoverflow.com/questions/8615240/start-an-android-app-intent-from-an-nfc-tag?rq=1 – Aravin Jul 25 '13 at 18:14
  • `But.. it always comes back to my app in an infinite loop of intent throwing` What exactly do you mean? Please show the code where you create the intent and your manifest. – Simon Jul 25 '13 at 18:15
  • You didn't answer my question. What *exactly* do you mean by infinite loop? You have shown no code which could result in an infinite loop. – Simon Jul 25 '13 at 18:37
  • I send off the intent (for which I have `setComponent(null)` so it does not explicitly come back to me.) Then android, because it auto-chooses the best NFC app to handle the intent, keeps choosing mine, and my app keeps receiving the intent and sending it back off forever. Not a conventional infinite-loop as seen within code. But rather a loop as in back-and-forth intent-passing. – ginsunuva Jul 25 '13 at 18:38

2 Answers2

5

A custom chooser dialog/popup will be better for you in this case. Instead of launching an intent, use the PackageManager to queryIntentActivities(Intent, int). From the List<ResolveInfo> that queryIntentActivities(Intent, int) returns, filter out your own app using the packageName:

String packageName = "";
for(ResolveInfo resInfo : resolvedInfoList) {

    packageName = resInfo.activityInfo.applicationInfo.packageName;

    // Exclude `packageName` from the dialog/popup that you show

}

Edit 1:

The following code will create and show a PopupWindow whenever showList() is called. The xml layout file used to return popupView contains nothing but a LinearLayout(R.layout.some_popup_view):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/llPopup"
    android:orientation="vertical" >

</LinearLayout>

This code is just a simple demonstration. For it to be anything close to usable, you will probably need to add a ListView with a custom adapter to this PopupWindow. In the OnClickListener for the ListView, you will retrieve the package name of the Application that the user clicks on, and generate an intent to start that activity. As of now, the code only displays how to filter out your own application using a custom chooser. In the if block, replace "com.example.my.package.name" with your app's package name.

public void showList() { 

    View popupView = getLayoutInflater().inflate(R.layout.some_popup_view, null);

    PopupWindow popupWindow = new PopupWindow(popupView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

    LinearLayout llPopup = (LinearLayout) popupView.findViewById(R.id.llPopup);

    PackageManager pm = getPackageManager();

    Intent intent = new Intent();

    // In my case, NfcAdapter.ACTION_NDEF_DISCOVERED was not returning anything
    //intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
    intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED);

    List<ResolveInfo> resolvedInfoList = pm.queryIntentActivities(intent, 0);

    String packageName = "";

    for(ResolveInfo resInfo : resolvedInfoList) {

        packageName = resInfo.activityInfo.applicationInfo.packageName;

        // Exclude `packageName` from the dialog/popup that you show
        if (!packageName.equals("com.example.my.package.name")) {

            TextView tv = new TextView(this);

            tv.setText(packageName);

            llPopup.addView(tv);
        }            

    }

    popupWindow.showAtLocation(popupView, Gravity.CENTER, 0, 0);
}
Vikram
  • 51,313
  • 11
  • 93
  • 122
  • So I can create the chooser instead of android doing it? How would I do the whole process? If you could, can you show me exactly what I need to write? How do I "exclude" my name for example? – ginsunuva Jul 25 '13 at 18:44
  • Also, once an NFC chooser popup appeared for me, because I had 2 nfc readers installed, but it only showed those two. I don't know if android's default nfc reader would show up in a chooser. – ginsunuva Jul 25 '13 at 18:47
  • @ginsunuva I won't be able to update my answer right now as I'm busy. I'll leave a comment when I do. – Vikram Jul 25 '13 at 19:16
  • @ginsunuva See **Edit 1** above. – Vikram Jul 25 '13 at 20:55
  • Oh thanks, wow, I didn't know I'd have to build a chooser. But instead I decided I will user the PackageManager and just read through the pending activities, and if mine is the only one, to not even start a new activity. – ginsunuva Jul 25 '13 at 21:22
  • Or, would you happen to know anything about invoking android's default Nfc Service? – ginsunuva Jul 25 '13 at 21:23
  • @ginsunuva I don't think there's a `default Nfc Service`(I could be wrong here). Take a look at [The Tag Dispatch System](http://developer.android.com/guide/topics/connectivity/nfc/nfc.html#tag-dispatch) and [How NFC Tags are Dispatched to Applications](http://developer.android.com/guide/topics/connectivity/nfc/nfc.html#dispatching). – Vikram Jul 25 '13 at 21:28
  • When I scan a tag that my app cannot read, it defaults to an application (or service, idk) called `Nfc Service` or redirects to the browser depending on the mime type (I think someone redirects it to browser because browser itself never shows up to handle any type of nfc intent in a chooser popup). – ginsunuva Jul 25 '13 at 21:38
1

there is also another option than to do a own chooser here ( different looking chooser might confuse the user )

public void rethrowIntentExcludingSelf() {
    final ComponentName component = new ComponentName(this, getClass());
    this.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

    final Intent intent = this.getIntent();
    intent.setComponent(null);
    this.startActivity(intent);
    new android.os.Handler().postDelayed(
            new Runnable() {
                @Override
                public void run() {
                    getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
                }
            },250);
}

I am using it - and it works fine - just do not like this magic constant 250 - but do not yet see another way.

ligi
  • 39,001
  • 44
  • 144
  • 244