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 whenevercreateNdefMessage()is invoked, there is no need to save and restoremessagesToSendArray.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()andhandleNfcIntent()).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)
whereStatusequals to the lengthnof theLanguage Codeif theTextis UTF-8 encoded andLanguage Codeis an IANA language code (e.g. "en" for English). Consequently, the proper way to encode that record would be: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):
Finally, in
createRecords()you create the arrayrecordsasConsequently, the array has one element accessible at index 0. Hoever, you try to access element 1 later on:
This results in an
IndexOutOfBoundsexception. SincecreateRecords()is called by Android through thecreateNdefMessage()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 inrecordswhere you store your newly created NDEF record to 0:Moreover, you need to remove the line
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.