These days I\'m working on simulating modal dialog in Android. I\'ve googled a lot, there\'s much discussions but sadly there\'s not much options to get it modal. Here\'s so
Finally I ended up with a really straight and simple solution.
People who's familiar with Win32 programming possibly knows how to implement a modal dialog. Generally it runs a nested message loop (by GetMessage/PostMessage) when there is a modal dialog up. So, I tried to implement my own modal dialog in this traditional way.
At the first, android didn't provide interfaces to inject into ui thread message loop, or I didn't find one. When I looked into source, Looper.loop(), I found it's exactly what I wanted. But still, MessageQueue/Message haven't provided public interfaces. Fortunately, we have reflection in java. Basically, I just copied exactly what Looper.loop() did, it blocked workflow and still properly handled events. I haven't tested nested modal dialog, but theoretically it would work.
Here's my source code,
public class ModalDialog {
private boolean mChoice = false;
private boolean mQuitModal = false;
private Method mMsgQueueNextMethod = null;
private Field mMsgTargetFiled = null;
public ModalDialog() {
}
public void showAlertDialog(Context context, String info) {
if (!prepareModal()) {
return;
}
// build alert dialog
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(info);
builder.setCancelable(false);
builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
ModalDialog.this.mQuitModal = true;
dialog.dismiss();
}
});
AlertDialog alert = builder.create();
alert.show();
// run in modal mode
doModal();
}
public boolean showConfirmDialog(Context context, String info) {
if (!prepareModal()) {
return false;
}
// reset choice
mChoice = false;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(info);
builder.setCancelable(false);
builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
ModalDialog.this.mQuitModal = true;
ModalDialog.this.mChoice = true;
dialog.dismiss();
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
ModalDialog.this.mQuitModal = true;
ModalDialog.this.mChoice = false;
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
doModal();
return mChoice;
}
private boolean prepareModal() {
Class> clsMsgQueue = null;
Class> clsMessage = null;
try {
clsMsgQueue = Class.forName("android.os.MessageQueue");
} catch (ClassNotFoundException e) {
e.printStackTrace();
return false;
}
try {
clsMessage = Class.forName("android.os.Message");
} catch (ClassNotFoundException e) {
e.printStackTrace();
return false;
}
try {
mMsgQueueNextMethod = clsMsgQueue.getDeclaredMethod("next", new Class[]{});
} catch (SecurityException e) {
e.printStackTrace();
return false;
} catch (NoSuchMethodException e) {
e.printStackTrace();
return false;
}
mMsgQueueNextMethod.setAccessible(true);
try {
mMsgTargetFiled = clsMessage.getDeclaredField("target");
} catch (SecurityException e) {
e.printStackTrace();
return false;
} catch (NoSuchFieldException e) {
e.printStackTrace();
return false;
}
mMsgTargetFiled.setAccessible(true);
return true;
}
private void doModal() {
mQuitModal = false;
// get message queue associated with main UI thread
MessageQueue queue = Looper.myQueue();
while (!mQuitModal) {
// call queue.next(), might block
Message msg = null;
try {
msg = (Message)mMsgQueueNextMethod.invoke(queue, new Object[]{});
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
if (null != msg) {
Handler target = null;
try {
target = (Handler)mMsgTargetFiled.get(msg);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (target == null) {
// No target is a magic identifier for the quit message.
mQuitModal = true;
}
target.dispatchMessage(msg);
msg.recycle();
}
}
}
}
Hopefully this would help.