简体   繁体   中英

how to intercept NFC tag before onNewIntent executes

I have an app that captures NFC tags. The problem i have had in the past is that users hover over the tag in an unsteady manner causeing the NFC adapter to trigger twice.

I have done a few things to counter this.

manifest:

<activity
    android:name=".NfcActivity"
    android:screenOrientation="portrait"
    android:launchMode="singleTask" 
    android:noHistory="true"
    android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation">

    <intent-filter>


        <action android:name="android.nfc.action.NDEF_DISCOVERED" />

        <category android:name="android.intent.category.DEFAULT" />

        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>

This sets the NFC capturing Activity to be the only instance in the stack and there to be no history. I've overridden all the config changes that can stop and relaunch this activity, the latter can lead to intent data being redelivered to the activity, making it look like a duplicate scan.

In the Activity itself i have overridden onNewIntent to do nothing but show a bad scan screen. I also understand that onNewIntent should mirror onCreate from a functionality standpoint, but because previous versions of the app have sent 2 scans to the next Activity, i just want the NFC capturing code in one place, onCreate.

In onCreate i do further tests to counter against hovering over a tag and creating a bad scan.

  • I check the launched form history flag in the intent. Android can kill an app when low on memory and relaunch later REDELIVERING the original intent. This can cause what seems like a duplicate scan.
  • In onCreate i check that the user STILL has the phone connected to the tag. This proves the user is not hovering over the tag with ndefTag.connect();

The app seems to work fine but on one particular phone (Samsung Galaxy Young 2), if the user places the phone on the tag for say a few seconds the the NFC adapter seems to fire a few times in a row.

When this happens the original scan is cancelled. The reason for this is oncreate processes the tag but when a subsequent scan happens(by hovering, by accident), onPause -> onNewIntent runs. So the Activity jumps out of onCreate and stops processing the tag. onNewIntent shows a failed scan screen and launches the menu screen.

The above isn't too much of a problem as all that happens is the user must re-scan the tag.

What i would like to happen is:

When onCreate runs, the tag is processed no matter what, even if onNewintent executes. Is there a way maybe to intercept the intent before is reaches onNewintent and onPause?

Maybe there is a global flag i can use, that can be checked first to say that onCreate is still running and onNewIntent shouldn't, or more importantly onPause isn't called making onCreate stop running.

import java.util.List;
import java.util.concurrent.ExecutionException;

import org.ndeftools.Message;
import org.ndeftools.Record;

import android.app.Activity;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.os.Vibrator;
import android.util.Log;
import android.widget.Toast;

public class NfcActivity extends Activity {

    private static final String TAG = NfcActivity.class.getName();

    protected NfcAdapter nfcAdapter;
    protected PendingIntent nfcPendingIntent;

    Handler handler;
    Runnable runnable;

    Handler failHandler;
    Runnable failRunnable;

    Parcelable[] messages;

    Intent i;

    Tag tag;
    String tagId;

    boolean nfcConnected;

    ProgressDialog progressDialog;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.nfcactivitylayout);
        Log.e(TAG, "oncreate");
        nfcConnected = false;

        // initialize NFC
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        nfcPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

        tag = null;
        tagId = null;

        i = getIntent();

        if ((i.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {

            //check to see if Android has previously killed the app and relaunched it from History
            //and delivered the original intent.
            //if it has do not process and launch the menu screen
                Intent processPayloadIntent = new Intent(NfcActivity.this, NfcscannerActivity.class);
                processPayloadIntent.setAction("QRCODE_ACTION"); 
                processPayloadIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(processPayloadIntent);

            }else{



        tag = i.getParcelableExtra(NfcAdapter.EXTRA_TAG);

        tagId = bytesToHexString(tag.getId());

        Log.e(TAG, "tagID = " + tagId);

        Log.e(TAG, "oncreate intent action = " + i.getAction());




        //The activity has captured tag data, prove the user is not hovering over the tag and is doing a good scan
        //hovering can trigger the adapter twice

        AsyncNfcConnect asnc = new AsyncNfcConnect();
        try {
            asnc.execute().get();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }




        Log.e(TAG, "nfcConnected!!!!!!!!!!!!!!!!!!!!!!!!! = " + nfcConnected);

        if(nfcConnected == true){


            int buildVersionSdk = Build.VERSION.SDK_INT;
            int buildVersionCodes = Build.VERSION_CODES.GINGERBREAD;

            Log.e(TAG, "buildVersionSdk = " + buildVersionSdk
                    + "buildVersionCodes = " + buildVersionCodes);

            int themeVersion;
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD) {

                themeVersion = 2;

            } else {

                themeVersion = 1;
            }

            try{

            progressDialog = new ProgressDialog(this, themeVersion);
            progressDialog.setTitle("NFC Tag Scanned");
            progressDialog.setMessage("Processing tag...");
            progressDialog.setIndeterminate(true);
            progressDialog.show();

            }catch(Exception e){        }




        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(i.getAction()) || NfcAdapter.ACTION_TAG_DISCOVERED.equals(i.getAction())) {            

            if(NfcScannerApplication.isCanScanNfcTag()){

            messages = i.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
            if (messages != null) {


                //setContentView(R.layout.successfulnfc);

                NfcScannerApplication.startNfcTimer();
                //Toast.makeText(this, "NFC timer set", Toast.LENGTH_LONG).show();

                Log.e(TAG, "Found " + messages.length + " NDEF messages"); // is almost always just one

                vibrate(); // signal found messages :-)



                initHandler();
                handler.postDelayed(runnable,  2000);

      }else{

          Toast.makeText(this, "Data on tag was not correct", Toast.LENGTH_LONG).show();

              try{

                handler.removeCallbacks(runnable);
                Log.e(TAG, "just removed callback to runnable that reads nfc tag data");

                }catch(Exception e){

                }




                initFailHandler();
                failHandler.postDelayed(failRunnable,  1);



      }

            }else{


                try{

                    handler.removeCallbacks(runnable);
                    Log.e(TAG, "just removed callback to runnable that reads nfc tag data");

                    }catch(Exception e){

                    }




                    initFailHandler();
                    failHandler.postDelayed(failRunnable,  1);


            }



        } else {

            Toast.makeText(this, "Tag not recognized correctly", Toast.LENGTH_LONG).show();

                try{

                handler.removeCallbacks(runnable);
                Log.e(TAG, "just removed callback to runnable that reads nfc tag data");

                }catch(Exception e){

                }




                initFailHandler();
                failHandler.postDelayed(failRunnable,  1);

        }


        }else{


            try{

                Toast.makeText(this, "Phone wasn't connected to Tag", Toast.LENGTH_LONG).show();

                handler.removeCallbacks(runnable);
                Log.e(TAG, "just removed callback to runnable that reads nfc tag data");

                }catch(Exception e){

                }




                initFailHandler();
                failHandler.postDelayed(failRunnable,  1);



        }//end of NFC connect test


        }//end of launched from history check


    }//end of onCreate





    @Override
    protected void onStart() {
        super.onStart();
        Log.e(TAG, "onStart");
    }





    @Override
    protected void onStop() {
        super.onStop();
        Log.e(TAG, "onStop");
    }





    @Override
    public void onNewIntent(Intent intent) {
        Log.e(TAG, "onNewIntent");

                            Toast.makeText(this, "Bad scan!!!", Toast.LENGTH_LONG).show();



                            initFailHandler();
                            failHandler.postDelayed(failRunnable, 1);



    }//end of onNewIntent






    public void enableForegroundMode() {
        Log.e(TAG, "enableForegroundMode");

        IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); // filter for all
        IntentFilter[] writeTagFilters = new IntentFilter[] {tagDetected};
        nfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null);
    }

    public void disableForegroundMode() {
        Log.e(TAG, "disableForegroundMode");

        nfcAdapter.disableForegroundDispatch(this);
    }





    @Override
    protected void onResume() {
     super.onResume();
     Log.e(TAG, "onResume");

        enableForegroundMode();
    }

    @Override
    protected void onPause() {
        Log.e(TAG, "onPause");

        super.onPause();

        disableForegroundMode();

        if(handler != null){
            handler.removeCallbacks(runnable);
        }


    }

    private void vibrate() {
        Log.e(TAG, "vibrate");

        Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE) ;
        vibe.vibrate(500);
    }


    public void initHandler(){

          handler = new Handler();
          runnable = new Runnable() {
                public void run() {
                    processTag();

                }

                private void processTag() {
                    Log.e(TAG, "about to process tag");

                    try{
                        progressDialog.dismiss();

                    }catch(Exception e){

                        //do nothing
                    }

                    // parse to records
                    for (int i = 0; i < messages.length; i++) {
                        try {
                            List<Record> records = new Message((NdefMessage)messages[i]);

                            Log.e(TAG, "Found " + records.size() + " records in message " + i);

                            for(int k = 0; k < records.size(); k++) {
                                Log.e(TAG, " Record #" + k + " is of class " + records.get(k).getClass().getSimpleName());

                                Record record = records.get(k);

                                NdefRecord ndefRecord = record.getNdefRecord();

                                byte[] arr = ndefRecord.getPayload();

                                String payload = new String(arr);


                                if(payload.length() > 0){

                                payload = payload.substring(3, payload.length());

                                Log.e(TAG, "payload = " + payload);

                                String[] splitPayload = payload.split(",");

                                String tagType = splitPayload[0];
                                String tagCompany = splitPayload[1];
                                String tagClientID = splitPayload[2];
                                String tagClientName = splitPayload[3];

                                if(! tagClientID.equalsIgnoreCase("0") && tagClientID.length() > 0){

                                    handler.post(new Runnable(){
                                        public void run() {

                                            setContentView(R.layout.successfulnfc);

                                          }
                                        });






                                Intent processPayloadIntent = new Intent(NfcActivity.this, NfcscannerActivity.class);
                                processPayloadIntent.putExtra("payload", payload);
                                processPayloadIntent.putExtra("tagid", tagId);
                                processPayloadIntent.setAction("NFC"); 
                                processPayloadIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                                //processPayloadIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
                                startActivity(processPayloadIntent);
                                finish();
                                overridePendingTransition(0, R.anim.activity_animation_zoom_in);





                                    }else{
                                        Toast.makeText(NfcActivity.this, "Tag data problem/Scan problem.", Toast.LENGTH_LONG).show();

                                        initFailHandler();
                                        failHandler.postDelayed(failRunnable, 1);


                                    }

                                }else{
                                    Toast.makeText(NfcActivity.this, "Tag data problem/Scan problem.", Toast.LENGTH_LONG).show();

                                    initFailHandler();
                                    failHandler.postDelayed(failRunnable, 1);

                                }



                            }
                        } catch (Exception e) {
                            Log.e(TAG, "Problem parsing message", e);
                        }

                    }

                }
            };

        }





    public void initFailHandler(){

          failHandler = new Handler();
          failRunnable = new Runnable() {
                public void run() {

                    returnToMainMenu();

                }

                private void returnToMainMenu() {
                    //Log.e(TAG, "about to return to main menu");

                    try{
                        progressDialog.dismiss();

                    }catch(Exception e){

                        //do nothing
                    }

                    Toast.makeText(NfcActivity.this, "Please check your scanning technique.\nPlease do not hover over tag or swipe...", Toast.LENGTH_LONG).show();

                    failHandler.post(new Runnable(){
                        public void run() {

                            setContentView(R.layout.nfcfail);

                          }
                        });






                    //onBackPressed();
                    Intent processPayloadIntent = new Intent(NfcActivity.this, NfcscannerActivity.class);
                    processPayloadIntent.setAction("QRCODE_ACTION"); 
                    processPayloadIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                    startActivity(processPayloadIntent);
                    finish();
                    //overridePendingTransition(0, R.anim.activity_animation_zoom_in);

                }
            };

        }




    private String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder("0x");
        if (src == null || src.length <= 0) {
            return null;
        }

        char[] buffer = new char[2];
        for (int i = 0; i < src.length; i++) {
            buffer[0] = Character.forDigit((src[i] >>> 4) & 0x0F, 16);
            buffer[1] = Character.forDigit(src[i] & 0x0F, 16);
            System.out.println(buffer);
            stringBuilder.append(buffer);
        }

        return stringBuilder.toString();
    }



private class AsyncNfcConnect extends AsyncTask<String, Void, String> {






        @Override
        protected String doInBackground(String... params) {

            NfcActivity.this.nfcConnected = false;
            String result;
            Ndef ndefTag = Ndef.get(tag);

            try {
                Log.e(TAG, "about to test connect()********************************************");
                ndefTag.connect();  // this should already perform an IO operation and should therefore fail if there is no tag
                Log.e(TAG, "Ndef.connect() connected!********************************************");
                NdefMessage ndefMsg = ndefTag.getNdefMessage();  // this reads the current NDEF message from the tag and consequently causes an IO operation

                NfcActivity.this.nfcConnected = true;
                result = "OK";
                return result;

            } catch (Exception e) {
                // there is no tag or communication with tag dropped
                Log.e(TAG, "There a problem with connecting to the tag using Ndef.connect(");
                NfcActivity.this.nfcConnected = false;
                result = "NOTOK";
                return result;
            } finally {
                try {
                    ndefTag.close();
                } catch (Exception e) {
                }
            }


        }



    }//end of Async


}

You seem to insist on not handling NFC intents in onNewIntent() . I would suggest that onCreate() and onNewIntent() both call a common procedure for scanning the tag. In that way, both entry points would follow the same code path.

Your claim that the app "jumps out of onCreate" is probably just a figure of speech? What happens is that your tag scanning AsyncNfcConnect runs on a separate thread as a background task (as it should). That task is created in onCreate() and continues running after onCreate() has finished (you could add add a Log statement at the end of onCreate() to check). When the connection with the tag is lost somehow and the tag is rediscovered, onNewIntent() is called, as you observed.

In any case there is no way to prevent this from happening, so your app has to be able to handle it. To detect this and deal with it, you could set some flag inside your activity to indicate that your background task is running or has run. You could also store the information whether an exception has occurred and simply try scanning the tag again when it is rediscovered (this probably requires that you implement the suggestion I made above). If you want to make your app even more failure proof, you could also store the ID of the last scanned tag to positively identify it again when it is rediscovered after you have successfully scanned it (or not). When exceptions keep occurring with the same tag, you could indicate his after a certain number of times to the user (eg by suggesting that the device be positioned differently wrt the tag).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM