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).
Related
I am building an Android app using Eclipse and Android SDK. I would like to implement an NFC P2P function in my app in the sense that when you place the 2 phones back to back, both send a string and receive a string automatically. This would of course happen in a separate activity. I have managed to send a custom tag (String) but have been unable to intercept it and use it afterwards in the app code. How can I do this?
This is what I have so far:
public class MainActivity extends Activity {
public NfcAdapter mAdapter;
PendingIntent mPendingIntent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAdapter = NfcAdapter.getDefaultAdapter(this);
NdefRecord rec = NdefRecord.createUri("www.stackoverflow.com");
NdefMessage ndef = new NdefMessage(rec);
mAdapter.setNdefPushMessage(ndef, this);
}
I have spent a lot of time trying to find and understand solutions to intercept the tag. Unsuccessfully.
Thank you for your help.
You can use the foreground dispatch system to receive the NDEF message within your activity:
In 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);
Do something upon receiving the intent:
public void onNewIntent(Intent intent) {
...
}
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 currently trying to intercept all NFC communication on an Android Device. I have tried using a foreground dispatch will null for both the IntentFilters and TechList though when a picture is being beamed over and my application is in the foreground I do not intercept it. Things like Contacts however are intercepted.
Does anyone know how to also intercept things like pictures so that I am always grabbing all of the items sent to the phone? I do not even care about the picture, all I care about is the tag.
Thank you for all the help.
Your #enableForegroundDispatch method is like this?
protected final void enableNfcEventDiscover() {
final Intent intent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
if (nfcAdapter == null) {
throw new IllegalStateException(
"Cannot enable discover nfc events, you need set the NfcAdapter first");
} else {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
}
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