0

I've been working on an Android NFC application on Android Studio, and I've hit a major roadblock. Basically, everytime my app detects a new NDEF NFC Tag, a new intent is created, and MainActivity seems to be reloaded, which results in onPause(), onCreate and onResume() being called.

The problem is, the app functions on a "session" system. The user scans a tag, logs in on a remote server, and the boolean sessionOpen is set to true. After that, the user is supposed to scan a different NFC tag, which would re-direct them on another page, while still being logged in (using a cookie) and the session still being opened.

Now, since onCreate() is called after a new NFC intent, it resets everything back to 0, which makes it impossible for me to keep track of their session., or anything for that matter.

I've tried overriding onSaveInstanceState and onRestoreInstanceState, but to no avail. Is there a way to prevent the NFC Intent from reloading the MainActivity, or from calling onCreate()? Or is there a way to restore variables from an older save state?

Here is my MainActivity.Java : (bits not relevant as well as some declarations have been removed)


import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Parcelable;
import android.provider.Settings;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.CookieSyncManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import android.widget.Button;

import android.webkit.CookieManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;

import com.google.android.material.snackbar.Snackbar;

import okhttp3.Cookie;



public class MainActivity extends AppCompatActivity {

    public static final String sessionSavedState = "";

    public boolean sessionOpen;
    WebView web;
    TextView usernameview;
    TextView uridetector;

    protected LocationManager locationManager;
    protected LocationListener locationListener;

    public String NFCid = "";


    public static final String Error_Detected = "No NFC Tag Detected";
    NfcAdapter nfcAdapter;
    PendingIntent pendingIntent;
    IntentFilter writingTagFilters[];
    boolean writeMode;
    Tag myTag;
    Context context;




    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);




        session = findViewById(R.id.sessionview);
        time = findViewById(R.id.sessiontime);
        user = findViewById(R.id.userview);
        Memo = findViewById(R.id.multiline);
        Memo.setMovementMethod(new ScrollingMovementMethod());
        Memo.setText("Init\n");

        tag_status = findViewById(R.id.tagstatus);

        web = findViewById(R.id.WebView1);

        CookieManager.getInstance().removeAllCookies(null);

        WebSettings webSettings = web.getSettings();
        webSettings.setJavaScriptEnabled(true);
        web.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        web.setWebViewClient(new Callback());

        web.setWebViewClient(new WebViewClient(){

            @Override
            public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
                if (CookieManager.getInstance().getCookie(web.getUrl()).contains("dc-auth"))
                {
                    AuthCount = AuthCount + 1;
                }

                Memo.setText(Memo.getText() + "1) cookie before: " +  CookieManager.getInstance().getCookie(web.getUrl()) + "\n");
                Memo.setText(Memo.getText() + "session status : " + String.valueOf(sessionOpen) + "\n");
                if (!sessionOpen)
                {
                    if (CookieManager.getInstance().getCookie(web.getUrl()).contains("dc-auth") && AuthCount >= 2)
                    {
                        super.doUpdateVisitedHistory(view, url, isReload);
                        Memo.setText(Memo.getText() + "1) session opened\n");
                        sessionOpen = true;
                        tag_status.setImageResource(R.drawable.tagetape_wait);
                    }
                    else
                    {
                        super.doUpdateVisitedHistory(view, url, isReload);
                    }

                }
                else
                {
                    Memo.setText(Memo.getText() + "1) passage\n");
                    super.doUpdateVisitedHistory(view, url, isReload);
                }
                Memo.setText(Memo.getText() + "1.1) passage\n");
                String currentUrl = view.getUrl();
            }

        });

        web.loadUrl("adress.com");

        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        locationListener = new LocationListener() {
            @Override
            public void onLocationChanged(@NonNull Location location) {


            }

        };
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[] {
                    Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.INTERNET
            }, 10);
            return;
        }
        locationManager.requestLocationUpdates(locationManager.GPS_PROVIDER, 5000, 0, locationListener);

        Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        longitudeString = String.valueOf(location.getLongitude());
        latitudeString = String.valueOf(location.getLatitude());

        if (longitudeString.isEmpty() && latitudeString.isEmpty())
        {
            LocationChange = false;
        }
        else{
            LocationChange = true;
        }


        context = this;


        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (nfcAdapter == null) {
            Toast.makeText(this, "This device does not support NFC", Toast.LENGTH_SHORT).show();
            finish();
        }
        readFromIntent(getIntent());
        pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), 0);
        IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);

        tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
        writingTagFilters = new IntentFilter[] { tagDetected };

    }


    public void readFromIntent(Intent intent) {
        String action = intent.getAction();
        Uri uri = intent.getData();
        if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
            Parcelable[] rawMsgs = getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            byte[] uid = tag.getId(); //This is the Unique IDentifier of the card
            //tag_id.setText(bytesToHex(uid)); //recuperation du payload NFC
            NFCid = bytesToHex(uid);


            NdefMessage[] msgs = null;
            if (rawMsgs != null) {
                msgs = new NdefMessage[rawMsgs.length];
                for (int i = 0; i < rawMsgs.length; i++) {
                    msgs[i] = (NdefMessage) rawMsgs[i];
                    //uridetector.setText(msgs[i].toString());
                }
            }
            buildTagViews(msgs);
        }


    }


    @SuppressLint("SetTextI18n")
    private void buildTagViews(NdefMessage[] msgs) {
        if (msgs == null || msgs.length == 0) return;

        Uri ndef_uri;

        String text = "";

        String uri = "";
        String ID = "";

        String cookie = "";

        byte[] payload = msgs[0].getRecords()[0].getPayload();
        byte[] URIpayload = msgs[0].getRecords()[1].getPayload();

        int languageCodeLength = payload[0] & 0063; //get the language code, for e.x : "en"
        String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16"; //verify encoding



        try {
            text = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
            uri = new String(URIpayload, languageCodeLength - 1, URIpayload.length - languageCodeLength + 1, textEncoding);

            if (text.contains(";") && text.contains("user")) {
                textList = text.split(";");
                user.setText(textList[1]);
                session.setText("session de " + textList[1]);

                if (textList[2].contains("1")) { //DEBUG MODE


                    cookie = CookieManager.getInstance().getCookie(web.getUrl());
                    Memo.setText(Memo.getText() + "cookie before: " + cookie + "\n");
                }

                tag_status.setImageResource(R.drawable.tagdetected);


                if (uri.contains("user") && uri.contains("adress.com")) {

                    if (LocationChange = false)
                    {
                        Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "Service de geo-location en cours d'initialisation. Veuillez patienter...", Snackbar.LENGTH_LONG);
                        snackbar.show();
                    }
                    else
                    {
                        uri = uri + "arugments"
                        web.loadUrl("https://" + uri);

                    }
                }

            } else if (text.contains("mobile")) {
                tag_status.setImageResource(R.drawable.tagdetected);


                if (!sessionOpen) {
                    Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "Aucune session ouverte. Veuillez scanner un tag utilisateur et/ou vous logger.", Snackbar.LENGTH_LONG);
                    snackbar.show();
                }
                else
                {
                    tag_status.setImageResource(R.drawable.tagetape_wait);
                }

                if (uri.contains("stop") && uri.contains("adress.com"))
                {
                    cookie = CookieManager.getInstance().getCookie(web.getUrl());
                    Memo.setText(Memo.getText() + "cookie after: " + cookie + "\n");
                    if (sessionOpen) {
                        uri = uri + "?tagid=" + NFCid + "&deviceid=" + getDeviceId2(context);
                        web.loadUrl("https://" + uri);
                    }
                }
                else
                {
                    Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "Invalid NFC URL. ", Snackbar.LENGTH_LONG);
                    snackbar.show();
                }

            } else {
                Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "Tag NFC Invalide.", Snackbar.LENGTH_LONG);
                snackbar.show();
            }


        } catch (UnsupportedEncodingException e) {
            Log.e("UnsupportedEncoding", e.toString());
        }


        int prefixCode = payload[0] & 0x0FF;
        if (prefixCode >= URI_PREFIX.length) prefixCode = 0;

        String reducedUri = new String(payload, 1, payload.length - 1, Charset.forName("UTF-8"));



    }


    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        // Save the user's current game state
        savedInstanceState.putBoolean(sessionSavedState, sessionOpen);

// Always call the superclass so it can save the view hierarchy state
        super.onSaveInstanceState(savedInstanceState);
    }

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        // Restore UI state from the savedInstanceState.
        // This bundle has also been passed to onCreate.
        sessionOpen = savedInstanceState.getBoolean(sessionSavedState);

    }

    @Override
    protected void onDestroy(){
        super.onDestroy();

        // Clear all the cookies
        CookieManager.getInstance().removeAllCookies(null);
        CookieManager.getInstance().flush();

        web.clearCache(true);
        web.clearFormData();
        web.clearHistory();
        web.clearSslPreferences();
    }




    public void clearCookiesAndCache(Context context){
        CookieSyncManager.createInstance(context);
        CookieManager cookieManager = CookieManager.getInstance();

        cookieManager.removeAllCookie();
    }

    public static String getDeviceId2(Context context) {


        String androidId = Settings.Secure.getString(
                context.getContentResolver(), Settings.Secure.ANDROID_ID);

        return androidId;
    }




    private class Callback extends WebViewClient {
        @Override
        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
            return false; //do not override loaded page
        }
    }

    //timeout - disconnect methods



    @Override
    protected void onResume()
    {

        super.onResume();
        NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);

        if (sessionOpen) {
            mCountDown.start();
    }

    }
    @Override
    protected void onPause()
    {
        super.onPause();
        NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        nfcAdapter.disableForegroundDispatch(this);

        mCountDown.cancel();
    }
    @Override
    public void onUserInteraction()
    {
        super.onUserInteraction();

        // user interact cancel the timer and restart to countdown to next interaction
        mCountDown.cancel();
        if (sessionOpen) {
            mCountDown.start();
        }
    }





}

Surox
  • 25
  • 2
  • This is a perfect scenario where you should use some architecture pattern to decouple things from your activity. The recommended way is to use MVVM. ViewModels are not recreated when the activity recreates. – Filip Petrovski Jan 24 '22 at 20:01
  • 1
    Don't use the old API of `nfcAdapter.enableForegroundDispatch` use the newer and better API of `nfcAdapter.enableReaderMode` . This does not pause the App when a Tag is detected, instead a new Thread is created to handle the Tag. See https://stackoverflow.com/a/59290945/2373819 – Andrew Jan 24 '22 at 21:48
  • 1
    Does this answer your question? [Is there a possibility to read NFC tags from Android without intents?](https://stackoverflow.com/questions/59288436/is-there-a-possibility-to-read-nfc-tags-from-android-without-intents) – Andrew Jan 24 '22 at 21:48
  • Is there any code examples I could take a look at? Looking around I've managed to write [this](https://pastebin.com/E48eM2pQ) into my onResume() function, but it seems the app doesn't run readfromintent and buildmsgs when scanning a tag now... I've of course removed the NFC Intent from onCreate(), and added `disableReadermode` in onPause() – Surox Jan 25 '22 at 11:19
  • I've also tried writing the callback function to receive NDEF Messages seperately, i.e with `getNdefMessage` [here](https://pastebin.com/n9Gx7G2X), but the app is throwing exceptions, saying the ndef.connect() are invoked on a null object... – Surox Jan 25 '22 at 12:09
  • 1
    I've done a number of examples https://stackoverflow.com/a/64441986/2373819 and https://stackoverflow.com/a/59397667/2373819 https://stackoverflow.com/a/64921434/2373819 – Andrew Jan 25 '22 at 12:35
  • 1
    For `throwing exceptions, saying the ndef.connect()` you need to test the result of `Ndef ndefTag = Ndef.get(tag);` as per docs this will return `null` if the Tag is not of Ndef format. So the system things the tag you presented has not Ndef data on it. – Andrew Jan 25 '22 at 12:38
  • Alright, I've managed to implement and read the NDEF Payload, and it seems to be working well in that regard, as the main activity is no longer reloaded and everything is done inside of onTagDiscovered. What I've noticed however, is the detection rate for the NFC Tags is much lower, with me having to try a couple time before the app picks up on it. I do make sure to close the tag once it's read with `ndefTag.close();`, so I'm wondering what's the cause behind the sudden slow detection... – Surox Jan 25 '22 at 14:36
  • Outside the app, detection is fast so I'm thinking it has something to do with intent filters – Surox Jan 25 '22 at 14:45

0 Answers0