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
Related
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 ...
}
}
}
I want to scan an NFC tag without using Intent. In other words I want to force the scan. I have already read the:
http://developer.android.com/guide/topics/connectivity/nfc/index.html
https://code.google.com/p/ndef-tools-for-android/
but both use Intents.
P.S.: My case is that the NFC tag is permanently attached to the device, so I cannot use intents.
Use foreground dispatch:
http://developer.android.com/guide/topics/connectivity/nfc/advanced-nfc.html#foreground-dispatch
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null || !nfcAdapter.isEnabled()) {
Log.e(TAG, "No NFC Adapter found.");
//finish();
}
Intent intent = new Intent(this, getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
// Handles all MIME based dispatches. You should specify only the ones that you need.
ndef.addDataType("*/*");
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("failed to add MIME type", e);
}
//intentFiltersArray = new IntentFilter[]{ndef,};
//Use no intent filters to accept all MIME types
intentFiltersArray = new IntentFilter[]{};
// The tech list array can be set to null to accept all types of tag
techListsArray = new String[][]{new String[]{
IsoDep.class.getName(),
NfcA.class.getName(),
NdefFormatable.class.getName()
}};
}
public void onPause() {
nfcAdapter.disableForegroundDispatch(this);
super.onPause();
}
public void onResume() {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
super.onResume();
}
public void onNewIntent(Intent intent) {
Tag nfcTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//do something with tagFromIntent
}
Easy.
When you hit your "flexibility" time... and you actually change. Use INTENt, read the tag, and save the information on your sdcard, shared preferences, cloud wherever.
And use this information each time you want to read the TAG. Instead read the file which was created las ttime when tag was attached.
Next time when you will remove tag and add another one, your file will be recreated.
Don't read tag, read file created by tag attaching to device.
You cannot do what you want. Reading a tag without an intent is not possible due to the way the Android NFC subsystem is built.
Also it is a very bad idea to glue a tag onto the backside of a phone. NFC will - as long as no tag has been detected - periodically check for the existence of a tag. Once a tag has been detected it will get powered over the air until the tag does not answer anymore.
If the tag is always in range of the phone it will drain battery life like crazy.
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've made an app that is called when the intent android.nfc.action.TAG_DISCOVERED is sent, but then I want to get the info of the card in the onNewIntent method, but I don't know how to handle this kind of nfc cards. I tried with the following code:
public void onNewIntent(Intent intent) {
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//do something with tagFromIntent
NfcA nfca = NfcA.get(tagFromIntent);
try{
nfca.connect();
Short s = nfca.getSak();
byte[] a = nfca.getAtqa();
String atqa = new String(a, Charset.forName("US-ASCII"));
tv.setText("SAK = "+s+"\nATQA = "+atqa);
nfca.close();
}
catch(Exception e){
Log.e(TAG, "Error when reading tag");
tv.setText("Error");
}
}
tv is a TextView, but when this code is executed it never gets changed.
OnNewIntent is called if your activity is already running and is set to be singleTask.
You'll want to make the code it's own method and call it in onCreate() as well as onNewIntent()
If you have other apps that can manage the same tag at higher level (NDEF_DISCOVERED or TECH_DISCOVERED) your app, that manage only the lower level, never will be called.
To use your app you need to open your app, than scan the tag.
If your app never starts, be sure to have done these steps:
In OnCreate() get your NfcAdapter:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
....
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
// Stop here, we definitely need NFC
Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show();
finish();
return;
}
//method to handle your intent
handleTag(getIntent());
}
In onResume enable the foreground dispatch:
#Override
public void onResume() {
super.onResume();
final Intent intent = new Intent(this.getApplicationContext(), this.getClass());
final PendingIntent pendingIntent = PendingIntent.getActivity(this.getApplicationContext(), 0, intent, 0);
mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
In onPause disable it:
#Override
protected void onPause() {
mNfcAdapter.disableForegroundDispatch(this);
super.onPause();
}
In onNewIntent call the function to handle your intent
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleTag(intent);
}
In your function handle your tag:
private void handleTag(Intent intent){
String action = intent.getAction();
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action){
// here your code
}
}
Don't forget to add the permissions in your manifest:
<uses-permission android:name="android.permission.NFC" />
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<activity>
....
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>
</activity>
More info here NFC Basis and here Read NFC tags