NFC tag detection is not calling onNewIntent and it's Launching From Main Activity

android_dev picture android_dev · Mar 20, 2014 · Viewed 8k times · Source

I am new to NFC. I tried to connect to NFC and share the text data from a device to another device.

I install my application on both devices and in one device I open my application and start tapping the device to the another device in order to transmit data over Beam.

On the other device, my application opens due to the Beam interaction. However, the activity is started with the default MAIN intent action and not with TAG_DISCOVERED (or similar NFC intent). Every time it does the same thing.

Also, it's not calling the onNewIntent() method. I tried to call onNewIntent from onCreate, but the intent action is still MAIN in that case. I expected to receive an NFC intent for the Beam interaction. So can you please tell me where did I go wrong?

In this code I am not sharing the data. I just need the tag first.

Manifest:

<activity android:name="com.framentos.hellonfc.MainActivity"
        android:clearTaskOnLaunch="true"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        android:screenOrientation="portrait" >
        <intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.nfc.action.TECH_DISCOVERED" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.nfc.action.TAG_DISCOVERED" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
        <meta-data
            android:name="android.nfc.action.TECH_DISCOVERED"
            android:resource="@xml/nfc_tech_filter" />
</activity>

Java code:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    ctx = this;
    // startHandler();
    _handler = new Handler();
    Button btnWrite = (Button) findViewById(R.id.button);
    message = (TextView) findViewById(R.id.nfcwriteTag);
    btnWrite.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

        }
    });
    adapter = NfcAdapter.getDefaultAdapter(this);
    if (adapter == null) {
        message.setText("NFC is not supported on this device.");
    }
    if (adapter.isEnabled()) {
        message.setText("NFC is Enabled on this device.");
    } else {
        message.setText("Please enable NFC to communicate.");
    }
    pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
            getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    IntentFilter tagDetected = new IntentFilter(
            NfcAdapter.ACTION_NDEF_DISCOVERED);
    tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
    IntentFilter tagTech = new IntentFilter(
            NfcAdapter.ACTION_TECH_DISCOVERED);
    tagTech.addCategory(Intent.CATEGORY_DEFAULT);
    IntentFilter tagDetect = new IntentFilter(
            NfcAdapter.ACTION_TAG_DISCOVERED);
    tagDetect.addCategory(Intent.CATEGORY_DEFAULT);
    writeTagFilters = new IntentFilter[] { tagDetected, tagTech ,tagDetect};
    // handleIntent(getIntent());
}


@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    Log.i(getPackageName(), "on New Intent is called...!");
    handleIntent(getIntent());
}

public void onPause() {
    super.onPause();
    WriteModeOff();
}

@Override
public void onResume() {
    super.onResume();
    WriteModeOn();
}

private void WriteModeOn() {
    writeMode = true;
    adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters,
            null);
}

private void WriteModeOff() {
    writeMode = false;
    adapter.disableForegroundDispatch(this);
}

Answer

Michael Roland picture Michael Roland · Apr 3, 2014

From the code that you posted in your question, I assume that you did not register your application to send a specific NDEF message. In that case, if your app is open on one device, Android will automatically Beam an NDEF message containing a URI record with the Play Store link of your app and an Android Application Record (AAR) to the other device.

So your second device will receive the following NDEF message:

+---------------------------------------------------------------------------------------+
| WKT:URI | http://play.google.com/store/apps/details?id=your.package.name&feature=beam |
+---------------------------------------------------------------------------------------+
| EXT:android:com:pkg | your.package.name                                               |
+---------------------------------------------------------------------------------------+

What happens now if your app is not already open on the second device is, that the Android Application Record (second record) will force your app to be started. However, looking at your manifest, you do not have an intent filter that matches the first record of this NDEF message (the Play Store URL). Consequently, Android thinks that you do not expect an NDEF message and uses the standard android.intent.action.MAIN (with category android.intent.category.LAUNCHER) to start your app (or rather the first activity of your app that has an intent filter for action MAIN with category LAUNCHER).

In order to receive an NFC intent together with the whole NDEF message in your app, you would need to define a proper intent filter that matches the first record in the above NDEF message:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="http"
          android:host="play.google.com"
          android:pathPrefix="/store/apps/details?id=your.package.name" />
</intent-filter>

Now, Android will recognize that your app/activity is ready to receive an NFC intent and pass the NDEF_DISCOVEREDintent to your activity. Note that you still won't receive that intent through onNewIntent() if your app is not already running. Instead you can get the intent that started your activity with the activity's getIntent() method. E.g. in onCreate/onStart/onResume, you could use

Intent intent = getIntent();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
    NdefMessage ndefMessage = null;
    Parcelable[] rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
    if ((rawMessages != null) && (rawMessages.length > 0)) {
        ndefMessage = (NdefMessage)rawMessages[0];
    }

    // TODO: do something with the received NDEF message
}

to get the intent and the NDEF message.

Regarding the intent filters you already have in your manifest:

  1. NDEF_DISCOVERED:

    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    

    An NDEF_DISCOVERED intent filter without a <data ... /> tag will never trigger on many NFC devices. You should always define what specific data you expect to be present in the launching NDEF record.

  2. TECH_DISCOVERED:

    <intent-filter>
        <action android:name="android.nfc.action.TECH_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <meta-data android:name="android.nfc.action.TECH_DISCOVERED"
               android:resource="@xml/nfc_tech_filter" />
    

    You should only use this intent filter if you wan't you application to be started upon detection of a certain NFC tag technology (you define the specific technologies in the nfc_tech_filter.xml file. For your Beam scenario, you would not use such an intent filter. Btw. the <category ...> tag is not used for this intent filter.

  3. TAG_DISCOVERED:

    <intent-filter>
        <action android:name="android.nfc.action.TAG_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    

    This intent filter should normally not be used in the manifest. It is meant as a fallback that triggers if no other app could possibly handle a detected NFC tag. Using this can lead to bad user-experience (e.g. your app being started for tags it can't or does not actually want to handle). It is primarily available for backward compatibility with API level 9 (?).

In order to also catch the NFC intents if your activity is already started, you could register for the foreground dispatch like this (in the onResume method):

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);

That way, you will receive any NFC discovery events that occur while your activity is in the foreground in your activity's onNewIntent method. They will be dispatched through an ACTION_TAG_DISCOVERED intent.