I'm trying to read an NFC tag from within my app, but every time I scan the tag it opens a default app (I think it's Android's default one...)
I would like to prevent any tag from being read outside my app when its open. How can I accomplish that?
ScanNFC.java (Activity to Read NFC Data):
public class ScanNFC extends AppCompatActivity {
public static final String MIME_TEXT_PLAIN = "text/plain";
public static final String TAG = "NfcDemo";
private NfcAdapter mNfcAdapter;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scan_nfc);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if (toolbar != null)
{
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
mTextView = (TextView) findViewById(R.id.tvTapNFC);
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter != null) {
mTextView.setText("Read an NFC tag");
} else {
mTextView.setText("This phone is not NFC enabled.");
}
handleIntent(getIntent());
}
@Override
protected void onResume() {
super.onResume();
setupForegroundDispatch(this, mNfcAdapter);
}
@Override
protected void onPause() {
stopForegroundDispatch(this, mNfcAdapter);
super.onPause();
}
@Override
protected void onNewIntent(Intent intent) {
handleIntent(intent);
}
private void handleIntent(Intent intent)
{
String action = intent.getAction();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
String type = intent.getType();
if (MIME_TEXT_PLAIN.equals(type)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
new AsyncNdefReaderTask().execute(tag);
} else {
Log.d(TAG, "Wrong mime type: " + type);
}
} else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
String[] techList = tag.getTechList();
String searchedTech = Ndef.class.getName();
for (String tech : techList) {
if (searchedTech.equals(tech)) {
new AsyncNdefReaderTask().execute(tag);
break;
}
}
}
}
private class AsyncNdefReaderTask extends NdefReaderTask
{
@Override
protected void onPostExecute(String result) {
Log.d("onPostExecute", "onPostExecute");
if (result != null) {
mTextView.setText("Read content: " + result);
}
}
}
public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter)
{
final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0);
IntentFilter[] filters = new IntentFilter[1];
String[][] techList = new String[][]{};
filters[0] = new IntentFilter();
filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
filters[0].addCategory(Intent.CATEGORY_DEFAULT);
try {
filters[0].addDataType(MIME_TEXT_PLAIN);
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("Check your mime type.");
}
adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList);
}
public static void stopForegroundDispatch(final Activity activity, NfcAdapter adapter) {
adapter.disableForegroundDispatch(activity);
}
}
AndroidMAnifest.xml:
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".ScanNFC"
android:label="@string/title_activity_scan_nfc"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain" />
<data android:scheme="http" />
</intent-filter>
</activity>
</application>
How can I prevent other apps from reading NFC tags when my app is running?
The key to this is to use either the foreground dispatch system (NfcAdapter.enableForegroundDispatch()
) or the reader-mode API (NfcAdapter.enableReaderMode()
). Note that the latter only works on Android 4.4+.
You already try to use the foreground dispatch system in your code. However, you currently only register for a very specific tag type (which probably does not match your tag):
public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter) {
final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0);
IntentFilter[] filters = new IntentFilter[1];
String[][] techList = new String[][]{};
filters[0] = new IntentFilter();
filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
filters[0].addCategory(Intent.CATEGORY_DEFAULT);
try {
filters[0].addDataType(MIME_TEXT_PLAIN);
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("Check your mime type.");
}
adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList);
}
With that code you register to receive only events for tags that contain an NDEF Text record (or a text/plain MIME type record).
You could instead register to be notified for any tag by simply using:
public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter) {
final Intent intent = new Intent(activity, activity.getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
final PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
adapter.enableForegroundDispatch(activity, pendingIntent, null, null);
}