I develop an app with NFC Tags interaction. Most of time application must ignore tag discovery events. And other apps should not catch tag discoery events when my app is foreground. So I used EnableForegroundDispatch to handle that.
This works fine. I handle excess TAG_DISCOVERED intents into my activity's onNewIntent methods.
The problem: When my activity is launched from another app, enableForegroundDispatch is not working. I receive TAG_DISCOVERED intents in new activity's onCreate method.
Here is my foregroundDispatch
if (nfcAdapter == null) {
Log.w(TAG, "enableForegroundNfcDispatch: no nfcAdapter", new Exception());
return;
}
Log.d(TAG, "enableForegroundNfcDispatch: ");
if (!nfcMonopolyMode) {
if (nfcTagDiscoveryPendingIntent != null) {
nfcAdapter.enableForegroundDispatch(this, nfcTagDiscoveryPendingIntent, nfcTagDiscoveryFilter, null);
return;
}
IntentFilter discovery = new IntentFilter(ACTION_TAG_DISCOVERED);
nfcTagDiscoveryFilter = new IntentFilter[]{discovery};
Intent nfcProcessIntent = new Intent(getBaseContext(), getClass())
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
nfcTagDiscoveryPendingIntent = PendingIntent.getActivity(getApplicationContext(), REQUEST_CODE_NFC_DISCOVERED_TAG, nfcProcessIntent, 0);
nfcMonopolyMode = true;
nfcAdapter.enableForegroundDispatch(this, nfcTagDiscoveryPendingIntent, nfcTagDiscoveryFilter, null);
}
May be there is something to do with flags? Please help
My experience is that if you enableReaderMode with all the flags (including skip NDEF check) then basically detection of any card type is sent to your App if running in the foreground.
As well as enableReaderModein onResume I have Broadcaster Receiver to enableReaderMode of NFC service State change.
Checking a number of Different Tags this seems to work BUT they are all NfcA based of varying formats. As confirmed by stackoverflow.com/questions/33633736/… but it seems Android 10 might have a timing issues with Kiosk Mode
Related
I have a react-native app which reads NFC tags and writes a URL to them.
It uses ForegroundDispatch to make sure the app captures the scanned info and doesn't navigate away to the browser when the link is detected. The link is used only when the app is not launched, it opens a browser and shows information from my server to the external user.
When I'm in the app and I turn the screen off (power button), then turn it back on again and scan an NFC tag, the browser opens up. It looks like the scanned intent doesn't deliver to the app and is instead handled by the phone as default. After the first scan, which opens the browser, I navigate back to the app and the following scans work fine inside the app.
Here is the twist: on most phones it works as expected. Works on Pixel 3 (Android 9), on Huawei (Android 5.1) on Sony (Android 7) an a few others. But when I use UleFone X2 (http://ulefone.com/product.html) this issue occurs.
Am I doing something incorrectly to initialize the app so it handles itself the same every time, even when it's resumed?
From my research it looks like UleFone disables NFC when the screen goes off and when it turns back on, am I not initializing it properly? If I open the recent apps and navigate away from my app, then back into it, the NFC starts working.
This is my main activity:
public class MainActivity extends ReactActivity {
private NfcAdapter mAdapter;
private PendingIntent mPendingIntent;
private IntentFilter[] mFilters;
private String[][] mTechLists;
private int mCount = 0;
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
}
#Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
mAdapter = NfcAdapter.getDefaultAdapter(this);
mPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("*/*");
} catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
};
mFilters = new IntentFilter[] {
ndef,
};
mTechLists = new String[][] { new String[] {NfcA.class.getName()}, new String[] {MifareClassic.class.getName()}, new String[] {Ndef.class.getName()}, };
Log.d("scantest", "onCreate completed");
}
#Override
protected void onPause() {
super.onPause();
if (mAdapter != null) mAdapter.disableForegroundDispatch(this);
Log.d("scantest","onPause completed");
}
#Override
public void onResume() {
super.onResume();
Intent intent = getIntent();
if (mAdapter != null) mAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters, mTechLists);
Log.d("scantest","onResume completed");
}
Expected behaviour:
All Android phones that support NFC handle NFC scanning in the app the same way, on launch and after it is resumed. The intent should always deliver to my app.
Current behaviour:
Most phones work fine after screen goes off/on again. On some phones (UleFone) if I turn the screen off/on, the first scan opens the browser, and the following ones work inside the app.
Current workaround:
After turning screen off/on, if I navigate away from the app using recent apps (I don't even have to click on another app, just have to make sure the recent apps tiles come up) then click to go back to my app, NFC doesn't fail on the first scan.
Otherwise the first scan fails and opens the browser.
A workaround could be to add this intent-filter to your activity
https://developer.android.com/guide/topics/connectivity/nfc/nfc#tag-disc
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>
With this you declare to android your app is want to catch any NFC tag. If device has another app which catch nfc tag, then a menu is shown to user to select which app get NFC intent.
I would like to start an application using an NFC tag. I got that part working using an android application record (AAR) as described in Start Android application from NFC-tag with extra data or by using NDEF_DISCOVERED / TECH_DISCOVERED intent filters. But how do I pass data from the NFC tag (e.g. some text) to my activity upon starting it through the NFC event?
I've read through NFC Basics, but as far as I understand that it seems to want to implement a mechanism for reading the tag, when I really do not want to re-read the tag once the app is opened by the tag, but instead I just want the data passed in at the same time.
Moreover, these mechanisms seem to allow the app to read the tag after it has been started by the tag. In other words, I am worried that if someone hits the tag later when the app is already opened that tag will be read again (which is what I do not want).
Second, how do I create such NDEF message?
Android will automatically read the NDEF message of an NFC tag and process it in order to
start registered activities based on the first NDEF record, and
start apps based on Android Application Records (AAR) anywhere in the NDEF message.
In order to get your activity started and have Android pass the pre-read NDEF message, you could use the NDEF_DISCOVERED intent filter:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="vnd.android.nfc"
android:host="ext"
android:pathPrefix="/example.com:mycustomtype"/>
</intent-filter>
Then from within your activity, you could process that NDEF message:
public void onResume() {
super.onResume();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
NdefMessage[] msgs = null;
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsgs != null) {
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; ++i) {
msgs[i] = (NdefMessage)rawMsgs[i];
}
}
if ((msgs != null) && (msgs.length > 0)) {
NdefRecord[] records = msgs[0].getRecords();
NdefRecord firstRecord = records[0];
byte[] payloadData = firstRecord.getPayload();
// do something with the payload (data passed through your NDEF record)
// or process remaining NDEF message
}
}
}
Note that onResume() is run whenever your activity becomes the foreground activity. Hence, it might be run multiple times for the same tag. THerefore, you could either use another life-cycle method or take some precautions that you do not parse the message multiple times.
If you want to drop all further NFC events, once your activity is open, you could follow the approach that I described in response to Android app enable NFC only for one Activity. Hence, you would register for the foreground dispatch (which gives your activity priority in receiving NFC events, and you can then simply drop those events.
public void onResume() {
super.onResume();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
public void onPause() {
super.onPause();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.disableForegroundDispatch(this);
}
public void onNewIntent(Intent intent) {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
// drop NFC events
}
}
Finally, to create the NDEF message for your NFC tag, you would do something like this:
byte[] payload = ... // generate your data payload
NdefMessage msg = new NdefMessage(
NdefRecord.createExternal("example.com", "mycustomtype", payload)
)
If you want to make sure that only your app is started by this tag (or if not installed Play Store is opened for your app), you could also add an AAR:
NdefMessage msg = new NdefMessage(
NdefRecord.createExternal("example.com", "mycustomtype", payload),
NdefRecord.createApplicationRecord("com.example.your.app.package")
)
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 am trying to use NFC to send a URL from an Android app to a WP8 phone.
When beaming to an Android device, the URL is sent correctly. However, when beaming to WP8, IE loads a link to the Play Store instead of the one I want to send (e.g. "http://www.stackoverflow.com").
The Play Store link is: "https://play.google.com/store/apps/details?id=com.example.conductrnfc&feature=beam". Where "com.example.conductrnfc" is the package name in the project.
The code I used to generate the NFC message is given below. Is there something I'm doing wrong here that breaks compatibility with WP8?
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
#Override
public NdefMessage createNdefMessage(NfcEvent event)
{
NdefRecord uriRecord = NdefRecord.createUri(urlString);
return new NdefMessage(new NdefRecord[] { uriRecord });
}
},
this);
Can you try this:
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
#Override
public NdefMessage createNdefMessage(NfcEvent event)
{
byte[] payload = urlString.getBytes();
NdefRecord uriRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, new byte[0], payload);
return new NdefMessage(new NdefRecord[] { uriRecord });
}
},
this);
Eventhough I still miss some more debugging results from the OP, I thought I'd give it a shot:
As the discussion in the commands revealed that the createNdefMessage callback is not called when interacting with a WP8 phone, it would be interesting why this appens and how to prevent this. Unfortunately I have no details about the actual lifecycle of the activity, so I can only guess what might go wrong.
One reason why a registered createNdefMessage callback may not be called is that the activity that registered the callback is no longer in the foreground. So there may be a difference between an Android device and a WP8 device that causes the current activity to be paused.
Another reason would be that the WP8 device interupts communication before the Android NFC stack had the time to call the createNdefMessage callback method. However, this should be detectable as the Beam UI would typically disappear before the user is able to click it.
One cause for reason 1 may be that the WP8 device itself sends an NDEF message that causes intent processing on the Android device. If that's the case, a method to overcome this problem may be to register for the foreground dispatch system. This would prevent regular intent processing and would directly send all incoming NDEF messages to the current activity:
#Override
public void onResume() {
super.onResume();
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
adapter.enableForegroundDispatch(this, pi, null, null);
}
#Override
public void onNewIntent(Intent intent) {
if (intent != null) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) ||
NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action) ||
NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
Log.d("NdefTest", "This problem was actually caused by an incoming NDEF message.");
}
}
}
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);
}
}