Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Android's NFC foreground dispatch system have a bug?

I have an annoying issue with the foreground dispatch behavior. Sometimes instead of calling onNewIntent(), it completely recreates the activity, which breaks the app's workflow.

My concrete situation: Activity A is the MainActivity, which uses the foreground dispatch. Everything works as it should. However, in my activity B, which is launched from the browser (VIEW action), the foreground dispatch doesn't work under some circumstances anymore.

The workflow:

  • I start the MainActivity, switch to the browser (without closing the MainActivity), launch activity B and attach my NFC device --> it creates a new activity B.

  • I start the MainActivity and close it again. After that I switch
    to the browser, launch activity B and attach my NFC device --> everything works with onNewIntent()

The code is correct, e.g. if I attach the NFC device in the first scenario twice, it works as it should at the second time, but not at the first time. In the MainActivity and activity B I definitively call the disableForegroundDispatch() method in the activity's onPause() method.

Is there a solution for my specific problem? For me it sounds like a bug.

Edit:

public void resume(Activity targetActivity) {
    if (nfc != null && nfc.isEnabled()) {
        // nfc is the default NFC adapter and never null on my devices

        Intent intent = new Intent(targetActivity, targetActivity.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(targetActivity, 0, intent, 0);
        nfc.enableForegroundDispatch(targetActivity, pendingIntent, null, new String[][] { new String[] { IsoDep.class.getName() } });
    }
}

public void pause(Activity targetActivity) {
    if (nfc != null && nfc.isEnabled()) {
        nfc.disableForegroundDispatch(targetActivity);
    }
}

These methods are called in the corresponding methods in each activity. Thanks for the help!

Solution: After a very long research I finally found the issue. Logcat printed:

  • startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: Intent

I found other issues at Stackoverflow, where people had have the same issue with the NotificationManager, but all the hints didn't help me. Adding the flag singleTask to my activity B did the trick for me, but to be honest I don't understand it, because the context is always an activity.

I removed all the code from the MainActivity and the first scenario still didn't work. I romved the MainActivity from the manifest and after that everything was fine. Maybe it is a problem, that an app instance is running and activity B is launched from the browser? I don't know.

Anyway, thanks for the help NFC guy!

like image 625
vRallev Avatar asked Nov 04 '22 01:11

vRallev


1 Answers

The IntentFilter[] that you pass to enableForegroundDispatch is empty. So your NFC intent probably arrive at your Activity due to the IntentFilter(s) in the manifest file. This explains the behaviour you observe, as an NFC intent always creates a new instance when delivered this way.

Add something like this instead to your code for enabling foreground dispatch:

IntentFilter[] iFilters = new IntentFilter[2];
iFilters[0] = new IntentFilter();
iFilters[0].addAction("android.nfc.action.TECH_DISCOVERED");
iFilters[1] = new IntentFilter();
iFilters[1].addAction("android.nfc.action.TAG_DISCOVERED");
iFilters[1].addCategory(Intent.CATEGORY_DEFAULT);

And pass that as parameter to enableForegroundDispatch.

UPDATE: I recently learned more about this specific problem. It is caused by the way Android determines in which task a new Activity should be launched. I don't know or understand the specific details of how that works, but the effect is that:

  1. When Activity B is launched from the Browser, it is created in the Browser's task
  2. When the NFC intent arrives, the system determines that a new Activity B is to be created in Activity A's task

Because of 2., the SINGLE_TOP is not ignored: there is only one instance of Activity B at the top of A's task. When Activity A is closed, it's task has disappeared, so Activity B will always be created in the Browser's task, as you have observed.

You may feel that this is an Android bug in this case (I do, I think), but this behaviour of how to create activities in which task is so fundamental to Android that many apps rely on it. So it is very unlikely that this will ever change.

Possible work-around: declare Activity B with launchMode "singleTask" (or "singleInstance"). Then a new (3rd) task will be created when B is launched.

like image 103
NFC guy Avatar answered Nov 13 '22 16:11

NFC guy