Search code examples
androidnfcnfc-p2pandroid-beam

Android NFC: one activity to send and one to receive only


I want my ActivityNFCSender to send an integer to my ActivityNFCReceiver via NFC. The below code works. But there is a problem: when connection is established, BOTH activities display the prompt to send! But I want only the first one to be able to send (an integer) and display the prompt. How to do it?

ActivityNFCSender:

public class ActivityNFCSender extends ActivityBase {

public static final String EXTRA_RES_ID = "res_id";

private NfcAdapter mNfcAdapter;
private int mReservServerId;


@Override
public void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_nfc);
    super.onCreate(savedInstanceState);

    mReservServerId = getIntent().getIntExtra(EXTRA_RES_ID, 0);
}


public void onResume(){
    super.onResume();   

    mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
    if(mNfcAdapter == null){
        messageDialogQuit(R.string.error_no_nfc);
    }
    else if(!mNfcAdapter.isEnabled()){
        new AlertDialog.Builder(this).setCancelable(true).setMessage(R.string.error_nfc_off)
            .setPositiveButton(R.string.turn_on, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                        Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS);
                        startActivity(settingsIntent);                          
                    }
                })
            .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();    
                        finish();
                    }
                })                  
            .create().show();
    }
    else{           
        //Set sending
        mNfcAdapter.enableForegroundNdefPush(ActivityNFCSender.this,  getIntAsNdef(mReservServerId));
    }
}

public void onPause(){      
    if(mNfcAdapter != null && mNfcAdapter.isEnabled()){
        mNfcAdapter.disableForegroundNdefPush(this);
    }
    super.onPause();        
}


private NdefMessage getIntAsNdef(int pInt) {
    byte[] textBytes = (pInt+"").getBytes(Charset.forName("UTF8"));
    NdefRecord textRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "text/plain".getBytes(), new byte[] {}, textBytes);
    return new NdefMessage(new NdefRecord[] {
        textRecord
    });
}

ActivityNFCReceiver:

public class ActivityNFCReceiver extends ActivityBase {

private NfcAdapter mNfcAdapter;
private PendingIntent mNfcPendingIntent;
private IntentFilter[] mNdefExchangeFilters;    


@Override
public void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_nfc);
    super.onCreate(savedInstanceState);

    ((TextView) findViewById(R.id.nfcTVStatus)).setText(R.string.receiving);
}


public void onResume(){
    super.onResume();   

    mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
    if(mNfcAdapter == null){
        messageDialogQuit(R.string.error_no_nfc);
    }
    else if(!mNfcAdapter.isEnabled()){
        new AlertDialog.Builder(this).setCancelable(true).setMessage(R.string.error_nfc_off)
            .setPositiveButton(R.string.turn_on, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                        Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS);
                        startActivity(settingsIntent);                          
                    }
                })
            .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();    
                        finish();
                    }
                })                  
            .create().show();
    }
    else{           
        mNfcPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        // Intent filters for exchanging over p2p.
        IntentFilter ndefDetected = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
        try {
            ndefDetected.addDataType("text/plain");
        } 
        catch (MalformedMimeTypeException e) {
            e.printStackTrace();
        }

        mNdefExchangeFilters = new IntentFilter[] { ndefDetected };

        //set receiving
        mNfcAdapter.enableForegroundDispatch(ActivityNFCReceiver.this, mNfcPendingIntent, mNdefExchangeFilters, null);
    }
}

public void onPause(){      
    if(mNfcAdapter != null && mNfcAdapter.isEnabled()){
        mNfcAdapter.disableForegroundDispatch(ActivityNFCReceiver.this);
    }
    super.onPause();        
}


@Override
protected void onNewIntent(Intent intent) {
    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
        NdefMessage[] msgs = getNdefMessages(intent);
        if(msgs.length > 0){
            try{
                NdefRecord firstRecord = msgs[0].getRecords()[0];
                byte[] payload = firstRecord.getPayload();
                String str = new String(payload, Charset.forName("UTF8"));

                int reservServerId = Integer.parseInt(str);
                returnResult(reservServerId);
            }
            catch(Exception e){
                messageDialog(R.string.error_during_NFC);
            }
        }
    }
}


public void onCancelClicked(View pV){
    finish();
}

private NdefMessage[] getNdefMessages(Intent intent) {
    // Parse the intent
    NdefMessage[] msgs = null;
    String action = intent.getAction();
    if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
        Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        if (rawMsgs != null) {
            msgs = new NdefMessage[rawMsgs.length];
            for (int i = 0; i < rawMsgs.length; i++) {
                msgs[i] = (NdefMessage) rawMsgs[i];
            }
        }
        else {
            // Unknown tag type
            byte[] empty = new byte[] {};
            NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
            NdefMessage msg = new NdefMessage(new NdefRecord[] { record });
            msgs = new NdefMessage[] { msg };
        }
    }
    else {
        Log.d(TAG, "Unknown intent.");
        finish();
    }
    return msgs;
}


private void returnResult(int pResServerId){
    Intent result = new Intent();
    result.putExtra(ActivityCompanySubMenu.EXTRA_RES_SERVER_ID, pResServerId);
    setResult(RESULT_OK, result);
    finish();
}

}


Solution

  • You can disable the Beam UI for a specific activity (i.e. your ActivityNFCReceiver) by using the NfcAdapter.setNdefPushMessage method:

    public void onCreate(Bundle savedInstanceState) {
    
        [...]
    
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (mNfcAdapter != null) {
            mNfcAdapter.setNdefPushMessage(null, this);
        }
    }
    

    Be aware that the enableForegroundNdefPush method has been deprecated since Android 4.0 (API level 14) and that pretty much all Android devices that support NFC use Android 4.0 or later.


    While disabling the Beam UI that way works on Nexus devices and is the officially documented way to go (see setNdefPushMessage):

    If setNdefPushMessage(...) is called with a null NDEF message [...] then NDEF push will be completely disabled for the specified activity(s). This also disables any default NDEF message the Android OS would have otherwise sent on your behalf for those activity(s).

    it seems that -- unfortunately -- this does not work for some Android devices' NFC implementation. For instance, it does not work on Samsung Galaxy S3 running Android 4.3.