From d9f6a0ebb7aba7e5047034bcd92b9e5028fbc830 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Thu, 23 Feb 2012 20:23:01 -0800 Subject: [PATCH] Add a method to run the handler's messages from the tests This is really brutal, but after trying many many things I didn't find a better way. It works with the current implementation of MessageQueue. Since this only introduces a utility method it doesn't really do anything, but the functionality it provides will be essential to Bug: 5975557 Change-Id: I81c4113a08f9a8d8a88294d7dd3b6c8c483c8b1d --- .../inputmethod/latin/InputLogicTests.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index 78bd4d741..628601924 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -19,6 +19,8 @@ package com.android.inputmethod.latin; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.os.Looper; +import android.os.MessageQueue; import android.preference.PreferenceManager; import android.test.ServiceTestCase; import android.text.InputType; @@ -127,6 +129,51 @@ public class InputLogicTests extends ServiceTestCase { } } + // We need to run the messages added to the handler from LatinIME. The only way to do + // that is to call Looper#loop() on the right looper, so we're going to get the looper + // object and call #loop() here. The messages in the handler actually run on the UI + // thread of the keyboard by design of the handler, so we want to call it synchronously + // on the same thread that the tests are running on to mimic the actual environment as + // closely as possible. + // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method + // is called, so we need to do that at the right time so that #loop() returns at some + // point and we don't end up in an infinite loop. + // After we quit, the looper is still technically ready to process more messages but + // the handler will refuse to enqueue any because #quit() has been called and it + // explicitly tests for it on message enqueuing, so we'll have to reset it so that + // it lets us continue normal operation. + private void runMessages() { + // Here begins deep magic. + final Looper looper = mLatinIME.mHandler.getLooper(); + mLatinIME.mHandler.post(new Runnable() { + @Override + public void run() { + looper.quit(); + } + }); + // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue). + // Once #quit() is called remaining messages are not processed, which is why we post + // a message that calls it instead of calling it directly. + looper.loop(); + + // Once #quit() has been called, the message queue has an "mQuiting" field that prevents + // any subsequent post in this queue. However the queue itself is still fully functional! + // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal, + // coming back to this method to run the messages. + MessageQueue queue = looper.getQueue(); + try { + // However there is no way of doing it externally, and mQuiting is private. + // So... get out the big guns. + java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting"); + f.setAccessible(true); // What do you mean "private"? + f.setBoolean(queue, false); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME. private void type(final int codePoint) { // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the