Reputation: 1674
I am developing an app with NFC. I was working with an example in which sending and receiving data was carried by the same Activity. Now I need to move sending to the other one and for some reason receing doesn't work anymore.
Here is my mainifest:
<uses-permission android:name="android.permission.NFC" />
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<activity
android:name=".Screens.LogIn.LogInActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
And the receiving activity:
public class LogInActivity extends AppCompatActivity {
public static final String TAG = LogInActivity.class.getSimpleName();
private String messagesToReceive= null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_in);
replaceFragment();
if (getIntent().getAction().equals(NfcAdapter.ACTION_NDEF_DISCOVERED)) {
handleNfcIntent(getIntent());
}
}
private void replaceFragment() {
android.support.v4.app.FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
LogInFragment fragment = new LogInFragment();
ft.replace(R.id.fragmentFrame, fragment, LogInFragment.TAG);
ft.commit();
}
@Override
public void onResume() {
super.onResume();
handleNfcIntent(getIntent());
}
@Override
public void onNewIntent(Intent intent) {
handleNfcIntent(intent);
}
private void handleNfcIntent(Intent NfcIntent) {
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(NfcIntent.getAction())) {
Parcelable[] receivedArray =
NfcIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if(receivedArray != null) {
NdefMessage receivedMessage = (NdefMessage) receivedArray[0];
NdefRecord[] attachedRecords = receivedMessage.getRecords();
for (NdefRecord record:attachedRecords) {
String string = new String(record.getPayload());
if (string.equals(getPackageName())) { continue; }
messagesToReceive = string;
}
Toast.makeText(this, "Received " +
" Messages", Toast.LENGTH_LONG).show();
}
else {
Toast.makeText(this, "Received Blank Parcel", Toast.LENGTH_LONG).show();
}
}
}
The problem is that NfcIntent.getAction()
always equals android.intent.action.MAIN
even if the app opens when I send data with NFC. As you can see, there is action NDEF_DISCOVERED
in the manifest.
This is what I send:
public NdefRecord[] createRecords() {
NdefRecord[] records = new NdefRecord[2];
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
byte[] payload = messagesToSend.
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[0] = record;
}
else {
byte[] payload = messagesToSend.//messagesToSendArray.get(i).
getBytes(Charset.forName("UTF-8"));
NdefRecord record = NdefRecord.createMime("text/plain",payload);
records[0] = record;
}
records[1] = NdefRecord.createApplicationRecord(getActivity().getPackageName());
return records;
}
@Override
public NdefMessage createNdefMessage(NfcEvent event) {
if (messagesToSend != null && !messagesToSend.equals("")) {
return null;
}
NdefRecord[] recordsToAttach = createRecords();
return new NdefMessage(recordsToAttach);
}
Upvotes: 2
Views: 1081
Reputation: 40869
Receiving the intent action MAIN
instead of NDEF_DISCOVERED
for an NFC event is usually a clear indication that the sending side sent an NDEF message containing an Android Application Record and that you did not filter for the first record of that NDEF message.
Since your receiving side has an NDEF_DISCOVERED
intent filter, the problem is most likely located at the sending side. Looking at your code, this could have several reasons:
The sending side creates NDEF messages in createNdefMessage()
. There, you test if messagesToSend
is null or empty. If its neither null nor empty, you return null
from the NDEF message creation callback:
if (messagesToSend != null && !messagesToSend.equals("")) {
return null;
}
Since you did not reveal if messagesToSend
is null or empty, nobody can tell if you atually come to the point where you create your own NDEF message. However, its likely that messagesToSend
is null/empty since returning null
from createNdefMessage()
would disable Beam completely.
You did not reveal the remaining code of the sending side. Hence, it's impossible to tell if you actually registered the createNdefMessage()
callback. You should have a like like
nfcAdapter.setNdefPushMessageCallback(this, this);
in onCreate()
. If that's not the case, Android will use a default NDEF message. The default NDEF message typically consists of a URI record and an AAR for your app. This would explain why the receiving side sees intent action MAIN, since your activity is not registered for receiving that specific URI record.
On Android versions below Jelly Bean, the condition
(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
evaluates to true
and you, consequently, hit that branch of the if-statement. In this branch you create an NFC Forum Text record with an invalid structure. This may cause the receiving device to fail to decode the NDEF record as a valid Text record. As a result, the receiving device might not match this record to the NDEF_DISCOVERED
intent filter for type text/plain
.
In order to fix this, you need to create a valid Text record. The Text RTD requires 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);
}
When the method createRecords()
is called on line
NdefRecord[] recordsToAttach = createRecords();
messagesToSend
is either null or empty (since you would have returned null
otherwise). If messagesToSend
is null
specifically and you hit the line
byte[] payload = messagesToSend.getBytes(Charset.forName("UTF-8"));
in createRecords()
, then this will cause a NullPointerException
(since you try to access a method of a null instance). As you never catch this exception, the callback createNdefMessage()
will fail with that same exception. In that case, Android will handle this exception and will automatically use the default NDEF message (consisting of a URI record and an AAR for your app). Since your receiving side does not filter for that URI record (the first record of the default NDEF message), your receiver will be statred solely by the AAR and you will receive intent action MAIN, since Android assumes that your activity is ready to receive that specific NDEF message.
Upvotes: 2