In my android app, once android manifest detects the nfc tag, it opens the loginactivity class.
loginactivity uses (NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction())) {} to run the various functions on the tag.
I would also like to implement nfc tag detection on the page itself using intentfilter and enableForegroundDispatch
How do i combine both types of nfc detection in the same class?
What i think might work:
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction())) {
onNewIntent(GetIntent());
}
else{
pendingIntent = PendingIntent.getActivity(this,0,new Intent(this,getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),0);
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
intentFiltersArray= new IntentFilter[] {ndef, };
techListsArray = new String[][] {new String[] {IsoDep.class.getName()}};
}
Of course the onPause and onResume has enable foreground dispacth.
Is this the correct way to do it? Thanks!
You can easily combine both methods (using NFC discovery intents in your application manifest to get your activity started upon detection of NFC tags and using foreground dispatch to receive events while your activity is visible in the foreground).
For the manifest part you would do something like this (or adapted to whatever event you want to trigger upon):
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfc_tech_filter" />
And your nfc_tech_filter.xml could look like this (or adapted to whatever tag technology you want to trigger upon):
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
</tech-list>
</resources>
Then, in your activity's onCreate(), onStart() or onResume() method, you would grab the intent and feed it to your intent handling method. That could look something like this:
handleIntent(getIntent());
In order to use the foreground dispatch, you would register the for the foreground dispatch system in your activity's onResume() method (don't forget to unregister in onPause()!):
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
Alternatively you could also only register for TECH_DISCOVERED intents with IsoDep technology (like in the manifest):
nfcAdapter.enableForegroundDispatch(this, pendingIntent,
new IntentFilter[] { new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED) },
new String[][] { new String[] { IsoDep.class.getName() } }
);
Then you will receive NFC intents in your activity's onNewIntent() method. Thus, you would instruct your intent handler to process those intents too:
public void onNewIntent(Intent intent) {
handleIntent(intent);
}
Finally, you could use an intent handler like this, to process the intents:
private void handleIntent(Intent intent) {
String action = intent.getAction();
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) ||
NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
// do something ...
}
}
}
Related
I'm not at all familiar with NFC tag detection and I'm trying to set up a listener for any NFC tag detected in an activity. I want to just display a toast message when the activity detects an NFC tag but I'm having trouble doing so.
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity)
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Toast.makeText(applicationContext, "NFC Tag Detected", Toast.LENGTH_LONG).show()
}
And in my manifest I have this:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
I'm testing this using a Samsung Galaxy S6 and S7. When I put them together while the activity is running on one of them, I want to be able to see a toast message but so far nothing is showing up. I don't need to read the tag, I don't care what type of tag it is, I only need to know that there was a tag detected.
The intent filters that you registered in your app manifest do not make much sense.
android.nfc.action.TAG_DISCOVERED (when used in the manifest) is only a fall-back mechanism to catch any NFC tags that are not handled by other apps.
android.nfc.action.NDEF_DISCOVERED also needs a data type specification to actually catch NFC tags that contain specific NDEF records. It won't match any tags without one.
android.nfc.action.TECH_DISCOVERED also needs a tech to catch specific tag technologies. It won't match any tags without one.
Moreover, you would probably want to put each intent action in a separate <intent-filter> to have better control over categories, data types, etc.
However, since you are only interested in receiving NFC discovery events while your activity is in the foreground, you have better and (somewhat) more reliable options to detect tags: the foreground dispatch system and the reader mode API.
You would want to choose between one of them:
If you are on Android 4.4+ and if you are only interested in detecting other tags (and no peer-to-peer mode devices). I would strongly suggest that you use the reader mode API since it gives you much better control over what tags you want to detect and how these tags should be processed. Also, if you want to be able to detect and speak to a HCE application on another Android device, your only option is the reader mode API.
If you also want to support devices before Android 4.4 or if you also want to receive data from peer-to-peer devices (e.g. over Android Beam), you will need to stick to the foreground dispatch system.
Foreground Dispatch System
You can register your activity to receive NFC intents during onResume():
#Override
public void onResume() {
super.onResume();
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
That's probably something like this in Kotlin (though not tested):
fun onResume() {
super.onResume()
val pendingIntent = PendingIntent.getActivity(this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null)
}
Make sure to unregister this again during onPause():
#Override
public void onPause() {
super.onPause();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.disableForegroundDispatch(this);
}
Kotlin:
fun onPause() {
super.onPause()
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
nfcAdapter.disableForegroundDispatch(this)
}
You will then receive NFC events as TAG_DISCOVERED intents through onNewIntent():
#Override
public void onNewIntent(Intent intent) {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
// TODO: process intent
}
}
Kotlin:
fun onNewIntent(intent: Intent) {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
// TODO: process intent
}
}
Reader Mode API
With the reader mode API, you register your activity for receiving NFC callbacks (no intents are used here!) during onStart():
#Override
public void onStart() {
super.onStart();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableReaderMode(this, new NfcAdapter.ReaderCallback() {
#Override
public void onTagDiscovered(Tag tag) {
// TODO: use NFC tag
}
}, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B | NfcAdapter.FLAG_READER_NFC_F | NfcAdapter.FLAG_READER_NFC_V | NfcAdapter.FLAG_READER_NFC_BARCODE, null);
}
Kotlin:
fun onStart() {
super.onStart()
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
nfcAdapter.enableReaderMode(this, object : NfcAdapter.ReaderCallback() {
fun onTagDiscovered(tag: Tag) {
// TODO: use NFC tag
}
}, NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_NFC_B or NfcAdapter.FLAG_READER_NFC_F or NfcAdapter.FLAG_READER_NFC_V or NfcAdapter.FLAG_READER_NFC_BARCODE, null)
}
You also should make sure to unregister during onStop():
#Override
public void onStop() {
super.onStop();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.disableReaderMode(this);
}
Kotlin:
fun onStop() {
super.onStop()
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
nfcAdapter.disableReaderMode(this)
}
You receive discovered tag handles through the onTagDiscovered(Tag tag) callback method above. Instead, you could, of course, also implement the NfcAdapter.ReaderCallback interface on your activity class and pass this instead of an anonymous class to the enableReaderMode method.
Since I changed my NFC foreground dispatch registration to use the TECH_DISCOVERED intent filter, I keep having to choose between multiple applications to process the NFC tag. Is there a way to directly receive the NFC intent in my app when a tag is discovered?
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);
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
IntentFilter[] mFilters = new IntentFilter[] {
ndef
};
final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0);
adapter.enableForegroundDispatch(activity, pendingIntent, mFilters, null);
}
public static void stopForegroundDispatch(final Activity activity, NfcAdapter adapter) {
adapter.disableForegroundDispatch(activity);
}
The TECH_DISCOVERED intent filter requires a tech-list. Consequently, your current foreground-dispatch registration does not listen for any tag technologies at all.
When you register that intent filter through the manifest, you would use
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfc_tag_filter" />
to do that. Similarly, when you register for the foreground dispatch with the enableForegroundDispatch() method, you need to specify a tech-list (an array of string arrays) in the last argument of enableForegroundDispatch(). E.g. to listen for all possible tag technologies (i.e. NFC-A or NFC-B or NFC-F or NFC-V or NFC-Barcode), you would use:
IntentFilter[] filters = new IntentFilter[] {
new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED),
};
String[][] techList = new String[][] {
new String[] { NfcA.class.getName() },
new String[] { NfcB.class.getName() },
new String[] { NfcF.class.getName() },
new String[] { NfcV.class.getName() },
new String[] { NfcBarcode.class.getName() },
};
adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList);
Note that if you want to filter for any tag through the foreground dispatch system, you can also simply use the catch-all foreground dispatch:
adapter.enableForegroundDispatch(activity, pendingIntent, null, null);
However, be aware that a TAG_DISCOVERED intent will be delivered to your app in that case.
I'm guessing most of you face palmed when you saw this title, and thought we've seen this before well I agree, but I've tried everything to solve this issue and it wont remedy. So here goes.
My app can be launched from the home screen(icon) and everything works OK it scans a NFC tag and sends the relevant data to the server.
However what I also want is for the App to be launched from NFC tag, which it does no problem.
However! when the app is launched via the NFC tag you then remove the phone and re-present it to the tag it should then read the tag and send the data to the server. Exactly the same behaviour as launching the app via the the icon, but when you scan the NFC tag this time a new instance of the app is launched which ultimately kicks the current instance down the stack, the version of the instance that was busy reading the tag so I get a ANR.
in a nutshell:
from home screen -> scan a tag -> app launches.
you then rescan the tag->
The current instance of the app disappears to be replaced by a new instance.
I want the previous instance to stay where it was and work.
A few guesses from me at this point would be that:
-The NFC is not getting properly registered to app, hence the the OS doing default behaviour.
-There is a flag not being set correctly when launched via a NFC tag telling the OS the activity does not need to relaunched.
anyway here's the code:
<uses-permission android:name="android.permission.NFC"/>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http"android:host="#string/URL_for_intent_filtering" android:pathPrefix="/id/" />
//Procedure of NFC startup called from OnCreate
private void startNFC()
{
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if(! mNfcAdapter.isEnabled())//check the NFC is switched on
{
mMainActivityHandler.sendEmptyMessage(NFC_DISABLED);
}
//its ok to carry on and instantiate the NFC even though its not enabled.
if(mNfcAdapter != null)
{
int requestID = (int) System.currentTimeMillis();
mPendingIntent = PendingIntent.getActivity(this, requestID, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_UPDATE_CURRENT);
IntentFilter intentF = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
intentF.addDataScheme("http");
intentfilter = new IntentFilter[] {intentF};
if(DEBUG) Log.i("DEBUG","+++++++++++++++++++++++++++++++++++++++++++++++ NFC HAS BEEN STARTED");
}else
{
//we have a problem with the NFC adapter probably because the device doesn't have one.
if(DEBUG) Log.i("DEBUG","+++++++++++++++++++++++++++++++++++++++++++++++ NFC HAS FAILED");
mMainActivityHandler.sendEmptyMessage(NFC_FAILED);
}
}
#Override
protected void onPause()
{
if(DEBUG) Log.i("MA","+++++++++++++++++++++++++++++++++++++++ ON PAUSE");
try{
mNfcAdapter.disableForegroundDispatch(this);
}catch(Exception e)
{
}
super.onPause();
}
//and here we should reinstate the NFC adapter
#Override
protected void onResume()
{
if(DEBUG) Log.i("MA","+++++++++++++++++++++++++++++++++++++++ ON RESUME");
//setupDataListener(true);
setupServerComms(getApplicationContext());
//mNfcAdapter.enableForegroundDispatch(this,mPendingIntent,intentfilter,null);
mNfcAdapter.enableForegroundDispatch(this,mPendingIntent,null,null);
super.onResume();
}
Just add this line in your AndroidManifest.xml into your Activity and you are probably done.
android:launchMode="singleTask"
So what you want is for the app to catch the intent and then read the information and do something with it correct? Well you have a pending intent that is used to do this but your setup seems a little different from what I usually do. Here is an example of how I use a pending intent to catch the tag data (from there you can do whatever you want like send it to a server):
Class example{
NfcAdapter adapter;
Tag motor;
String FileName;
PendingIntent detectTag;
IntentFilter NFC;
IntentFilter [] Catcher;
String [][] TechList;
TextView readToField;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_activity);
// Catch NFC detects
adapter = NfcAdapter.getDefaultAdapter(this);
detectTag = PendingIntent.getActivity(this,0,new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
NFC = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
Catcher = new IntentFilter[] {NFC,};
TechList = new String [][]{new String[]{android.nfc.tech.NfcV.class.getName()}};
}
public void onNewIntent(Intent intent)
{
super.onNewIntent(intent);
motor = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Bridge bridge = (Bridge) getApplication();
bridge.setCurrentTag(motor);
}
public void onPause()
{
super.onPause();
adapter.disableForegroundDispatch(this);
}
public void onResume()
{
super.onResume();
adapter.enableForegroundDispatch(this,detectTag ,Catcher , TechList);
}
}
The Bridge object is just a class I use to save the tag information so I can send information to tags from other (behind the scenes) classes.
Finally, I have always been told that for onResume and onPause you should call the super.method first.
I am developing an app that will use NFC tags for identification. All examples I find however, are about starting an app when a certain card is read. I tried looking for an example or documentation on how to do it differently, to no avail.
What I want is:
user starts my app
user scans NFC card
app decides next step
I got some code working now, I just don't get the tag data:
In onCreate:
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
try {
tech.addDataType("*/*");
} catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[] { tech };
And in onResume:
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techList);
The intent only gets there when the app is active, but the Intent I receive is the PendingIntent I defined myself, not the ACTION_TECH_DISCOVERED intent I want.
I found part of the answer here: NFC broadcastreceiver problem
That solution doesn't provide a complete working example, so I tried a little extra. To help future visitors, I'll post my solution. It is a NfcActivity which subclasses Activity, if you subclass this NfcActivity, all you have to do is implement its NfcRead method, and you're good to go:
public abstract class NfcActivity extends Activity {
// NFC handling stuff
PendingIntent pendingIntent;
NfcAdapter nfcAdapter;
#Override
public void onResume() {
super.onResume();
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
#Override
protected void onPause() {
super.onPause();
nfcAdapter.disableForegroundDispatch(this);
}
// does nothing, has to be overridden in child classes
public abstract void NfcRead(Intent intent);
#Override
public void onNewIntent(Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
NfcRead(intent);
}
}
}
If you want that the user first starts your app and only then scans an NFC card, I would recommend using NFC foreground dispatch. In that way, your Activity does not need to have any intent filters in the manifest (and thus will never be called by accident when the user scans another NFC card).
With foreground dispatch enabled, your Activity can receive all NFC intents directly (without any app chooser pop-ups) and decide what to do with it (e.g. pass it on to another Activity).
I want my app to listen to nfc tags only when is activated. For this I tried to register an nfc listener as following, without any success.
IntentFilter filter = new IntentFilter("android.nfc.action.TECH_DISCOVERED");
registerReceiver(nfcTagListener, filter);
BroadcastReceiver nfcTagListener = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Log.d("nfc", "" + tag.getId());
}
}
};
I tried as well to declare the intent in my manifest following the apidemos and works perfectly, it launches my activity and gets the nfc tag id. But this is not what I want, I want to detect the tag id only when I am inside that activity. I am thinking it might be related to the following line included in the api demos. But I dont know how to do that programatically
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/filter_nfc">
Any hint?
Thanks!
Try to use Foreground Dispatch System.
To enable it, on the activity's onCreate method, you should prepare some stuffs:
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
after that, create the IntentFilters (in my example, all actions are handled using Intent Filters):
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("*/*");
} catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
IntentFilter tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
try {
tech.addDataType("*/*");
} catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
IntentFilter[] intentFiltersArray = new IntentFilter[] { tag, ndef, tech };
after that, you'll need a String array to contain supported technologies:
String[][] techList = new String[][] { new String[] { NfcA.class.getName(),
NfcB.class.getName(), NfcF.class.getName(),
NfcV.class.getName(), IsoDep.class.getName(),
MifareClassic.class.getName(),
MifareUltralight.class.getName(), Ndef.class.getName() } };
in the onResume method, you should enable the foreground dispatch method:
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techList);
and disable in onPause:
#Override
protected void onPause() {
super.onPause();
nfcAdapter.disableForegroundDispatch(this);
}
By this way, you have successfully initialized the needed mechanism. To handle a received Intent you should override the onNewIntent(Intent intent) method.
#Override
public void onNewIntent(Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
// reag TagTechnology object...
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
// read NDEF message...
} else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
}
}
Note: if you want to handle the intents by only foreground dispatching, do not enable Intent Dispatch System in your manifest file, just give the right permissions to your application there.
I hope that helps.
If you do not want to use foreground mode, your can always programmatically enable or disable intent filters.
The NDEF Tools for Android project has working sample using foreground mode, also detects
NFC device support
NFC enabled / disabled on activity launch, or later changed
NFC push enabled / disabled on activity launch, or later changed