0

I am trying to access an ISO-14443-4 NFC tag to read a rather substantial amount of data from the NFC tag and I am getting ANR frequently.

My Activity Code:

public class CardPairingActivity extends AppCompatActivity implements InfoDialogCopyDialog.InfoDialogCopyDialogListener, AlertCallbackDialog.AlertDialogListener {

    private static String username = "";
    private static String password = "";
    private static final byte role = Constants.USER_KEYMAN;
    private NfcAdapter nfcAdapter = null;
    private IntentFilter[] intentFiltersArray = null;
    private String[][] techListsArray = null;
    private PendingIntent pendingIntent = null;
    private APDUResult apduRes = null;
    private static Handler handler = new Handler();
    private static Runnable runnable = null;
    private static Context myContext = null;
    private static IsoDep iso14443 = null;
    private static Intent _intent = null;
    public static int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK;
    private static PairingCallBack callBack = null;

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

        myContext = this;
        callBack = new PairingCallBack();
        callBack.setContext(myContext);

        NfcManager nfcManager = (NfcManager) getSystemService(Context.NFC_SERVICE);
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        nfcAdapter.enableReaderMode(this, callBack, READER_FLAGS, null);

        if (nfcManager != null) {
            System.out.println("NFC Manager ready ...");
        }

        if (nfcAdapter != null) {
            System.out.println("NFC Adapter ready ...");
        }

        pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE);
        intentFiltersArray = new IntentFilter[]{new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)};
        techListsArray = new String[][]{new String[]{NfcA.class.getName()}, new String[]{NfcB.class.getName()}, new String[]{IsoDep.class.getName()}};

        if (android.os.Build.VERSION.SDK_INT > 9) {
            StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
            StrictMode.setThreadPolicy(policy);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        Intent intent = new Intent();
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE);
        if (nfcAdapter != null) {
            nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
            nfcAdapter.enableReaderMode(this, callBack, READER_FLAGS, null);
        }
    }

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

        if (nfcAdapter != null) {
            try {
                nfcAdapter.disableForegroundDispatch(this);
                nfcAdapter.disableReaderMode(this);
            } catch (IllegalStateException ex) {
                Log.e("ATHTAG", "Error disabling NFC foreground dispatch", ex);
            }
        }
    }

    @Override
    public void infoDialogCopyDialog() {
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    public static void continueExec() throws InvalidAlgorithmParameterException, InvalidSizeException, NoSuchPaddingException, IllegalBlockSizeException, IOException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, ShortBufferException, InvalidParameterSpecException, InvalidKeySpecException, NoSuchProviderException, IllegalFormatException {
        handler.post(new Runnable() {
            @Override
            public void run() {
                System.out.println("Authenticating to card ...");
                System.out.println("Card User Role: " + (int) role);
                System.out.println("Card Username: " + username);
                System.out.println("Card Secret: " + password);

                try {
                    if (API.cardAuthenticate(Constants.USER_KEYMAN, username, password.toCharArray())) {
                        // Login success
                        // DO VERY LONG CARD READING
                        API.readAndStoreNFCUserProfileData();
                    } else {
                        // Login fail
                        System.out.println("[ERR] Card Login FAILED ...");
                        int tries = API.adminCardLoginTriesRemaining(role, null);
                        if (tries != -1) {
                            SharedResource.showNotificationDialog("Card Pairing Failed", "Card Login Failed. \r\nLogin Retries Left: " + tries, "OK", myContext, MainActivity.class);
                        } else {
                            SharedResource.showNotificationDialog("Card Pairing Failed", "Card Login Failed.", "OK", myContext, MainActivity.class);
                        }
                    }
//                    SharedResource.getIsoDep().close();
//                    SharedResource.setIsoDep(null);
                } catch (InvalidAlgorithmParameterException e) {
                    throw new RuntimeException(e);
                } catch (InvalidSizeException e) {
                    throw new RuntimeException(e);
                } catch (NoSuchPaddingException e) {
                    throw new RuntimeException(e);
                } catch (ShortBufferException e) {
                    throw new RuntimeException(e);
                } catch (IllegalBlockSizeException e) {
                    throw new RuntimeException(e);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } catch (NoSuchAlgorithmException e) {
                    throw new RuntimeException(e);
                } catch (InvalidParameterSpecException e) {
                    throw new RuntimeException(e);
                } catch (InvalidKeySpecException e) {
                    throw new RuntimeException(e);
                } catch (BadPaddingException e) {
                    throw new RuntimeException(e);
                } catch (InvalidKeyException e) {
                    throw new RuntimeException(e);
                } catch (NoSuchProviderException e) {
                    throw new RuntimeException(e);
                } catch (IllegalFormatException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void alertCallbackDialog() {
        System.out.println("AlertCallbackDialog runs ...");
    }
}

NFC Callback

public class PairingCallBack implements NfcAdapter.ReaderCallback {
    Context myContext = null;

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onTagDiscovered(Tag tag) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                boolean hasIsoDep = false;

                if (tag != null) {
                    System.out.println("New tag found !!!");
                    System.out.println("Tag ID: " + BinUtils.toHexString(tag.getId()));
                    System.out.println("\r\n");
                    for (String tagTech : tag.getTechList()) {
                        System.out.println("Tech: " + tagTech);
                        if (tagTech.equals("android.nfc.tech.IsoDep")) {
                            hasIsoDep = true;
                        }
                    }
                } else {
                    System.out.println("No new tag found !!!");
                }

                if (hasIsoDep) {
                    try {
                        IsoDep iso14443 = IsoDep.get(tag);
                        iso14443.connect();
                        iso14443.setTimeout(120000);
                        SharedResource.setIsoDep(iso14443);
                        System.out.println("Selecting KeyVault applet ...");

                        boolean isSuccess = API.adminInit();
                        boolean isContinue = true;

                        System.out.println("-- PairingCallBack :: IsSuccess: " + isSuccess);
                        System.out.println("-- PairingCallBack :: IsContinue: " + isContinue);

                        if (isSuccess) {
                            if (isContinue) {


                                if ((byte) (SharedResource.getHwInfo().getInteractiveCapabilities() & Constants.INTERACT_SCREEN) == Constants.INTERACT_SCREEN) {
                                    System.out.println("[INF] Found interactive card ...");
                                    String toShow = "Please verify OTP Code:\n" + SharedResource.getSessionData().getOtpCode();
                                    String title = "Secure Card Session";
                                    AlertCallbackDialog dialog = new AlertCallbackDialog(title, toShow, myContext);
                                    dialog.show(((AppCompatActivity) myContext).getSupportFragmentManager(), "aldialog");
                                } else {
                                    System.out.println("[INF] Not interactive card ...");
                                    CardPairingActivity.continueExec();
                                }

                            }
                        } else {
                            System.out.println("[ERR] Failed to select applet !!!");
                        }
                    } catch (IOException | InvalidSizeException | InvalidAlgorithmParameterException |
                             NoSuchPaddingException | ShortBufferException | IllegalBlockSizeException |
                             NoSuchAlgorithmException | InvalidParameterSpecException |
                             InvalidKeySpecException | BadPaddingException | InvalidKeyException |
                             NoSuchProviderException ex) {
                        Log.e("ATHTAG", "Error found during tag comms", ex);
                    } catch (IllegalFormatException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();
    }

    public void setContext(Context context) {
        myContext = context;
    }

}

Alert dialog box to display an OTP secure channel code before downloading user profile from card

public class AlertCallbackDialog extends AppCompatDialogFragment {

    private AlertCallbackDialog.AlertDialogListener listener;
    private String title = "";
    private String message = "";
    private View view = null;

    public AlertCallbackDialog(String title, String message, Context context) {
        this.title = title;
        this.message = message;
    }

    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        System.out.println("[INF] Creating AlertCallbackDialog ...");
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setTitle(title);
        builder.setMessage(message);
        return builder.create();
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onDismiss(@NonNull DialogInterface dialog) {
        super.onDismiss(dialog);
        System.out.println("[INF] Dismissed AlertCallbackDialog ...");
        try {
            CardPairingActivity.continueExec();
        } catch (InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        } catch (InvalidSizeException e) {
            throw new RuntimeException(e);
        } catch (NoSuchPaddingException e) {
            throw new RuntimeException(e);
        } catch (IllegalBlockSizeException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (BadPaddingException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        } catch (ShortBufferException e) {
            throw new RuntimeException(e);
        } catch (InvalidParameterSpecException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException(e);
        } catch (NoSuchProviderException e) {
            throw new RuntimeException(e);
        } catch (IllegalFormatException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            System.out.println("[INF] Attaching AlertCallbackDialog ...");
            listener = (AlertCallbackDialog.AlertDialogListener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString() + " must implement AlertDialogListener");
        }
    }

    public interface AlertDialogListener {
        void alertCallbackDialog();
    }
}

I am wondering if there's something that is causing the ANR to as I have attempted to spawn off a new thread in handler and tried to remove as many things in the activity.

thotheolh
  • 7,040
  • 7
  • 33
  • 49
  • Are you performing the NFC reading on a background thread, or the activity thread? Performing it on the Activity thread would explain the ANRs. – Shark Jun 13 '23 at 10:38
  • It uses an NfcAdapter.ReaderCallback and calls a new thread in handler. – thotheolh Jun 13 '23 at 10:40
  • And what are you using for the Handler's backing thead? By "backing thread" i mean - which `Looper` is it using? Are you sure it's not using the UI thread, since I just see the `new Handler()` - i know it **shouldn't** default to the UI thread, however try looking at [this answer](https://stackoverflow.com/a/29389627/1101692) and fix your Handler initialization to make sure it's running on a background thread. – Shark Jun 13 '23 at 10:46
  • Not sure why you are using both `enableForegroundDispatch` and `enableReaderMode` as really `enableReaderMode` is a newer and better replacement for `enableForegroundDispatch`. Also not sure why you are creating a new thread in `onTagDiscovered` as that is already in it's own thread. Possibly with all the extra threading you have added, it's got back to the UI Thread as normally in `onTagDiscovered` you cannot interact with the UI thread and do things like show a dialog. – Andrew Jun 13 '23 at 11:36

0 Answers0