Hardy Hardy - 1 month ago 7
Android Question

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

BeamFileActivity
is 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

Answer

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. But you don't need to include your domain object name, instead use the words externalType with that exact spacing and capitalization.

<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:externalType"/>
</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 your intent filter would be "com.myapp:externalType". Yes, it should say externalType exactly like that. 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:externalType".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 exteranl 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).

EDIT - I made some changes above based on what ended up partially working for me. I never got it to open my app but with the above changes it did send a message correctly. Perhaps if I had dropped the application record everything would have worked.

EDIT - This is what ended up working for me I ended up using the mimetype NFC message type because the external type never opened my app, it always went to the app store, so this is what I added to the manifest:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="application/com.myapp"/>
</intent-filter>

I added this to my BeamFileActivity:

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

I got rid of the Application Record

Comments