Search code examples
androidnfcandroid-pendingintentmifareandroid-11

NFC not detected on android 11


I have to make an app that scans an NFC card, then takes a picture at the same time and send this information to a server, but my problem is that devices on android 11 doesn't detect my NFC Tag unlike devices on android 6 to android 9. I enable the foreground dispatch when i press a button (to take a picture (in, in2b and out2b) and then disable it only in onPause method. Also, when my app is running, the other apps (like NFC Tools) can't detect any NFC tag unless I stop my app. if anyone has any idea of the source of the problem, its welcome help, thanks Here is my code :



public class ScanRes extends AppCompatActivity {

    private NfcAdapter nfcAdapter;
    private PendingIntent pintent;
    private String nfcID = "";
    private ImageView imgpts;
    private int nbrClicks = 0;
    private TextView battery;
    private String URLAPI;
    private TextView msg_in;
    private TextView msg_out;
    private static boolean netWorskStatus = true;
    private NetworkChangeReceiver connectionState;


    private SurfaceView cameraDisplay;
    private Camera camera;
    SurfaceHolder surfaceHolder;


    //Valeurs Latitude et Longitude init au début
    private String Longitude = null;
    private String Latitude = null;
    private String Accuracy = null;

    private ImageView in;
    private ImageView in2b;
    private ImageView out2b;
    private TextView msg_sans_btn;
    private static final String SCAN_IN = "in";
    private static final String SCAN_OUT = "ou";
    private static final String SCAN = "u";
    private String specialScanReason = "";
    private String selectedChantier = "";
    private String scanType="";
    private TextView in_msg;

    //Variables qui vont stocker les options téléchargées via le serveur
    private int gps_frequency;
    private boolean photo_on;
    private int nbr_bouton;
    private boolean saved;
    private boolean mode_kiosque_on;
    private  boolean gps_force;
    private boolean mode_chantier_on;
    private boolean special_on;
    private boolean gps_on;
    private String nomChantierCourant;
    private String base64Img = "";
    private TextView msg_if_mode_boutons;
    private boolean mode_controle_acces_on;



    private boolean nfcScanRight = false;

    public static Handler handler;     //Attention, il était private avant
    public static HandlerThread handlerThread;
    public static Looper looper;

    private Handler handlerUS;
    public static HandlerThread handlerThreadUS;
    public static Looper looperUS;

    private static final int SPECIAL_SCAN_REQUEST_CODE = 00005;
    private static final int SELECTION_CHANTIERS_REQUEST_CODE = 00010;
    private static final int ADMIN_ACTIVITY_REQUEST_CODE = 5024;
    String chantiers[];
    private TextView chantierCourant;




    private void takePictureAfterScan(Context currentContext, String fileName, int rapportDeDivision){
        try{
            camera.takePicture(null, null, new Camera.PictureCallback() {
                @Override
                public void onPictureTaken(byte[] data, Camera camera) {
                    //startCameraSource();
                    Bitmap im = BitmapFactory.decodeByteArray(data, 0, data.length);
                    Matrix matrix = new Matrix();
                    matrix.postRotate(-90);
                    Bitmap image = Bitmap.createBitmap(im, 0, 0, im.getWidth(), im.getHeight(), matrix, true);
                    Bitmap resized = Bitmap.createScaledBitmap(image, im.getWidth()/rapportDeDivision, im.getHeight()/rapportDeDivision, false);
                    ByteArrayOutputStream byteArrayOutputStreamc = new ByteArrayOutputStream();
                    resized.compress(Bitmap.CompressFormat.JPEG, 50, byteArrayOutputStreamc);
                    byte[] byteArrayc = byteArrayOutputStreamc.toByteArray();
                    //String imgBuffc = Base64.encodeToString(byteArrayc, Base64.NO_WRAP);
                    base64Img = Base64.encodeToString(byteArrayc, Base64.NO_WRAP);
                    //WriteOnce(currentContext, imgBuffc, fileName);
                }
            });
        }catch(Exception e){
            e.printStackTrace();
        }
    }


    private void startCameraSource(){
        //cameraDisplay = (SurfaceView) findViewById(R.id.camera);
        if ((cameraDisplay != null) && (cameraDisplay.getHolder() != null) && (camera != null)) {
            try {
                camera.setPreviewDisplay(cameraDisplay.getHolder());
                camera.startPreview();
            } catch (Exception e) {
            }
        }
    }

    //Fonction initialisant la caméra
    private void initCamera() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 2552);
            return;
        }
        //cameraDisplay = (SurfaceView) findViewById(R.id.camera);
        try {
            if (camera != null)
                camera.release();
            camera =null;
            camera = Camera.open(1);

            camera.setDisplayOrientation(90);
            //cameraDisplay.setVisibility(View.VISIBLE);
            cameraDisplay.getHolder().addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceCreated(SurfaceHolder holder) {
                    startCameraSource();
                }

                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                }

                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {

                }
            });

        } catch (RuntimeException e) {
            if (camera == null)
                //setText("camera error");
                Log.i("CAMERA", "CAMERA ERROR");
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scan_res);

        connectionState = new NetworkChangeReceiver();
        registerNetworkBroadcastReceiver(connectionState);
        surfaceHolder = cameraDisplay.getHolder();

        handlerThread = new HandlerThread("MyHandlerThread", Thread.MAX_PRIORITY);
        handlerThread.start();
        looper = handlerThread.getLooper();
        handler = new Handler(looper);

        handler.post(new Runnable(){
            @Override
            public void run() {
                if(photo_on) {
                    initCamera();
                }
            }
        });


        //Init NFC Adapter
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        //If no NfcAdapter, display that the device has no NFC
        if (nfcAdapter == null){
            Toast.makeText(this,"Votre appareil n'est pas compatible avec le NFC", Toast.LENGTH_SHORT).show();
            finish();
        }
        pintent = PendingIntent.getActivity(this,0,new Intent(this,this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),0);
    }


    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onStart(){
        super.onStart();

        in.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                nfcScanRight = true;
                nfcAdapter.enableForegroundDispatch(ScanRes.this,pintent,null,null);
                scanType = SCAN;
                if(photo_on){
                    takePictureAfterScan(ScanRes.this, "base64.txt", 2);

                    handler.post(new Runnable(){
                        @Override
                        public void run() {
                            initCamera();
                            startCameraSource();
                        }
                    });
                }

                Toast.makeText(ScanRes.this, "Passez votre badge",Toast.LENGTH_SHORT).show();
            }
        });

        in2b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                nfcScanRight = true;
                nfcAdapter.enableForegroundDispatch(ScanRes.this,pintent,null,null);
                scanType = SCAN_IN;
                if(photo_on){
                    takePictureAfterScan(ScanRes.this, "base64.txt", 2);

                    handler.post(new Runnable(){
                        @Override
                        public void run() {
                            initCamera();
                            startCameraSource();
                        }
                    });
                }

                //cancelScan(5, ScanRes.this, ScanRes.this);
                Toast.makeText(ScanRes.this, "Passez votre badge",Toast.LENGTH_SHORT).show();
            }
        });

        out2b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                nfcScanRight = true;
                nfcAdapter.enableForegroundDispatch(ScanRes.this,pintent,null,null);
                scanType = SCAN_OUT;
                if(photo_on){
                    takePictureAfterScan(ScanRes.this, "base64.txt", 2);

                    handler.post(new Runnable(){
                        @Override
                        public void run() {
                            initCamera();
                            startCameraSource();
                        }
                    });
                }

                //cancelScan(5, ScanRes.this, ScanRes.this);
                Toast.makeText(ScanRes.this, "Passez votre badge",Toast.LENGTH_SHORT).show();
            }
        });


        if(special_on){
            in.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    // TODO Auto-generated method stub
                    scanType = SCAN;
                    if(photo_on){
                        takePictureAfterScan(ScanRes.this, "base64.txt", 2);
                    }
                    Intent ScanSpecial = new Intent(ScanRes.this, ScanSpecial.class);
                    startActivityForResult(ScanSpecial, SPECIAL_SCAN_REQUEST_CODE);
                    return true;
                }
            });

            in2b.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    // TODO Auto-generated method stub
                    scanType = SCAN_IN;
                    if(photo_on){
                        takePictureAfterScan(ScanRes.this, "base64.txt", 2);
                    }
                    Intent ScanSpecial = new Intent(ScanRes.this, ScanSpecial.class);
                    startActivityForResult(ScanSpecial, SPECIAL_SCAN_REQUEST_CODE);
                    return true;
                }
            });

            out2b.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    // TODO Auto-generated method stub
                    scanType = SCAN_OUT;
                    if(photo_on){
                        takePictureAfterScan(ScanRes.this, "base64.txt", 2);
                    }
                    Intent ScanSpecial = new Intent(ScanRes.this, ScanSpecial.class);
                    startActivityForResult(ScanSpecial, SPECIAL_SCAN_REQUEST_CODE);
                    return true;
                }
            });
        }

        Log.i("onstart", "onstart");
    }


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

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

                if(photo_on){
                    initCamera();
                    startCameraSource();
                }
                //if(netWorskStatus) sendUnregisteredScans(ScanRes.this);
            }
        });

        if(netWorskStatus) sendUnregisteredScans(ScanRes.this);

        assert nfcAdapter != null;

        //Dans le cas où l'on a  pas de bouton, on active le foregroundDispatch pour lire la carte sans avoir à appuyer sur un bouton
        if(nbr_bouton == 0){
            nfcScanRight = true;
            nfcAdapter.enableForegroundDispatch(ScanRes.this,pintent,null,null);
        }

        Log.i("onresume", "onresume");
    }

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

        if (nfcAdapter != null) {
            nfcAdapter.disableForegroundDispatch(this);
        }
        Log.i("onpause", "onpause");
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        unRegisterNetworkBroadcastReceiver(connectionState);
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        if(camera != null){
            camera.release();
        }
        Log.i("ondestroy", "ondestroy");
    }


    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        if(nfcScanRight){

            resolveIntent(intent);
            startCameraSource();

        }
        else{
            Toast.makeText(this, "Veuillez appuyer sur l'un des boutons avant de Scanner", Toast.LENGTH_SHORT);
        }
        Log.i("onNewIntent", "onNewIntent");
    }

    private void resolveIntent(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)) {
            Tag tag = (Tag) intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            assert tag != null;
            String nfc = getTagID(tag);

            handler.post(new Runnable(){
                @Override
                public void run() {
                    if(nbr_bouton == 0){
                        takePictureAfterScan(ScanRes.this, "base64.txt", 2);
                    }
                    JSONObject data = new JSONObject();
                    data = JSONDataBuilder(nfcID, base64Img, Latitude, Longitude, Accuracy);

                    if(gps_force && Latitude!= null && Longitude != null && Accuracy != null){
                        if(!sendToServer(data)){
                            Toast.makeText(ScanRes.this,"Vous n'êtes pas connecté à internet, le pointage sera envoyé lorsque vous serez connecté", Toast.LENGTH_SHORT).show();
                            Write(ScanRes.this, data.toString(), "listePointagesNonEnvoyes.txt");
                        }
                    }
                    else{
                        if(!sendToServer(data)){
                            Toast.makeText(ScanRes.this,"Vous n'êtes pas connecté à internet, le pointage sera envoyé lorsque vous serez connecté", Toast.LENGTH_SHORT).show();
                            Write(ScanRes.this, data.toString(), "listePointagesNonEnvoyes.txt");
//                            Write(ScanRes.this, data.toString(), "listePointagesNonEnvoyes.txt");
                        }
                    }
                }
            });
        }
    }


    private String getTagID(Tag tag){
        byte[] id = tag.getId();
        nfcID = toReversedHex(id).replaceAll("\\s", "");
        nfcScanRight = false;
        return  nfcID;
    }

    private String toReversedHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; ++i) {
            if (i > 0) {
                sb.append(" ");
            }
            int b = bytes[i] & 0xff;
            if (b < 0x10)
                sb.append('0');
            sb.append(Integer.toHexString(b));
        }
        return sb.toString();
    }

}

here is my manifest file content :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.digitalnomade.pointeusenomade"
    android:versionCode="2"
    android:versionName="3.0.0">

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

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

    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <uses-feature android:name="android.hardware.location.gps" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:extractNativeLibs="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme.NoActionBar">
        <activity android:name=".sccanact"></activity>
        <activity android:name=".SelectionChantiers" />
        <activity android:name=".ScanSpecial" />
        <activity android:name=".Infos" />
        <activity android:name=".Admin" />
        <activity
            android:name=".ScanRes"
            android:configChanges="orientation"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.BATTERY_CHANGED" />
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Solution

  • There have been a number of questions on this recently and it seems to be very device specific and the documentation for the old Camera API says that some devices might block the main event loop of your App and as the old NFC API you are using requires your App to be Paused and Resumed for the main event loop to receive the data from the NFC.

    If you are targeting API 19 and above for your App I would always use the newer and better NFC API called enableReaderMode, instead of enableForegroundDispatch.

    This newer API gives you more control and it does not require your App to be paused and resumed and the data is handled in it's own thread and won't be blocked by a blockage to the main event loop by the camera.

    Note I've not tried using enableReaderMode with camera operations, I just always use it for NFC operations because it is a better method of working with NFC.