英文:
Displaying Android AlertDialog immediately
问题
我注意到在Android中,AlertDialog不会立即显示。AlertDialog可能会在所有代码完全运行完毕后才渲染出来,这对UI流程来说太晚了。
我正在尝试构建一个用于Android的NFC卡片读卡器应用程序,在NFC通信期间,卡片和手机将彼此交换加密挑战,并且AlertDialog应该显示一次性挑战,以允许最终用户物理验证他们正在通信的卡片的真实性。
我注意到只有在所有其他主要代码完全执行后,AlertDialog才会显示挑战代码,这违反了挑战代码的目的。
我想知道是否有某种方法可以强制即时渲染一个AlertDialog,以便在要求时立即显示,并显示适当的挑战代码?
以下是我正在处理的代码的框架:
public class CardPairingActivity extends AppCompatActivity implements InfoDialogCopyDialog.InfoDialogCopyDialogListener {
// 这里是你的代码...
}
我该如何在用户继续进行其他活动之前,即时显示卡片发出的挑战代码?
英文:
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 ?
答案1
得分: 0
问题在于您在主线程循环中处理传入的NFC数据(违反了建议),然后将AlertDialog添加到主线程循环要处理的项目队列的末尾,使用handler.post(runnable);
因此,在onNewIntent
中的其余代码之后,runnable
中的所有代码都会运行
如果您希望用户在执行cardAuthenticate
之前确认“消息”,则代码流程应如下伪代码所示
- onNewIntent
- 存储
Tag
对象(简单的全局变量可以工作) - 连接到标签并获取消息
- 理想情况下关闭
Tag
连接 - 显示对话框
- 在确认对话框回调中重新连接到存储的Tag对象
- 调用
cardAuthenticate
使用旧的基于Intent的NFC方法不是理想的,使用更新且更好的enableReaderMode
会自动在后台线程中处理NFC,以防止由于NFC操作而导致ANR消息。
即使使用enableReaderMode
,您仍然需要存储Tag对象以供对话框的确认事件稍后使用。
请注意,从Android 12L开始,如果在使用过程中Tag对象过期,还应捕获SecurityException
。
英文:
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
- onNewIntent
- Store
Tag
object (a simple global variable can work) - Connect to Tag and get message
- Ideally Close
Tag
connection - Display dialog
- In the confirm dialog callback re-connect to the stored Tag object
- call
cardAuthenticate
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论