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
Related
I'm actually developping an app with an activity that has to handle a NFC tag to make the user able to use this application.
This activity is called when the app is launched and on resume.
Most of the time this works fine, but from time to time the phone (Samsung galaxy xcover 4) stops looking for new NFC tags, it doesn't even play a sound on detection.
I tried, when this bug append, to use another application from Play Store to handle the NFC tag, but nothing happened.
Here is my detection activity :
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null || !nfcAdapter.isEnabled())
{
finish();
return;
}
final Intent intent = new Intent(this.getApplicationContext(), this.getClass());
final PendingIntent pendingIntent = PendingIntent.getActivity(this.getApplicationContext(), 0, intent, 0);
//method to handle your intent
handleTag(getIntent());
}
#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);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
#Override
protected void onPause()
{
super.onPause();
nfcAdapter.disableForegroundDispatch(this);
}
#Override
protected void onNewIntent(Intent intent)
{
super.onNewIntent(intent);
handleTag(intent);
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
}
private void handleTag(final Intent intent)
{
String action = intent.getAction();
final Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action))
{
try
{
Class isoDep = Class.forName("android.nfc.tech.IsoDep");
Method isoDep_get = isoDep.getDeclaredMethod("get", Tag.class);
final IsoDep techIsoDep = (IsoDep) isoDep_get.invoke(null, tag);
if (techIsoDep != null)
{
// --- Tag detected
}
}
catch (Exception e)
{
Log.e(TAG, "Exception while processing IsoPcdA object", e);
}
}
}
Edit :
I noticed this comes from the fact that, even if the screen doesn't turn black, the cpu or NFC reader goes to sleep mode, I have to lock and unlock the phone to make it work again, I'm now looking for a way to keep the cpu running all the time, what I've tried :
Only works with Galaxy Xcover 3 (Android 6.0.1)
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Tag");
wl.acquire();
I'm still not able to make it work on Galaxy Xcover 4 (Android 8.1)
Tried with :
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
But didn't work also.
This can happen when you are using Debugging mode in Android Studio and the phone lose the tag. the phone will not able to detect a tag anymore else when you turn off the NFC and turn it on again.
I didn't read your code, because u said the same happend with another apps.
Just try to turn the NFC off and on in your phone.
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.
I got an application that reads and writes nfc tags with ndef data format. This all seems to be ok. But whenever i try to get closer a tag to my phone, it produces a new activity. I only want to obtain data on the tag without opening new intent. So i can make my app decide whether the tag has been tapped two times in a row.
The code that i handle coming tags:
public class MainActivity extends Activity {
public static Context myContext;
private Button myButton;
private int currentID, currentBalance;
protected void onCreate(Bundle savedInstanceState) { // Firstly Created when
// a tag tapped to
// the phone
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myButton = (Button) findViewById(R.id.button1);
currentID = 0;
currentBalance = 0;
myButton.setOnClickListener(new View.OnClickListener() { // Tag write
// function
// places
// here
public void onClick(View arg0) {
Intent i = new Intent(getApplicationContext(), nfcWrite.class);
i.putExtra("id",currentID);
i.putExtra("balance",currentBalance);
startActivity(i);
}
});
}
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
Intent intent = getIntent();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefRecord myRecord = ((NdefMessage) rawMsgs[0]).getRecords()[0];
String nfcData = new String(myRecord.getPayload());
String[] splitData = nfcData.split("-");
currentID = Integer.parseInt(splitData[0]);
currentBalance = Integer.parseInt(splitData[1]);
Toast.makeText(getApplicationContext(), nfcData, Toast.LENGTH_LONG).show();
}
}
}
Add android:launchMode="singleTask" to the <activity> element for MainActivity. Note that if the activity is already running, onNewIntent() will be called, instead of onCreate(), to deliver the NDEF Intent to you. Hence, you will need to handle the NDEF Intent in both onCreate() (if the activity was not already running) and onNewIntent() (if the activity was already running).
This sample project illustrates the technique.
Instead of setting the launch-mode of the activity that receives the NFC intents to "singleTask" (see CommonsWare's answer), the preferred way for NFC applications to receive NFC-related intents while the app is in foreground is the foreground dispatch system (or alternatively the reader mode API when using only Android 4.4).
In order to use the foreground dispatch, you would create a pending intent like this:
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
0);
Then, in your activity's onResume() method, you would enable the foreground dispatch like this:
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
You will then receive intents notifying you about discovered tags through the activity's onNewIntent() method:
public void onNewIntent(Intent intent) {
...
}
Moreover, you have to disable the foreground dispatch in your activity's onPause() method:
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.disableForegroundDispatch(this);
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 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