Receiving intent action MAIN instead of NDEF message

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

Answers (1)

Michael Roland
Michael Roland

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:

  1. 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.

  2. 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.

  3. 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);
    }
    
  4. 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

Related Questions