0

I'm trying to read NFC tags in my Android Application, the NFC tags are simple card with plain text on it, after looking at Android documentation and looking some other guides i've ended up with the following code in my AndroidManifest:

<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />

    <activity
        android:name=".pterm" // activity where i would be able to read NFC
        android:screenOrientation="portrait"
        android:theme="@style/SplashScreen">
        <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.nfc.action.TAG_DISCOVERED" />
        </intent-filter>
    </activity>

And in my activity i've added the following code:

  @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (nfcRead) {
            readFromIntent(intent);
        }
    }

    private void readFromIntent(Intent intent) {
        String action = intent.getAction();
        if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)
                || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)
                || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
            Parcelable[] rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
            NdefMessage[] messages = null;
            if (rawMessages != null) {
                messages = new NdefMessage[rawMessages.length];
                for (int i = 0; i < rawMessages.length; i++) {
                    messages[i] = (NdefMessage) rawMessages[i];
                    NdefRecord[] records = messages[i].getRecords();
                    //if you are sure you have text then you don't need to test TNF
                    for(NdefRecord record: records){
                        processRecord(record);
                    }
                }
            }
        }
    }

    public void processRecord(NdefRecord record) {

        short tnf = record.getTnf();
        switch (tnf) {

            case NdefRecord.TNF_WELL_KNOWN: {
                if (Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)) {
                    String yourtext = processRtdTextRecord(record.getPayload());
                    Log.e("NFC:", yourtext);
                } else if (Arrays.equals(record.getType(), NdefRecord.RTD_URI)) {
                    return;
                } else if (Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER)) {
                    return;
                } else {
                    return;
                }
            }
            case NdefRecord.TNF_MIME_MEDIA: {
                if (record.toMimeType().equals("MIME/Type")) {
                    // handle this as you want
                } else {
                    //Record is not our MIME
                }
            }
            // you can write more cases
            default: {
                //unsupported NDEF Record
            }
        }
    }

    private String processRtdTextRecord(byte[] payload) {
        String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
        int languageCodeLength = payload[0] & 0063;

        String text = "";
        try {
            text = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            Log.e("UnsupportedEncoding", e.toString());
        }
        return text;
    }

But when i'm trying to read the NFC tag the event onNewIntent is not even triggered but the NFC is read as the device makes NFC notification sound..

The purpose of the application is to read the NFC only when a custom AletDialog is UP, once the NFC has read the value it should be placed in an EditText and a new value could be read again only when the Dialog is up again.

The application is using LockTaskMode.

NiceToMytyuk
  • 3,644
  • 3
  • 39
  • 100

1 Answers1

1

Update:

Based on the comment that the App is in LockTaskMode this might change the systems ability to re-launch Apps to deliver an intent to it (I have no experience of LockTaskMode)

I suggest that you don't use the old NFC API's that have to use launching or re-launching Apps when you are in the restrictive LockTaskMode.

I suggest that you try the newer and much better enableReaderMode NFC API to read cards. https://developer.android.com/reference/android/nfc/NfcAdapter#enableReaderMode(android.app.Activity,%20android.nfc.NfcAdapter.ReaderCallback,%20int,%20android.os.Bundle)

This enableReaderMode NFC API causes a callback in your App that starts a new thread to handle the incoming NFC data, there is no launch or re-launching of you App and therefore might not have a problem with LockTaskMode.

example of enableReaderMode API use.


public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback{

private NfcAdapter mNfcAdapter;

@Override
    protected void onResume() {
        super.onResume();
        enableNfc();
    }

    private void enableNfc(){
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if(mNfcAdapter!= null && mNfcAdapter.isEnabled()) {
            // READER_PRESENCE_CHECK_DELAY is a work around for a Bug in some NFC implementations.
            Bundle options = new Bundle();
            options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);

            // Ask for all type of cards to be sent to the App as they all might contain NDEF data
            mNfcAdapter.enableReaderMode(this,
                    this,
                    NfcAdapter.FLAG_READER_NFC_A |
                            NfcAdapter.FLAG_READER_NFC_B |
                            NfcAdapter.FLAG_READER_NFC_F |
                            NfcAdapter.FLAG_READER_NFC_V |
                            NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
                    options);
        } else {
            NfcMessage();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(mNfcAdapter!= null)
            mNfcAdapter.disableReaderMode(this);
    }


// This gets run in a new thread on Tag detection
// Thus cannot directly interact with the UI Thread
public void onTagDiscovered(Tag tag) {

// Get the Tag as an NDEF tag Technology
Ndef mNdef = Ndef.get(tag);

        // only process if there is NDEF data on the card
        if (mNdef != null) {
             NdefMessage mNdefMessage = mNdef.getCachedNdefMessage();

             // Now process the Ndef message
             ....

             // If success of reading the correct Ndef message
             // Make a notification sound (as we disabled PLATFORM_SOUNDS when enabling reader Mode)
             // As getting a confirmation sound too early causes bad user behaviour especial when trying to write to cards
        }

        // Ignore other Tag types.

}


....

Original I'll leave the original answer here as it shows here the working of the System NFC App which with enableForegroundDispatch directly delivers the an intent with the NFC data in it to your App by re-launching it and Pausing and Resuming it which fires the onNewIntent
It also explains if you have not enableForegroundDispatch then the system NFC App passes the NFC intent to the Android OS which causes it to find an App that can handle this type of Intent, which it does by looking at all the App's manifest files for Intent filter to match.

So the Manifest Intent filters will cause Android to launch your App if a matching Tag is scanned by the system.

onNewIntent is for

the activity is re-launched while at the top of the activity stack instead of a new instance of the activity being started, onNewIntent() will be called on the existing instance with the Intent that was used to re-launch it.

From https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)

So in this scenario where your App is not running it cannot be re-launched thus the Intent has to be captured in onCreate Instead.

So in onCreate put something like

        Intent intent = getIntent();

        // Check to see if the App was started because of the Intent filter in the manifest, if yes read the data from the Intent
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
          readFromIntent(intent);
        }

Now if you want to cover receiving NFC data while the App is running you need to use enableForegroundDispatch and onNewIntent

https://developer.android.com/guide/topics/connectivity/nfc/advanced-nfc#foreground-dispatch

Which can be done creating a Intent Filter in onResume and enabling foreground dispatch (foreground dispatch should be disabled when your app is not in the foreground i.e in onPause as shown).

e.g. something like


private NfcAdapter adapter;

public void onResume() {
    super.onResume();
    pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
            getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
    try {
        // Only look for plain text mime type
        ndef.addDataType("text/plain");
    }
    catch (MalformedMimeTypeException e) {
        throw new RuntimeException("fail", e);
    }
    intentFiltersArray = new IntentFilter[] {ndef, };


    adapter = NfcAdapter.getDefaultAdapter(this);
    adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, null);
}

@Override
public void onPause() {
    super.onPause();
    adapter.disableForegroundDispatch(this);

}

Andrew
  • 8,198
  • 2
  • 15
  • 35
  • I'm not interested in opening the App when the NFC has been read but just to be able to read data from an nfc card when an AlertDialog is up and set it's data in an EditText, after trying to set `Intent` in onCreate, or by using `enableForegroundDispatch` and `onNewIntent` in debug i'm still unable to read `readFromIntent` method, and the application is still trying to be relaunched (but as it's in LockTaskMode it's just casting the message to unlock it) – NiceToMytyuk Oct 20 '20 at 09:30
  • Updated Answer based on the new info that you are using `LockTaskMode` as this could interfere with NFC reading using the old Intent based method. – Andrew Oct 20 '20 at 10:41
  • 1
    I should have also said it is probably best to always capture all NFC types in the Activity and then choose only to process it it IF the alert Dialog is being shown at the time. This means the OS won't try and do anything (Including play a Tag Discovered sound) when the Alert Dialog is not shown (Another benefit of `enableReaderMode` is control of the Tag Discovered sound) – Andrew Oct 20 '20 at 16:13
  • I've actually added `enableNFC()` in my method where i'm calling dialog.show() and i'm calling `disableReaderMode()` on it's dismiss, when a new tag has been read and it's pattern `.equals` to what i'm looking for i'm doing the sond notification and accepting the read data, thank you! – NiceToMytyuk Oct 20 '20 at 16:17