This is how usually an NFC tag is detected in my app:
protected void onNewIntent(Intent intent) {
if (intent.getAction().equals(NfcAdapter.ACTION_TAG_DISCOVERED)) {
Tag nfcTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
...
}
}
Now I need to also listen if an NFC tag is held close to the reader for a long time (about 3 seconds). In that case I want to do something else (similar to distinguishing between a normal press and a longpress on a view). Is this possible?
The method
isConnected()
tells you whether the connection to the tag ist still alive. If you check the connection periodically, you can detect a long connection.
The concept behind NFC is to quickly exchange small amounts of data between a tag and an NFC device (or two NFC devices) and not to detect the duration of an interaction. Consequently, there is no dedicated event that lets you distinguish between short and slightly longer interactions.
As corvairjo wrote, you could connect to the tag and check if the tag is still connected after a certain amount of time (e.g. 3 seconds). However, you can only measure the time from the point where your app is notified about the tag (i.e. after onNewIntent() was invoked). You can't measure the time that Android needed to notify your app after the user actually scanned the tag.
Note that isConnected() on its own is not reliable for all device/tag combinations. The most reliable way to test if a tag is still present would be to send a valid command to the tag and check if you get a response:
new AsyncTask<Tag, Void, Boolean>() {
protected Boolean doInBackground(Tag... tags) {
try {
Thread.sleep(3000);
// test if tag is still connected
Ndef ndef = Ndef.get(tags[0]);
if (ndef != null) {
try {
ndef.connect();
ndef.getNdefMessage();
} finally {
ndef.close();
}
return Boolean.TRUE;
}
} catch (Exception e) {
}
return Boolean.FALSE;
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
// "long press" event
}
}
}.execute(tag);
If your tag supports NDEF (the Ndef tag technology) you could simply query the tag for its NDEF message using Ndef.getNdefMessage() (see above). If your tag does not support NDEF, you would first need to find out what commands your tag supports and then send such a command using the proper tag technology.
E.g. if your tag is an MIFARE Ultralight or NTAG tag (or any NFC Forum Type 2 tag), you could use:
// test if tag is still connected
NfcA nfca = NfcA.get(tags[0]);
if (nfca != null) {
try {
nfca.connect();
byte[] response = nfca.transceive(new byte[] { (byte)0x30, (byte)0x00 });
if ((response != null) && (response.length > 0))
return Boolean.TRUE;
}
} finally {
ndef.close();
}
}
Related
I have an application where I am supposed to scan NFC tags and Call different functions depending on the tag result, but every time I scan an NFC on my phone the default Android reader pops up and the app doesnt recognize it, is there a way I can override the default reader and let the application scan the NFC instead?
ValueNotifier<dynamic> result = ValueNotifier(null);
void _tagRead() {
NfcManager.instance.startSession(
onDiscovered: (NfcTag tag) async {
result.value = tag.data;
if(result.value.toString().toLowerCase()=="main " || result.value.toString().toLowerCase()=="it" || result.value.toString().toLowerCase()=="eng"){
addRecord(result.value.toString());
}
else{
registerAttendance(result.value.toString());
}
NfcManager.instance.stopSession();
});
}
I'm working on an NFC based app developed in Xamarin for Android.
The app has a 'Connect' button and the NFC scanning process starts only when that button
is tapped.
With proper intents configured, whenever an NFC tag is detected, HandleNewIntent() method gets called and NFC read procedure is followed.
internal void HandleNewIntent(Intent intent)
{
try
{
if (intent.Action == NfcAdapter.ActionTagDiscovered || intent.Action == NfcAdapter.ActionNdefDiscovered)
{
_currentTag = intent.GetParcelableExtra(NfcAdapter.ExtraTag) as Tag;
if (_currentTag != null)
{
Task.Run(() => { ReadDataFromTag(_currentTag); });
}
}
}
catch (Exception ex)
{
//Display error
}
}
In normal cases this works fine. However, if the phone is kept in contact with the NFC tag and then the 'Connect' button is tapped on, then the TagDiscovered intent never gets fired. User has to take the phone away, bring it back in contact with the NFC tag and then only the event gets fired.
I observed the same behaviour with generic NFC apps on play store, and on 2 different Android phones.
Looks like Android keeps the NFC of phone tied up when in contact with NFC tag because of which the intents are not detected. Is there anything to be done to release such links (if any) before initiating new NFC connection?
All NFC is handled by the System NFC service and System App and is normally triggered by a Tag coming in to range and then an Intent being delivered to the right app immediately, therefore the system NFC service has already delivered the Intent to another App before you App has had the button pressed.
You don't show all the code that you do to setup your Apps' NFC configuration but as you are talking about Intents you are probably using the old and unreliable enableForegroundDispatch API which has a number of issues.
While it is more normal to enable foreground NFC detection as soon as you App is resumed and then process the Tag on immediate detection, it is possible to store the Tag object until a button is pressed.
I suggest that you use the better and newer enableReaderMode API that has less of a problem because it is a more direct interface to the NFC hardware and thus you use case seems to work using the code below.
public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback{
private NfcAdapter mNfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void Button(View view) {
Log.v("TAG", "DetailedScoresActivity:onTagDiscovered:Start");
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if(mNfcAdapter!= null) {
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);
mNfcAdapter.enableReaderMode(this,
this,
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 |
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK |
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
options);
}
}
public void onTagDiscovered(Tag tag) {
Log.v("TAG", "onTagDiscovered:Start");
}
#Override
protected void onPause() {
super.onPause();
if(mNfcAdapter!= null)
mNfcAdapter.disableReaderMode(this);
}
}
For me you can place a Tag against the phone and then start the App and as soon as the Button is clicked and `enableReaderMode` is enabled then `onTagDiscovered` is triggered with the Tag object.
I'm working on a large app that wants to add NFC communication. This app has many manifests, one for the main shell app and one each for the many separate modules. Initially I registered the NFC service on the main manifest and it works fine. The issue is that the NFC service now triggers anytime the app is open and not when the user is on a specific screen.
So I wanted to ask, is there a way to have the NFC service register/un-register as a user navigates to/away from a specific screen? Or just a way to make it so that NFC communication is restricted to a specific screen? The size of this app is really tripping me up, I appreciate any help people can offer.
So it is not usual just to use manifest Intent filters for NFC operations though possible, usually one of the foreground API's is used instead.
The following solution works for API 19 upwards because it uses the enableReaderMode NFC API's because this is better and enables more control and is more reliable.
The basic concept is that in all Activities you claim to handle all NFC Tag types silently when the Activity is in the foreground, then for the Activity where you want the NFC reading to happen you actually do something when you are notified a Tag has been presented.
In your manifest you only have the following:
<uses-permission android:name="android.permission.NFC" />
and optionally
<uses-permission android:name="android.permission.VIBRATE" />
for user feedback.
You don't have any of the NFC Intent filters.
The in every Activity you have the following boiler plate code:-
public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback{
private NfcAdapter mNfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
// Rest of onCreate
}
#Override
protected void onResume() {
super.onResume();
if(mNfcAdapter!= null) {
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);
mNfcAdapter.enableReaderMode(this, this,
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 |
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK |
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
options);
// enabling FLAG_READER_SKIP_NDEF_CHECK is optional
// depending on if you are reading NDef data or not.
}
}
#Override
protected void onPause() {
super.onPause();
if(mNfcAdapter!= null)
mNfcAdapter.disableReaderMode(this);
}
public void onTagDiscovered(Tag tag){
// Do nothing when a NFC tag is detected
}
}
In Activities where you want to handle the NFC Tag change onTagDiscovered method to do something with the Tag data.
e.g. for read Ndef data from a Tag onTagDiscovered could look like:-
// This method is run in another thread when a card is discovered
// !!!! This method cannot cannot direct interact with the UI Thread
// Use `runOnUiThread` method to change the UI from this method
public void onTagDiscovered(Tag tag) {
// Read and or write to Tag here to the appropriate Tag Technology type class
// in this example the card should be an Ndef Technology Type
Ndef mNdef = Ndef.get(tag);
// Check that it is an Ndef capable card
if (mNdef!= null) {
// If we want to read
// As we did not turn on the NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
// We can get the cached Ndef message the system read for us.
NdefMessage mNdefMessage = mNdef.getCachedNdefMessage();
// Now your own code to process the Ndef message
// Finally feedback to the user that the NFC read was a success
// Make a Sound
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(),
notification);
r.play();
} catch (Exception e) {
// Some error playing sound
}
// Optionally Vibrate as well
Vibrator v = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
if (v != null) {
v.vibrate(500);
}
}
}
You can optionally you can also decide on when the NFC is handled when a particular Fragment is shown in your Activity or when a dialog is shown or any other state in your Activity by putting conditional clauses in your onTagDiscovered method.
e.g. Some pseudo code
public void onTagDiscovered(Tag tag) {
if correct fragment is being shown {
Handle NFC reading and feedback to user the NFC has been read
} else {
Do nothing
}
}
or
public void onTagDiscovered(Tag tag) {
if some condition is true {
Handle NFC reading and feedback to user the NFC has been read
} else {
Do nothing
}
}
Normally with Intent filter and enableForegroundDisplatch method of working with NFC the System service Handles the NFC event and notifies the user that an NFC card has been presented and then it interrupts you App by restarting the current Activity or starting another Activity from you App.
With enableReadmode and this method the NFC System Service is silent, because the Event is handled in another thread in your App, what the Activity currently is doing is not interrupted and you get full control about when you handle the NFC event or to silently ignore it.
The problem with any register/de-register approach is that the System NFC Service would always make a sound and possibly launch another App or display a default NFC content screen when you were de-registered.
Is it possible to force android to check if an NFC tag is near? I'm only able to read the tag when android detects it, I would like to force it to check if a tag is near at a specific moment
What you want to do is in general not possible. However if you can live with a dirty hack the following will work (thanks to unspecified behaviour):
First disable the reader-mode of all supported tag types. This brings the NFC subsystem into a clean state, e.g. it makes sure that the NFC controller will have no connection to the tag.
Once done restore the reader-mode again. If a tag is present at that moment you will get the usual discovery action as an intent. It may take a second or two though.
Control of the reader-mode is possible using NfcAdapter.enableReaderMode and NfcAdapter.disableReaderMode
I figured out a bit of a hack for this that works (for me, at least!)
First, when you initially detect the tag via the android.nfc.action.NDEF_DISCOVERED, get the tag as a field in your class and start a timer (this is in C#/Xamarin but the same should apply for Java):
_tag = Intent.GetParcelableExtra(NfcAdapter.ExtraTag) as Tag;
ReaderTimer = new Timer(2000);
ReaderTimer.Elapsed += TimerElapsed;
ReaderTimer.Start();
Now, every 2 seconds this will fire. It will attempt to reconnect to the tag. If it can't the tag is gone.:
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
if (_tag == null)
{
return;
}
Ndef ndef = Ndef.Get(_tag);
if (ndef == null)
{
// NDEF is not supported by this Tag.
return;
}
if (!ndef.IsConnected)
{
try
{
ndef.Close();
ndef.Connect();
}
catch (Exception ex)
{
// could not reconnect
// implies tag is not in proximity
//do whatever needs to be done when NFC is disconnected
ReaderTimer.Stop();
}
}
}
I've tested this with API 14.
Unfortunately when the screen is turned off ndef.Connect() fails, so that is registered as a disconnect.
Try this code and it works ! It will continuously check if the NFC tag is near to the phone or not.
#Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
readFromIntent(intent);
if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())){
myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
checkNFCStatus();
handler.postDelayed(this,1000);
}
}, 1000);
}
}
public void checkNFCStatus(){
try {
if(myTag != null) {
Ndef ndefTag = Ndef.get(myTag);
ndefTag.connect();
if (ndefTag.isConnected()) {
Log.d("network", "NFC connected");
} else {
Log.d("network", "NFC disconnected");
}
ndefTag.close();
}
} catch (IOException e) {
e.printStackTrace();
Log.d("network", "NFC disconnected");
}
}
//If the connection is not closed, an exception will be thrown
Is there a way to check at run time whether a device has an NFC reader? My app uses NFC to perform a task, but if no reader is present, it can perform the same task by using a button.
Hope This works for you
NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
NfcAdapter adapter = manager.getDefaultAdapter();
if (adapter != null && adapter.isEnabled()) {
//Yes NFC available
}else if(adapter != null && !adapter.isEnabled()){
//NFC is not enabled.Need to enable by the user.
}else{
//NFC is not supported
}
The simplest way to check if an Android device has NFC functionality is to check for the system feature PackageManager.FEATURE_NFC ("android.hardware.nfc"):
PackageManager pm = context.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_NFC)) {
// device has NFC functionality
}
However, there exist devices (at least one of Sony's first Android NFC smartphones has this issue) that do not properly report the FEATURE_NFC. (That's those devices that do not allow you to install apps that require NFC functionality through Play Store does such a check for apps that require NFC.)
Therefore, the more reliable solution is the one described by Sainath Patwary karnate. To check if a device has NFC functionality (or rather if a device has a running NFC service), you can use:
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
if (nfcAdapter != null) {
// device has NFC functionality
}
If you also want to check if the user enabled NFC on their device, you may use the NfcAdapter's isEnabled() method. But be warned that it's not always as easy as described by Sainath Patwary karnate. Particularly on Android 4.0.*, the isEnabled() method sometimes throws undocumented exceptions when the NFC service had crashed before, so you might want to catch those exceptions. Moreover, on Android >= 2.3.4 and < 4.1 (I could not reproduce the problem on later versions but that does not mean it is not there!), the first call to isEnabled() after the NFC service had been stopped or crashed always returned false, so it is advisable to always ignore the result of the first call of isEnabled().
if (nfcAdapter != null) {
try {
nfcAdapter.isEnabled();
} catch (Exception e) {}
bool isEnabled = false;
try {
isEnabled = nfcAdapter.isEnabled();
} catch (Exception e) {}
if (isEnabled) {
// NFC functionality is available and enabled
}
}
Here's my function that I use for detecting NFC presence.
public static boolean deviceHasNfc() {
// Is NFC adapter present (whether enabled or not)
NfcManager nfcMgr = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
if (manager != null) {
NfcAdapter adapter = manager.getDefaultAdapter();
return adapter != null;
}
return false;
}
As stated in #Sainath's answer you can also detect if the NFC is enabled using adapter.isEnabled()
For those of you doing Kotlin here is a quick enabled check extension following the rules posted above
fun Context.isNfcEnabled(): Boolean {
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
if (nfcAdapter != null) {
return try {
nfcAdapter.isEnabled
} catch (exp: Exception) {
// Double try this as there are times it will fail first time
try {
nfcAdapter.isEnabled
} catch (exp: Exception) {
false
}
}
}
return false
}