Search code examples
androidtestingandroid-fragmentsnfcforeground

How do I test/simulate NFC foreground dispatching?


I know you can simulate NFC tags simply by creating an intent and starting and activity with it. From my understanding and testing this only works, if you add intent-filters to your manifest.

I want to simulate tags and dispatch them to an activity/fragment via foreground dispatch only, so starting the activity with intent-filters in the manifest is not an option for me.

The structure of my program looks like this: Activity -> several Fragments, one of them is interested in NFC tags via foreground dispatching. The nfc-fragment has the necessary code for pendingIntent and dispatch-enabling and -disabling. The activity implements the onNewIntent() method, which invokes further handling of the intent via a method in the nfc-fragment, if the nfc-fragment is active.

The program works fine, but I need to test the behaviour with automated tests.

I've already tried using

final Intent intent = new Intent(NfcAdapter.ACTION_TAG_DISCOVERED);
intent.putExtra(NfcAdapter.EXTRA_ID, "1234567890".getBytes());
solo.getCurrentActivity().startActivity(intent);

but this just gives me an ActivityNotFoundException. Currently I retrieve the nfc-fragment and call the method to handle the intent manually from inside the test, but this gives me

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

since the method involves updating Views and whatnot. It works somehow, since I just need to switch to a different activity or fragment and then go back to get the view updates, but I'd like to know if there is a better and cleaner way of doing this.

I appreciate your help, let me know if you need more information.


Solution

  • I assume that solo.getCurrentActivity() refers to the activity that should receive the NFC intent, otherwise you have to adapt the activity class and context to refer to the right values:

    Class activityCls = solo.getCurrentActivity().getClass();
    Context packageContext = solo.getCurrentActivity();
    

    Then you create the pending intent (better yet, you could use the PendingIntent that was passed to the enableForegroundDispatch() method):

    PendingIntent pendingIntent = PendingIntent.getActivity(
        packageContext,
        0,
        new Intent(packageContext, activityCls).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
        0);
    

    Set up the parameters of the NFC intent:

    String intentAction = NfcAdapter.ACTION_TAG_DISCOVERED;
    Tag tag = ...;
    byte[] tagId = ...;
    NdefMessage ndefMessage = ...;
    

    Prepare the NFC intent:

    Intent intent = new Intent();
    intent.setAction(intentAction);
    intent.putExtra(NfcAdapter.EXTRA_TAG, tag);
    intent.putExtra(NfcAdapter.EXTRA_ID, tagId);
    if (ndefMessage != null) {
        intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] { ndefMessage });
    
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intentAction)) {
            Uri uri = message.getRecords()[0].toUri();
            String mime = message.getRecords()[0].toMimeType();
            if (uri != null) {
                intent.setData(uri);
            } else {
                intent.setType(mime);
            }
        }
    }
    

    Send the pending intent using the parametrization set-up above:

    pendingIntent.send(packageContext, Activity.RESULT_OK, intent);