androidnfcndefnfc-p2pandroid-beam

Why isn't Android Beam/NFC receiving the records that I send?


I've read the documentation a few times now and looked at the example code but I can't seem to figure out what I'm doing wrong.

Whenever I put my two devices together I see the Beam screen and tap it, but nothing seems to be sent and none of my breakpoints get hit. Here's my code, please help me figure out why nothing is sent to the other device. In the editor I can see that everything in BeamFileActivityis run, I just never see any of the break points in MainActivity get accessed on the receiving device. What am I doing wrong?

AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp">
    <uses-permission android:name="android.permission.NFC" />
    <uses-feature android:name="android.hardware.nfc" android:required="true" />
    <application
    android:name=".MainActivity"
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme">
        <meta-data
            android:name="QUERY_LOG"
            android:value="false" />
        <meta-data
            android:name="DOMAIN_PACKAGE_NAME"
            android:value="com.myapp.domain" />

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:windowSoftInputMode="adjustResize">

            <intent-filter>
                <action android:name="com.google.android.apps.drive.DRIVE_OPEN" />
                <action android:name="com.google.android.apps.drive.DRIVE_SAVE" />
                <data android:mimeType="application/vnd.google-apps.drive-sdk.111111111" />
                <data android:mimeType="application/vnd.google-apps.spreadsheet" />
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </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="/com.myapp:SpreadsheetDom"/>
            </intent-filter>
        </activity>
        <activity
            android:name=".dialog.BeamFileActivity"
            android:label="@string/title_activity_beam_file"
            android:parentActivityName=".MainActivity"
            android:theme="@style/AppTheme.NoActionBar">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.myapp.MainActivity" />
        </activity>
    </application>

MainActivity: This is what will receive the Beam intent, I'm only including the methods I think are relevant.

@Override
protected void onResume() {

    super.onResume();

    //I removed some code that reloads my application, didn't seem relevant

    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
        processIntent(getIntent());
    }
}

/**
 * Parses the NDEF Message from the intent and stores the collection
 */
void processIntent(Intent intent) {

    Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
    // only one message expected during the beam
    NdefMessage msg = (NdefMessage) rawMsgs[0];

    //This is the data I'm beaming, It has a method that converts its data to a byte array, and a method, fromBytes() that converts bytes into the object
    SpreadsheetDom dom = new SpreadsheetDom();
    try {

        for(int i = 0; i < msg.getRecords().length; i++) {

            if(SpreadsheetDom.class.getName().equalsIgnoreCase(new String(msg.getRecords()[i].getType()))) {

                dom.fromBytes(msg.getRecords()[i].getPayload());
                //removed some code that saves and displays changes
            }
        }
    } catch(IOException | ClassNotFoundException e) {

    }
}

@Override
public void onNewIntent(Intent intent) {

    setIntent(intent);
}

BeamFileActivity: This is a separate activity that just displays instructions for beaming the data and does the actual beam.

private void initFileToBeam(Long spreadsheetId) {

    try {

        List<SpreadsheetDom> spreadsheets = db.getSpreadsheetDao().queryForEq(SpreadsheetDom.SPREADSHEET_ID_NAME, spreadsheetId);
        if(spreadsheets != null && spreadsheets.size() > 0) {

            fileToBeam = spreadsheets.get(0);
        }
    } catch(SQLException e) {

        ErrorDialog ed = new ErrorDialog(this, "Unable to beam current collection.");
        ed.show();
    }
}

@Override
public NdefMessage createNdefMessage(NfcEvent event) {

    if(fileToBeam == null) {

        Long spreadsheetId = Settings.getInstance().get(SettingKey.CURRENT_SPREADSHEET).getValue();
        initFileToBeam(spreadsheetId);
    }

    NdefMessage msg = null;
    try {

        msg = new NdefMessage(
                new NdefRecord[]{
                        new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, "application/com.myapp:SpreadsheetDom".getBytes(), new byte[0], fileToBeam.toBytes()),
                        //NdefRecord.createExternal("com.myapp", "spreadsheetdom", fileToBeam.toBytes()),
                        NdefRecord.createApplicationRecord("com.myapp")
                });
    } catch(IOException e) {

        String textMessage = "Unable to transfer collection: " + e.getMessage();
        msg = new NdefMessage(
                new NdefRecord[]{
                        NdefRecord.createMime("text/plain", textMessage.getBytes()),
                        NdefRecord.createApplicationRecord("com.myapp")
                });
    }

    return msg;
}

@Override
public void onResume() {

    super.onResume();

    Intent exportFileIntent = getIntent();
    Long spreadsheetId = exportFileIntent.getLongExtra(Constants.EXTRA_SPREADSHEET_ID, Database.INVALID_ID);

    initFileToBeam(spreadsheetId);

    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);
}

Really all of this is from the examples and documentation here, here and here


Solution

  • You are sending a different NDEF record than what your MainActivity expects. Your MainActivity is registered for the NFC Forum external type "com.myapp:SpreadsheetDom":

    <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="/com.myapp:SpreadsheetDom"/>
    </intent-filter>
    

    Since intent filters in Android are case-sensitive but NFC Forum external type names are case-insensitive, Android automatically converts the names of NFC Forum external types (just as with MIME types) to lower-case. As your intent filter contains the upper-case letters "S" and "D", it will never match the type name of your NFC Forum external type. Therefore, you have to specify the type name as all lower-case to achieve a match (see also here):

    <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="/com.myapp:spreadsheetdom"/>
    </intent-filter>
    

    Next, in your BeamFileActivity you create an external record with the invalid type name "application/com.myapp:SpreadsheetDom":

    msg = new NdefMessage(new NdefRecord[] {
            new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, "application/com.myapp:SpreadsheetDom".getBytes(), new byte[0], fileToBeam.toBytes()),
            NdefRecord.createApplicationRecord("com.myapp")
    });
    

    An NFC Forum external type name that matches the above intent filter would be "com.myapp:spreadsheetdom" (again, all lower-case letters). You can create such a record with (make sure to use US-ASCII encoding for the type name):

    msg = new NdefMessage(new NdefRecord[] {
            new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, "com.myapp:spreadsheetdom".getBytes("US-ASCII"), new byte[0], fileToBeam.toBytes()),
            NdefRecord.createApplicationRecord("com.myapp")
    });
    

    Finally, note that "com.myapp" is not a well-formed domain name for an external type according to the NFC Forum Record Type Definition. Instead, a well-formed domain name would be "myapp.com" (Internet domain name format instead of Java package name format).