Search code examples
javaandroidandroid-intentnfcintentfilter

Application receiving NFC always pops up new instance in front


I run through the following life-cycle when starting my app through the launcher:

onCreate..., onPostCreate...,  onResume..., onNewIntent..., act:android.intent.action.MAIN, mNfcAdapter.disableForegroundDispatch OK.

When I then tap a tag, a new instance of application seems to be launched as I run through the following life-cycle then:

onPause..., onCreate..., onPostCreate...,  onResume..., onNewIntent..., act:android.nfc.action.TAG_DISCOVERED, myTag.mId:048a1382bd2384)

Since I try to use the foreground dispatch system to disable recieving NFC events, I expected my app to ignore the NFC tag. So why is my activity recreated instead? Is it because AndroidManifest.xml allows it?

package com.example.pdf.nfcaccess;

import android.annotation.SuppressLint;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;

// PDf import
import android.telephony.TelephonyManager;
import android.content.Intent;
import android.content.Context;
import android.content.IntentFilter;
import android.app.PendingIntent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.widget.TextView;
import android.widget.Toast;
import android.util.Log;
import android.nfc.tech.NfcF;
import android.nfc.Tag;

/**
 * An example full-screen activity that shows and hides the system UI (i.e.
 * status bar and navigation/system bar) with user interaction.
 */
public class FullscreenActivity extends AppCompatActivity {
    /**
     * Whether or not the system UI should be auto-hidden after
     * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
     */
    private static final boolean AUTO_HIDE = true;

    /**
     * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
     * user interaction before hiding the system UI.
     */
    private static final int AUTO_HIDE_DELAY_MILLIS = 3000;

    /**
     * Some older devices needs a small delay between UI widget updates
     * and a change of the status and navigation bar.
     */
    private static final int UI_ANIMATION_DELAY = 300;
    private final Handler mHideHandler = new Handler();
    private View mContentView;
    private final Runnable mHidePart2Runnable = new Runnable() {
        @SuppressLint("InlinedApi")
        @Override
        public void run() {
            // Delayed removal of status and navigation bar

            // Note that some of these constants are new as of API 16 (Jelly Bean)
            // and API 19 (KitKat). It is safe to use them, as they are inlined
            // at compile-time and do nothing on earlier devices.
            mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
        }
    };
    private View mControlsView;

    // application
    static FullscreenActivity mInstance;

    NfcAdapter mNfcAdapter;
    Intent mNfcIntent;
    PendingIntent mNfcPendingIntent;
    IntentFilter mTagIntentFilter;
    IntentFilter[] mIntentFiltersArray;
    String[][] mTechLists;
    TextView mTagContentText;

    // log
    //java.util.ArrayList<String> mLogItems = new java.util.ArrayList<String>();
    java.util.ArrayList<String> mLogItems = new java.util.ArrayList<String>();
    android.widget.ArrayAdapter<String> mLogAdapter;
    android.widget.ListView mLogList;

    /**/
    private final Runnable mShowPart2Runnable = new Runnable() {
        @Override
        public void run() {
            // Delayed display of UI elements
            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null) {
                actionBar.show();
            }
            mControlsView.setVisibility(View.VISIBLE);
        }
    };
    private boolean mVisible;
    private final Runnable mHideRunnable = new Runnable() {
        @Override
        public void run() {
            hide();
        }
    };
    /**
     * Touch listener to use for in-layout UI controls to delay hiding the
     * system UI. This is to prevent the jarring behavior of controls going away
     * while interacting with activity UI.
     */

    /* Enable NFC
     */
    private final View.OnTouchListener mFuncNfcEnable = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                traceTry("mFuncNfcEnable");
                if (mNfcAdapter != null) {
                    try {
                        traceTry("mNfcAdapter.enableForegroundDispatch");
                        mNfcAdapter.enableForegroundDispatch(FullscreenActivity.mInstance, mNfcPendingIntent, mIntentFiltersArray, mTechLists);
                        traceOk();
                    }
                    catch (Throwable t) {
                        traceFails(t);
                    }
                }
            }
            return false;
        }
    };

    /* read Intent
     */
    private final View.OnTouchListener mFuncNfcRead = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if (mNfcAdapter != null) {
                try {
                    traceTry("onNewIntent");
                    onNewIntent(getIntent());
                    traceOk();
                }
                catch (Throwable t) {
                    traceFails(t);
                }
            }
            return false;
        }
    };

    /* Disable NFC
     */
    private final View.OnTouchListener mFuncNfcDisable = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if (motionEvent.getAction() == MotionEvent.ACTION_DOWN){
                traceTry("mFuncNfcDisable");
                if (mNfcAdapter != null) {
                    try {
                        traceTry("mNfcAdapter.disableForegroundDispatch");
                        mNfcAdapter.disableForegroundDispatch(FullscreenActivity.mInstance);
                        traceOk();
                    }
                    catch (Throwable t) {
                        traceFails(t);
                    }
                }
            }
            return false;
        }
    };

    /* Quit
     */
    private final View.OnTouchListener mFuncBtnQuit = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            finish();
            return false;
        }
    };

    /**/
    private void trace(String m) {
        Log.d("NFCTags",m);
        /*TextView tv = (TextView)findViewById(R.id.logContent_value);
        String previous = tv.getText().toString();
        tv.setText(previous + "\n" + m);*/
        if (mLogAdapter != null) {
            mLogItems.add(m);
            mLogAdapter.notifyDataSetChanged();
            mLogList.setSelection(mLogList.getCount()-1);
        }
    }

    String mMessage = "";
    private void traceTry(String m) {
        trace(m + "...");
        mMessage = m;
    }

    private void traceOk() {
        trace(mMessage + " OK");
        mMessage = "";
    }

    private void traceFails(Throwable t) {
        String msg = mMessage + " fails";
        if (t != null) {
            msg += " exception:" + t.getMessage();
        }
        trace(msg);
        //Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
        mMessage = "";
    }

    /*
        */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        trace("onCreate...");

        // set global unique instance
        FullscreenActivity.mInstance = this;

        setContentView(R.layout.activity_fullscreen);

        // log
        mLogItems.add("starts");
        mLogAdapter = new android.widget.ArrayAdapter<String>(this, R.layout.support_simple_spinner_dropdown_item, mLogItems);
        mLogList = (android.widget.ListView) findViewById(R.id.logList_value);
        mLogList.setAdapter(mLogAdapter);

        mVisible = true;
        mControlsView = findViewById(R.id.fullscreen_content_controls);
        mContentView = findViewById(R.id.fullscreen_content);
        mTagContentText = (TextView) findViewById(R.id.tagContent_value);

        // Set up the user interaction to manually show or hide the system UI.
        mContentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                toggle();
            }
        });

        // Upon interacting with UI controls, delay any scheduled hide()
        // operations to prevent the jarring behavior of controls going away
        // while interacting with the UI.

        findViewById(R.id.nfcRead_btn).setOnTouchListener(mFuncNfcRead);

        //findViewById(R.id.nfcDisable_btn).setOnTouchListener(mFuncNfcDisable);

        findViewById(R.id.quit_btn).setOnTouchListener(mFuncBtnQuit);

        trace("onCreate > before initializing nfc");

        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
        trace("ID:" + tm.getDeviceId());
        trace("Network Operator Name:" + tm.getNetworkOperatorName());
        trace("Sim Operator Name:" + tm.getSimOperatorName());
        trace("Sim Serial Number:" + tm.getSimSerialNumber());
        trace("Phone Type:" + tm.getPhoneType());
        trace("Initial Phone Number:" + tm.getLine1Number());

        boolean tryNfc = true;

        if (tryNfc) {

            try {
                mMessage = "NfcAdapter.getDefaultAdapter";
                mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
                if (mNfcAdapter == null) {
                    mMessage = "NFC is not available";
                    traceFails(null);
                    return;
                } else {
                    traceOk();
                }
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            // Check if NFC is enabled
            try {
                mMessage = "test NfcAdapter.isEnabled";
                if (!mNfcAdapter.isEnabled()) {
                    mMessage = "NFC is not enabled. do it manually";
                    traceFails(null);
                    return;
                } else {
                    trace("NFC is enabled.");
                }
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            try {
                mMessage = "create new Intent";
                mNfcIntent = new Intent(this, getClass());
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            try {
                mMessage = "mNfcIntent.addFlags, PendingIntent.getActivity";
                mNfcIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
                mNfcPendingIntent = PendingIntent.getActivity(this, 0, mNfcIntent, 0);
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            try {
                mMessage = "new IntentFilter";
                mTagIntentFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
                mTagIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            try {
                mMessage = "addDataType, new IntentFilter[]";
                mTagIntentFilter.addDataType("*/*");
                //mTagIntentFilter.addDataType("text/plain");
                mIntentFiltersArray = new IntentFilter[]{mTagIntentFilter};
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            // Setup a tech list for all NfcF tags
            try {
                mMessage = "new tech list";
                //mTechLists = new String[][]{new String[]{NfcF.class.getName()}};
                mTechLists = new String[][]{};
                traceOk();
            } catch (Throwable t) {
                traceFails(t);
                return;
            }

            /*
            if (mNfcAdapter != null) {
                try {
                    mMessage = "mNfcAdapter.enableForegroundDispatch";
                    mNfcAdapter.enableForegroundDispatch(FullscreenActivity.mInstance, mNfcPendingIntent, mIntentFiltersArray, mTechLists);
                    traceOk();
                }
                catch (Throwable t) {
                    traceFails(t);
                }
            }
            */
        }
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        trace("onPostCreate...");

        // Trigger the initial hide() shortly after the activity has been
        // created, to briefly hint to the user that UI controls
        // are available.
        //delayedHide(100);
    }

    private void toggle() {

        trace("toggle...");

        if (mVisible) {
            hide();
        } else {
            show();
        }
    }

    private void hide() {

        trace("hide...");

        // Hide UI first
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.hide();
        }
        mControlsView.setVisibility(View.GONE);
        mVisible = false;

        // Schedule a runnable to remove the status and navigation bar after a delay
        mHideHandler.removeCallbacks(mShowPart2Runnable);
        mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
    }

    @SuppressLint("InlinedApi")
    private void show() {
        // Show the system bar
        mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
        mVisible = true;

        // Schedule a runnable to display UI elements after a delay
        mHideHandler.removeCallbacks(mHidePart2Runnable);
        mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
    }

    /**
     * Schedules a call to hide() in [delay] milliseconds, canceling any
     * previously scheduled calls.
     */
    private void delayedHide(int delayMillis) {
        mHideHandler.removeCallbacks(mHideRunnable);
        mHideHandler.postDelayed(mHideRunnable, delayMillis);
    }

    /**/
    public String intentToText(Intent intent){

        String report = "?";
        try {
            Bundle bundle = intent.getExtras();
            if (bundle != null) {
                java.util.Set<String> keys = bundle.keySet();
                java.util.Iterator<String> it = keys.iterator();
                report = "Intent:{";
                while (it.hasNext()) {
                    String key = it.next();
                    report += "\n[" + key + ":" + bundle.get(key) + "],";
                }
                report += "}\n";
            }
        }
        catch(Throwable t){
            trace("intentToText > " + t.getMessage());
        }
        return report;
    }

    /**/
    public static String byteArrayToHex(byte[] a) {
        StringBuilder sb = new StringBuilder(a.length * 2);
        for(byte b: a)
            sb.append(String.format("%02x", b & 0xff));
        return sb.toString();
    }

    /**/
    @Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        trace("onNewIntent...");

        handleIntent(intent);
    }

    /**/
    void handleIntent(Intent intent) {

        if (intent == null) return;

        String sAction = intent.getAction();
        trace("act:" + sAction);

        if( (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()))
            ||  (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction()))
            ||  (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()))) {

            String payload = intent.getDataString();

            mTagContentText.setText("act:" + sAction + "\n" + "pload:" + payload + "\n" + intentToText(intent));

            Tag myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            if (myTag != null) {
                trace("myTag.mId:" + byteArrayToHex(myTag.getId()));
                mTagContentText.setText(mTagContentText.getText() + "\n" + "myTag.mId:" + byteArrayToHex(myTag.getId()));

                android.os.Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
                if (rawMsgs != null) {
                    for (android.os.Parcelable p : rawMsgs) {
                        NdefMessage msg = (NdefMessage) p;
                        NdefRecord[] records = msg.getRecords();
                        for (NdefRecord record : records) {
                            short tnf = record.getTnf();
                            byte[] id = record.getId();
                            byte[] payLoad = record.getPayload();
                        }
                    }
                }
            }
        }

    }

    /**/
    @Override
    protected void onResume() {
        super.onResume();

        trace("onResume...");
        if (mNfcAdapter != null) {
            try {
                // See if the Activity is being started/resumed due to an NFC event and handle it
                onNewIntent( getIntent());
            }
            catch (Throwable t) {
                traceFails(t);
            }

            try {
                mMessage = "mNfcAdapter.disableForegroundDispatch";
                mNfcAdapter.disableForegroundDispatch(FullscreenActivity.mInstance);
                traceOk();
            }
            catch (Throwable t) {
                traceFails(t);
            }
        }
    }

    /**/
    @Override
    protected void onPause() {
        super.onPause();

        trace("onPause...");
    }
}

AndroidManifest.xml is :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.pdf.nfcaccess">

    <uses-permission android:name="android.permission.NFC" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_pdflauncher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:debuggable="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".FullscreenActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:label="@string/app_name"
            android:theme="@style/FullscreenTheme">
            <intent-filter>
                <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" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>

    <!--intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain"/>
    </intent-filter-->

</manifest>

Solution

  • You receive the NFC intent (action TAG_DISCOVERED) because you registered for it in the manifest:

    <intent-filter>
        <action android:name="android.nfc.action.TAG_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    

    This is also the reason why your activity is recreated upon receiving the intent.

    The intent filter that you register for the foreground dispatch (action NDEF_DISCOVERED with any MIME type) does not seem to match your tag (or you did not yet invoke the piece of code that would enable the foreground dispatch).

    Note that calling disableForegroundDispatch() will ony disable a foreground dispatch previously registered through enableForegroundDispatch(). It will not influence the intent filters in your manifest. See Android: Can I enable/disable an activity's intent filter programmatically? on how you could selectively disable intent filters registered in the manifest. However, with regard to NFC intents, you would probably want to register to receive events for all tags through the foreground dispatch system and then, upon receiving the event in onNewIntent() selectively ignore tags that you don't want.

    A few more things about your code (actually only about the NFC parts)

    1. For the NDEF_DISCOVERED intent filter in your manifest you would typically also want to specify a <data ... /> element that matches the data type of your tags.

    2. Do not use a TAG_DISCOVERED intent filter in your manifest (unless you really understand and want the impact of this). The TAG_DISCOVERED intent filter (when used in the manifest) is merely a compatibility mode for API level 9 (before Android 2.3.3) where NFC support was very, very limited and a fall-back mode that can be used to create apps that handle NFC tags that are not supported by any other app.

    3. The TECH_DISCOVERED intent filter requires a tech-list XML file in order to match any tag. Thus, your current version of this filter in your manifest will never match anything.

    4. Calling disableForegroundDispatch() in onResume() does not make any sense. By design, the foreground dispatch could never be enabled at this point of the activity life-cycle. The reason for this is that you must not call enableForegroundDispatch() before onResume() and you are required to call disableForegroundDispatch() at latest in onPause().

    5. In fact it does not make sense to use enableForegroundDispatch() / disableForegroundDispatch() anywhere other than in onResume() / onPause(). If you want to stop listening for tags upon other events (e.g. a pressing a button), you would simply remember your current state (process/don't process NFC events) in some flag and, when you receive a new NFC intent in onNewIntent(), you would either process or silently ignore the tag based n that flag.

    6. Your code should not manually call activity life-cycle methods (as you currently do with onNewIntent(getIntent());).