I noticed that AlertDialog in Android does not show immediately. The AlertDialog could be rendered after all the codes have fully run it's course and the dialog pops up which is too late in the UI process.
I am trying to build an NFC card reader application for Android and during the NFC communication, the card and phone will exchange cryptographic challenges to each other and the AlertDialog is suppose to display a one-time challenge to allow the end user to physically verify the authenticity of the card they are communicating with.
I noticed that the AlertDialog will only display the challenge code once all the other main codes have fully executed which defeats the purpose of a challenge code.
I would like to know if there are certain ways to force rendering of a just-in-time AlertDialog that will render immediately when asked to do so with the appropriate challenge codes to be displayed ?
Here is a skeleton code of what I am working on:
public class CardPairingActivity extends AppCompatActivity implements InfoDialogCopyDialog.InfoDialogCopyDialogListener {
private static String username = "";
private static String password = "";
private static final byte role = Constants.USER_KEYMAN;
private ImageView imageView = null;
private ProgressBar spinner = null;
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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card_pairing);
myContext = this;
NfcManager nfcManager = (NfcManager) getSystemService(Context.NFC_SERVICE);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
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);
}
}
protected void onPause() {
super.onPause();
if (nfcAdapter != null) {
try {
nfcAdapter.disableForegroundDispatch(this);
} catch (IllegalStateException ex) {
Log.e("ATHTAG", "Error disabling NFC foreground dispatch", ex);
}
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
System.out.println("Doing onNewIntent() ...");
// Use NFC to read tag
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
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();
SharedResource.setIsoDep(iso14443);
System.out.println("Selecting KeyVault applet ...");
HashMap<String, Object> result = API.adminInit();
boolean isSuccess = (boolean) result.get("Success");
if (isSuccess) {
String toShow = (String) result.get("Message");
if (toShow != null) {
System.out.println("Showing Secure Session popup ...");
runnable = new Runnable() {
@Override
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(myContext);
builder.setTitle("Secure Card Session");
builder.setMessage(toShow);
AlertDialog alert = builder.create();
alert.show();
}
};
handler.post(runnable);
}
if (API.cardAuthenticate(Constants.USER_KEYMAN, username, password.toCharArray())) {
// Login success
System.out.println("[INF] Card Login SUCCESS ...");
} 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", this, MainActivity.class);
} else {
SharedResource.showNotificationDialog("Card Pairing Failed", "Card Login Failed.", "OK", this, MainActivity.class);
}
}
} else {
System.out.println("[ERR] Failed to select applet !!!");
}
iso14443.close();
} catch (IOException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (InvalidSizeException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (InvalidAlgorithmParameterException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (NoSuchPaddingException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (ShortBufferException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (IllegalBlockSizeException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (NoSuchAlgorithmException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (InvalidParameterSpecException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (InvalidKeySpecException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (BadPaddingException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (InvalidKeyException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (IllegalFormatException ex) {
Log.e("ATHTAG", "Error found during tag comms", ex);
} catch (NoSuchProviderException e) {
throw new RuntimeException(e);
}
}
}
public static void setCredentials(String usr, String pass, String authorName, String authorID) {
username = usr;
password = pass;
serverAuthorName = authorName;
serverAuthorID = authorID;
}
@Override
public void infoDialogCopyDialog() {
}
}
What can I do to display a challenge-code issued by the card just-in-time before the user proceeds other activities ?
The problem is you are handling the incoming NFC data in your main thread loop (against recommendations) and then you adding the AlertDialog to end of the queue of items the main thread loop is to process with the handler.post(runnable);
Thus all the code in the runnable
get run after the rest of the code in onNewIntent
If you want the user to confirm the "Message" before you cardAuthenticate
then the flow of the code should go like the below pseudo Code
Tag
object (a simple global variable can work)Tag
connectioncardAuthenticate
It's not ideal to the the old Intent based method working with NFC, using the newer and better enableReaderMode
automatically handles NFC in a background thread so as to prevent ANR messages due to NFC operations.
Even with enableReaderMode
you will still have to store the Tag object for later use by the confirm event from the Dialog.
Note with Android 12L onwards you should also catch a SecurityException
for it the Tag object goes out of date while you are using it.