I have an application that seems to work fine and can transmit data via NFC perfectly well. I have a main activity, an activity to transmit the data, and a different activity to receive data.
The sender activity works great, but when the receiver gets the NFC intent, it restarts the app back to the main activity.
I'm not exactly sure why this is. I would like it to decline any pushes unless the user is already in that activity, and if they are, to stay in that activity and handle the NFC intent.
Here is the manifest:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Timer" />
<activity android:name=".AddSlaves"
android:label="Add Slave Devices"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity android:name=".JoinSrv"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
Here is the sender class:
public class JoinSrv extends Activity implements NfcAdapter.OnNdefPushCompleteCallback, NfcAdapter.CreateNdefMessageCallback {
//The array lists to hold our messages
private ArrayList<String> messagesToSendArray = new ArrayList<>();
private ArrayList<String> messagesReceivedArray = new ArrayList<>();
//Text boxes to add and display our messages
private NfcAdapter mNfcAdapter;
//Save our Array Lists of Messages for if the user navigates away
#Override
public void onSaveInstanceState(#NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putStringArrayList("messagesToSend", messagesToSendArray);
savedInstanceState.putStringArrayList("lastMessagesReceived", messagesReceivedArray);
}
//Load our Array Lists of Messages for when the user navigates back
#Override
public void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
messagesToSendArray = savedInstanceState.getStringArrayList("messagesToSend");
messagesReceivedArray = savedInstanceState.getStringArrayList("lastMessagesReceived");
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_join_srv);
//Check if NFC is available on device
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter != null) {
//Handle some NFC initialization here
} else {
Toast.makeText(this, "NFC not available on this device",
Toast.LENGTH_SHORT).show();
}
//Check if NFC is available on device
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter != null) {
//This will refer back to createNdefMessage for what it will send
mNfcAdapter.setNdefPushMessageCallback(this, this);
//This will be called if the message is sent successfully
mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
}
}
#Override
public NdefMessage createNdefMessage(NfcEvent event) {
//This will be called when another NFC capable device is detected.
//We'll write the createRecords() method in just a moment
NdefRecord[] recordsToAttach = createRecords();
//When creating an NdefMessage we need to provide an NdefRecord[]
return new NdefMessage(recordsToAttach);
}
#Override
public void onNdefPushComplete(NfcEvent event) {
//This is called when the system detects that our NdefMessage was
//Successfully sent.
messagesToSendArray.clear();
}
public NdefRecord[] createRecords() {
NdefRecord[] records = new NdefRecord[1];
//To Create Messages Manually if API is less than
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
byte[] payload = "192.168.1.100".
getBytes(Charset.forName("UTF-8"));
NdefRecord record = new NdefRecord(
NdefRecord.TNF_WELL_KNOWN, //Our 3-bit Type name format
NdefRecord.RTD_TEXT, //Description of our payload
new byte[0], //The optional id for our Record
payload); //Our payload for the Record
records[1] = record;
}
//Api is high enough that we can use createMime, which is preferred.
else {
byte[] payload = "192.168.1.100".
getBytes(Charset.forName("UTF-8"));
NdefRecord record = NdefRecord.createMime("text/plain",payload);
records[1] = record;
}
records[messagesToSendArray.size()] =
NdefRecord.createApplicationRecord(getPackageName());
return records;
}
private void handleNfcIntent(Intent NfcIntent) {
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(NfcIntent.getAction())) {
Parcelable[] receivedArray =
NfcIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (receivedArray != null) {
messagesReceivedArray.clear();
NdefMessage receivedMessage = (NdefMessage) receivedArray[0];
NdefRecord[] attachedRecords = receivedMessage.getRecords();
for (NdefRecord record : attachedRecords) {
String string = new String(record.getPayload());
//Make sure we don't pass along our AAR (Android Application Record)
if (string.equals(getPackageName())) {
continue;
}
messagesReceivedArray.add(string);
}
Toast.makeText(this, "Received " + messagesReceivedArray.size() +
" Messages", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Received Blank Parcel", Toast.LENGTH_LONG).show();
}
}
}
#Override
public void onNewIntent(Intent intent) {
handleNfcIntent(intent);
}
#Override
public void onResume() {
super.onResume();
handleNfcIntent(getIntent());
}
}
Here is the receiver class:
public class AddSlaves extends Activity implements NfcAdapter.OnNdefPushCompleteCallback, NfcAdapter.CreateNdefMessageCallback{
//The array lists to hold our messages
private ArrayList<String> messagesToSendArray = new ArrayList<>();
private ArrayList<String> messagesReceivedArray = new ArrayList<>();
//Text boxes to add and display our messages
private EditText txtBoxAddMessage;
private TextView txtReceivedMessages;
private TextView txtMessagesToSend;
private NfcAdapter mNfcAdapter;
private void updateTextViews() {
txtReceivedMessages.setText("Messages Received:\n");
//Populate our list of messages we have received
if (messagesReceivedArray.size() > 0) {
for (int i = 0; i < messagesReceivedArray.size(); i++) {
txtReceivedMessages.append(messagesReceivedArray.get(i));
txtReceivedMessages.append("\n");
}
}
}
//Save our Array Lists of Messages for if the user navigates away
#Override
public void onSaveInstanceState(#NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putStringArrayList("lastMessagesReceived",messagesReceivedArray);
}
//Load our Array Lists of Messages for when the user navigates back
#Override
public void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
messagesReceivedArray = savedInstanceState.getStringArrayList("lastMessagesReceived");
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_slaves);
txtReceivedMessages = (TextView) findViewById(R.id.txtMessagesReceived);
Button btnAddMessage = (Button) findViewById(R.id.buttonAddMessage);
//Check if NFC is available on device
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if(mNfcAdapter != null) {
//Handle some NFC initialization here
}
else {
Toast.makeText(this, "NFC not available on this device",
Toast.LENGTH_SHORT).show();
}
//Check if NFC is available on device
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if(mNfcAdapter != null) {
//This will refer back to createNdefMessage for what it will send
mNfcAdapter.setNdefPushMessageCallback(this, this);
//This will be called if the message is sent successfully
mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
}
}
#Override
public NdefMessage createNdefMessage(NfcEvent event) {
//This will be called when another NFC capable device is detected.
return null;
}
#Override
public void onNdefPushComplete(NfcEvent event) {
//This is called when the system detects that our NdefMessage was
//Successfully sent.
messagesToSendArray.clear();
}
private void handleNfcIntent(Intent NfcIntent) {
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(NfcIntent.getAction())) {
Parcelable[] receivedArray =
NfcIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if(receivedArray != null) {
messagesReceivedArray.clear();
NdefMessage receivedMessage = (NdefMessage) receivedArray[0];
NdefRecord[] attachedRecords = receivedMessage.getRecords();
for (NdefRecord record:attachedRecords) {
String string = new String(record.getPayload());
//Make sure we don't pass along our AAR (Android Application Record)
if (string.equals(getPackageName())) { continue; }
messagesReceivedArray.add(string);
}
Toast.makeText(this, "Received " + messagesReceivedArray.size() +
" Messages", Toast.LENGTH_LONG).show();
updateTextViews();
}
else {
Toast.makeText(this, "Received Blank Parcel", Toast.LENGTH_LONG).show();
}
}
}
#Override
public void onNewIntent(Intent intent) {
handleNfcIntent(intent);
}
#Override
public void onResume() {
super.onResume();
updateTextViews();
handleNfcIntent(getIntent());
}
}
You have qute a few issues in your code of the sender activity:
You store messagesToSendArray, but you never actually fill this array list with data (i.e. messagesToSendArray.size() is always 0). Since you freshly create the NDEF message whenever createNdefMessage() is invoked, there is no need to save and restore messagesToSendArray.
You wrote that you want to send NDEF messages in one activity, but you want to receive NFC events in another activity. However, you registered your sender activity to receive NDEF_DISCOVERED events in the manifest. There is no need for the NDEF_DISCOVERED intent filter in the manifest if you do not want to receive and process these events.
Moreover, there is no need to handle the NDEF_DISCOVERED intent in your sender activity (i.e. you can safely remove the methods onNewIntent() and handleNfcIntent()).
On Android versions below Jelly Bean you create an NFC Forum Text record with an invalid structure. The Text RTD requres a payload that is encoded in the form (also see this post)
+----------+---------------+--------------------------------------+
| Status | Language Code | Text |
| (1 byte) | (n bytes) | (m bytes) |
+----------+---------------+--------------------------------------+
where Status equals to the length n of the Language Code if the Text is UTF-8 encoded and Language Code is an IANA language code (e.g. "en" for English). Consequently, the proper way to encode that record would be:
public static NdefRecord createTextRecord(String language, String text) {
byte[] languageBytes;
byte[] textBytes;
try {
languageBytes = language.getBytes("US-ASCII");
textBytes = text.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
byte[] recordPayload = new byte[1 + (languageBytes.length & 0x03F) + textBytes.length];
recordPayload[0] = (byte)(languageBytes.length & 0x03F);
System.arraycopy(languageBytes, 0, recordPayload, 1, languageBytes.length & 0x03F);
System.arraycopy(textBytes, 0, recordPayload, 1 + (languageBytes.length & 0x03F), textBytes.length);
return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, null, recordPayload);
}
It's unclear to me why you create an NFC Forum Text record on Android versions below Jelly Bean while you create a MIME type record on Jelly Bean and above. You should be consistent and create the same record type on all platforms (see Method NdefRecord.createTextRecord("en" , "string") not working below API level 21):
String text = "192.168.1.100";
String language = "en";
NdefRecord record;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
record = NdefRecord.createTextRecord(language, text);
} else {
record = createTextRecord(language, text);
}
Finally, in createRecords() you create the array records as
NdefRecord[] records = new NdefRecord[1];
Consequently, the array has one element accessible at index 0. Hoever, you try to access element 1 later on:
records[1] = record;
This results in an IndexOutOfBounds exception. Since createRecords() is called by Android through the createNdefMessage() callback, the callback fails (due to the runtime exception) and Android will not use your NDEF message. Instead, Android will use a default NDEF message for your app. This default NDEF message contains an Android Application record that will cause your main activity to be called (since none of your other activities are registered to be started for the specific contents of the default NDEF message); see NFC tag detection is not calling onNewIntent and it's Launching From Main Activity. Consequently, you need to change the offset in records where you store your newly created NDEF record to 0:
records[0] = record;
Moreover, you need to remove the line
records[messagesToSendArray.size()] =
NdefRecord.createApplicationRecord(getPackageName());
since this would then overwrite the previously stored NDEF record at index 0 (messagesToSendArray.size() is 0) with an Android Application record. Again, this would cause your main activity to be started since you did not register for that specific record type in your manifest.
Finally, if you want to decline pushes unless the user is inside the receiver activity, you should consider using the foreground dispatch system. In that case, you would complete remove all NDEF_DISCOVERED intent filters from your manifest and instead register each activity (receiver, sender, and main) with the foreground dispatch system. In the receiver, you would then receive the NDEF messages through onNewIntent(). In the sender and in the main activity, you would simply ignore and drop any received NDEF message. See Android app enable NFC only for one Activity for an example.
Related
I am designing an app that needs to transfer a simple string using NFC, I have tried with multiple devices and still not working, something seems to be going wrong even though this is code snippet was taken directly from the android developer website
1 here is the code I have tried with no luck:(
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfcdisplay);
TextView textView = (TextView) findViewById(R.id.nfctesttext);
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();
finish();
return;
}
// Register callback
mNfcAdapter.setNdefPushMessageCallback(this, this);
}
#Override
public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
String text = ("Beam me up, Android!\n\n" +
"Beam Time: " + System.currentTimeMillis());
NdefMessage msg = new NdefMessage(
new NdefRecord[] { createMime(
"application/vnd.com.example.android.beam", text.getBytes())
/**
* The Android Application Record (AAR) is commented out. When a device
* receives a push with an AAR in it, the application specified in the AAR
* is guaranteed to run. The AAR overrides the tag dispatch system.
* You can add it back in to guarantee that this
* activity starts when receiving a beamed message. For now, this code
* uses the tag dispatch system.
*/
//,NdefRecord.createApplicationRecord("com.example.android.beam")
});
return msg;
}
#Override
public void onResume() {
super.onResume();
// Check to see that the Activity started due to an Android Beam
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
processIntent(getIntent());
}
}
#Override
public void onNewIntent(Intent intent) {
// onResume gets called after this to handle the intent
setIntent(intent);
}
/**
* Parses the NDEF Message from the intent and prints to the TextView
*/
void processIntent(Intent intent) {
textView = (TextView) findViewById(R.id.textView);
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
NfcAdapter.EXTRA_NDEF_MESSAGES);
// only one message sent during the beam
NdefMessage msg = (NdefMessage) rawMsgs[0];
// record 0 contains the MIME type, record 1 is the AAR, if present
textView.setText(new String(msg.getRecords()[0].getPayload()));
}
}
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 am just completed the NFC based application. In this I am simply scanning the NFC tag and fetching the serial number of the NFC.
But When NFC tag comes near across the device, then it show all the application list which can scan the NFC tag where there I can set by doing "Always" or "default" but I want this do somehow programmatically.
I am looking for this because it seems to bug in some devices that not working as it seems.
Bug in the devices : I have two devices that is not showing even "Always" or "default as action" in the dialog box when NFC tag come near across the device.
See the screenshot :
<activity
android:name="net.livepatrols.thepartnerSA.NFCActivity"
android:theme="#android:style/Theme.NoDisplay" >
<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" />
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
NFCActivity.java
public class NFCActivity extends Activity {
private NfcAdapter mNFCAdapter;
private PendingIntent mNfcPendingIntent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc);
// Create the OpenSqliteHelper object. It always best to create only
// once instance of this OpenSqliteHelper
DatabaseManager.init(this);
mNFCAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNFCAdapter == null) {
// Device does not support NFC
Toast.makeText(this,
getString(R.string.device_does_not_support_nfc),
Toast.LENGTH_LONG).show();
} else {
if (!mNFCAdapter.isEnabled()) {
// NFC is disabled
Toast.makeText(this, getString(R.string.enable_nfc),
Toast.LENGTH_LONG).show();
} else {
mNfcPendingIntent = PendingIntent.getActivity(NFCActivity.this,
0, new Intent(NFCActivity.this, NFCActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
}
}
}
#Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
#Override
protected void onPause() {
super.onPause();
mNFCAdapter.disableForegroundDispatch(this);
}
#Override
public void onResume() {
super.onResume();
try {
mNFCAdapter.enableForegroundDispatch(this, mNfcPendingIntent, null,
null);
Tag mTag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte byteId[] = mTag.getId();
int size = byteId.length;
// Convert the byte array to integer
String str = "";
if (size > 0) {
for (int i = 0; i < size; i++) {
byte myByte = byteId[i];
int myInt = (int) (myByte & 0xFF);
str += myInt;
}
}
SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(
"yyyyMMddhhmmss");
SharedPreferences mSharedPreferences = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext());
boolean mRegistrationStatus = mSharedPreferences.getBoolean(
"registration_status", false);
boolean mExpiredStatus = mSharedPreferences.getBoolean(
"expire_status", true);
Editor mEditor = mSharedPreferences.edit();
if (mRegistrationStatus && !mExpiredStatus) {
// Tag here started to scan the the NFC tags
mEditor.putString("message", "Tag Read in");
mEditor.commit();
if (Util.isDeviceConnectedWithInternet(this)) {
// Upload the NFC details.
// Start the service and send the NFC Tag Info in the
// intent.
Intent serviceIntent = new Intent(this,
UploadNFCTagInfoService.class);
serviceIntent.putExtra("mNFCSerialNumber", str);
serviceIntent.putExtra("mNFCScannedTimeStamp",
mSimpleDateFormat.format(new Date()));
startService(serviceIntent);
} else {
// Device is not connected with the Internet.
// Store the NFC Tag details in the SQLite database.
// When device connect with Internet later then
// these records will be uploaded on the database server.
Toast.makeText(this, getString(R.string.network_message), Toast.LENGTH_LONG).show();
mEditor.putString("message", "Tag has been saved in the application database to upload it later if your device is activated");
mEditor.commit();
NFCIItem mNFCItem = new NFCIItem();
mNFCItem.setSerialNumber(str);
mNFCItem.setScanTimeStamp(mSimpleDateFormat
.format(new Date()));
// Insert the record in the database.
DatabaseManager.getInstance().addNFCTag(mNFCItem);
}
} else if(!mRegistrationStatus) {
mEditor.putString("message", "Device is not activated to scan NFC Tags");
mEditor.commit();
}
finish();
} catch (Exception e) {
finish();
}
}
}
The only thing I know how to solve this issue is to add an additional AAR record (special NDEF record) on your tag. Subsequently, only your app is starting and nobody is asking anymore.
You can't. If the app is open, you can use foreground dispatch to make your app the preferred one (which you seem to already be using). Best you can do is tune your NFC tech filter to match the tag as best as possible (tech+NDEF, etc.). As for devices that don't show the 'Always' option, what happens if double-tap the app in the dialog?
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'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