Search code examples
xamarinxamarin.androidnfcndefandroid-applicationrecord

Reading NFC tags from outside of app returns no data


I have a xamarin app that is reading NFC tags. It works fine when the app is open, but if the app is in the background or closed, it is unable to read the data from the tag.

My intent filter:

[IntentFilter(
     new[] {"android.nfc.action.NDEF_DISCOVERED", "android.intent.action.VIEW", "android.intent.action.MAIN"},
     Categories =
         new[]
         {
             "android.intent.category.DEFAULT", "android.intent.category.BROWSABLE",
             "android.intent.category.LAUNCHER"
         },
     DataScheme = "http", DataHost = "app.myDomain.com")]

And in my OnNewIntent, my intent.Data is always coming back as null when scanned from outside the app. Are there any thoughts on where my problem may be at?

OnResume:

nfcAdapter.EnableForegroundDispatch(this, nfcPendingIntent, nfcIntentFiltersArray, null);

OnNewIntent:

protected override void OnNewIntent(Intent intent)
{            
if (AndroidNFCHelper.isNfcIntent(intent))
{
    DisplayMessage("And it is an NFC intent");
}

var tag = intent.GetParcelableExtra(NfcAdapter.ExtraTag) as Tag;

if (tag == null)
{
    Debug.WriteLine("tag is null");
    return;
}

//rest of OnNewIntent code...
}

So the interesting thing, when scanned outside of the app, AndroidNFCHelper.isNfcIntent comes back as false. When the same NFC tag is scanned in the app, it comes back as true. Continuing on, tag ends up as null and just hits the return statement. When scanned in the app, tag is not null and continues on.

PrepareForegroundNFCHandling - gets called in OnCreate():

private void PrepareForegroundNFCHandling()
{

var tagDetected = new IntentFilter(NfcAdapter.ActionTagDiscovered);
var ndefDetected = new IntentFilter(NfcAdapter.ActionNdefDiscovered);

nfcIntentFiltersArray = new[] { ndefDetected, tagDetected};

var intent = new Intent(this, GetType()).AddFlags(ActivityFlags.SingleTop | ActivityFlags.BroughtToFront);
nfcPendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);

nfcAdapter = NfcAdapter.GetDefaultAdapter(Application.ApplicationContext);            
}

Solution

  • The NDEF_DISCOVERED intent only filters for the type of the first record in a tag's NDEF message.

    As your app is started with intent action MAIN (AndroidNFCHelper.isNfcIntent(intent) is false) and without any Tag object in the intent extras (intent.GetParcelableExtra(NfcAdapter.ExtraTag) is null), your NDEF message probably contains an Android Application Record besides the URL.

    The reason why your app is started even if the NDEF message (read as "first record") does not match your intent filter is that the AAR will fall back to starting your app as if the launcher icon was clicked (i.e. action MAIN, category LAUNCHER) if there is no matching NFC intent filter.

    So your NDEF message probably looks something like this:

    +----------------------------------------------+
    | SOME RECORD                                  |
    +----------------------------------------------+
    | WKT: URI | http://app.mydomain.com/p/9       |
    +----------------------------------------------+
    | EXTERNAL: android.com:pkg | com.mydomain.app |
    +----------------------------------------------+
    

    Or simply like this:

    +----------------------------------------------+
    | EXTERNAL: android.com:pkg | com.mydomain.app |
    +----------------------------------------------+
    | WKT: URI | http://app.mydomain.com/p/9       |
    +----------------------------------------------+
    

    In the first case, you would need to modify the intent filter to match your record "SOME RECORD". In the latter case, you would need to modify your intent filter to match the external type "android:com:pkg":

    DataScheme = "vnd.android.nfc", DataHost = "ext", DataPathPrefix = "/android.com:pkg"
    

    Or, even better, you move your URL to the beginning of the NDEF message and the AAR to the end of the NDEF message:

    +----------------------------------------------+
    | WKT: URI | http://app.mydomain.com/p/9       |
    +----------------------------------------------+
    | EXTERNAL: android.com:pkg | com.mydomain.app |
    +----------------------------------------------+