2

I'm currently writing a couple of NFC enabled apps for Android and wanted to know how I can stop my application from being in the "choose application" list that's opened whenever a tag is scanned from the launcher or a non-NFC app. I only want my apps to be able to read the tags when they are open.

My current intent filters:

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>
<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" />

nfc_tech_filter.xml:

<tech-list>
    <tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
<tech-list>
    <tech>android.nfc.tech.Ndef</tech>
</tech-list>
Michael Roland
  • 39,663
  • 10
  • 99
  • 206

2 Answers2

6

You currently have a couple of NFC related intent filters in your app's manifest:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
    <action android:name="android.nfc.action.TECH_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

By adding such intent filters to the manifest of your app, you indicate that certain activities should be started (or presented as options in the intent chooser if multiple activities are registered for the same intents) upon these NFC tag discovery events.

Consequently, you need to remove those intent filters from the AndroidManifest.xml if you don't want your app to be launchable by those intents.

However, you would then also lose the ability to listen for those intents while an activity of your app is in the foreground. Since NFC intents are activity intents, you can't receive them through dynamically registered broadcast receivers (cf. RubbelDieKatz's answer). Instead, the Android API provides alternative means to catch NFC related events while an activity of your app is displayed in the foreground (this also allows your app to get precedence over other apps, which is not possible with manifest-based NFC intent filters):

  1. Since API level 10 (basically since the beginning of Android NFC), Android features the foreground dispatch system. You can register your foreground activity to receive NFC events by using the method NfcAdapter.enableForegroundDispatch() upon onResume() (don't forget to unregister when your activity loses focus in onPause()):

    public void onResume() {
        super.onResume();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
        adapter.enableForegroundDispatch(this, pendingIntent, null, null);
    }
    
    public void onPause() {
        super.onPause();
        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
        adapter.disableForegroundDispatch(this);
    }
    

    You could even register to receive only a specific subset of NFC discovery events by providing the optional tech-list and intent-filters arguments to enableForegroundDispatch().

    Once you registered your activity with the foreground dispatch system, you will receive NFC events through the onNewIntent() callback:

    public void onNewIntent(Intent intent) {
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    
        ...
    }
    
  2. Starting with API level 19, there is the new NFC reader mode API. You can now register to receive regular callbacks (instead of going through the intent dispatch which sometimes causes soome hickups). You can do this by using the method NfcAdpater.enableReaderMode():

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        ...
    
        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
        adapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_A, null);
    }
    

    By using other flags in addition to (or instead of) NfcAdapter.FLAG_READER_NFC_A you could set to reader mode to listen for other tag technologies as well.

    You will then receive callbacks upon tag discovery (the receiver needs to implement the callback interface NfcAdapter.ReaderCallback):

    public void onTagDiscovered(Tag tag) {
        ...
    }
    

Also see What's the difference between enableReaderMode and enableForegroundDispatch? for a more detailed comparison between the two options.

Michael Roland
  • 39,663
  • 10
  • 99
  • 206
  • 2
    Thanks for this answer, I am using the foreground dispatch system already, my apps are now working as expected. I also removed the filters in my manifest. – Alexander Krämer Aug 30 '17 at 09:13
1

You're using the NFC and Tech Discovered intent-filters. You could remove them and register intent listeners programmatically in your app.

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
    <action android:name="android.nfc.action.TECH_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

About registering the intent: Here's a very good answer.

In your onCreate method you can register a receiver like this:

private BroadcastReceiver receiver;

@Override
public void onCreate(Bundle savedInstanceState){

  // your oncreate code should be

  IntentFilter filter = new IntentFilter();
  filter.addAction("SOME_ACTION");
  filter.addAction("SOME_OTHER_ACTION");

  receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      //do something based on the intent's action
    }
  };
     registerReceiver(receiver, filter);
}

Remember to run this in the onDestroy method:

 @Override
 protected void onDestroy() {
  if (receiver != null) {
   unregisterReceiver(receiver);
   receiver = null;
  }
  super.onDestroy();
 }

Another interesting quote from the answer below that one:

One important point that people forget to mention is the life time of the Broadcast Receiver. The difference of programmatically registering it from registering in AndroidManifest.xml is that. In the manifest file, it doesn't depend on application life time. While when programmatically registering it it does depend on the application life time. This means that if you register in AndroidManifest.xml, you can catch the broadcasted intents even when your application is not running.

Edit: The mentioned note is no longer true as of Android 3.1, the Android system excludes all receiver from receiving intents by default if the corresponding application has never been started by the user or if the user explicitly stopped the application via the Android menu (in Manage → Application). https://developer.android.com/about/versions/android-3.1.html

This is an additional security feature as the user can be sure that only the applications he started will receive broadcast intents.

So it can be understood as receivers programmatically registered in Application's onCreate() would have same effect with ones declared in AndroidManifest.xml from Android 3.1 above.

If you unregister your receivers when your activity is killed, your application won't receive these broadcasts anymore.

RubbelDieKatz
  • 1,134
  • 1
  • 15
  • 32