private void runOnIcThread(Handler icHandler, final Runnable runnable) { if (DEBUG) { ThreadUtils.assertOnUiThread(); Log.d(LOGTAG, "runOnIcThread() on thread " + icHandler.getLooper().getThread().getName()); } Runnable runner = new Runnable() { @Override public void run() { try { Runnable queuedRunnable = mIcRunnableSync.take(); if (DEBUG && queuedRunnable != runnable) { throw new IllegalThreadStateException("sync error"); } queuedRunnable.run(); } catch (InterruptedException e) { } } }; try { // if we are not inside waitForUiThread(), runner will call the runnable icHandler.post(runner); // runnable will be called by either runner from above or waitForUiThread() mIcRunnableSync.put(runnable); } catch (InterruptedException e) { } finally { // if waitForUiThread() already called runnable, runner should not call it again icHandler.removeCallbacks(runner); } }
public void endWaitForUiThread() { if (DEBUG) { ThreadUtils.assertOnUiThread(); Log.d(LOGTAG, "endWaitForUiThread()"); } try { mIcRunnableSync.put(mIcSignalRunnable); } catch (InterruptedException e) { } }
private boolean processKey(int keyCode, KeyEvent event, boolean down) { if (GamepadUtils.isSonyXperiaGamepadKeyEvent(event)) { event = GamepadUtils.translateSonyXperiaGamepadKeys(keyCode, event); keyCode = event.getKeyCode(); } if (keyCode > KeyEvent.getMaxKeyCode() || !shouldProcessKey(keyCode, event)) { return false; } event = translateKey(keyCode, event); keyCode = event.getKeyCode(); View view = getView(); if (view == null) { InputThreadUtils.sInstance.sendEventFromUiThread( ThreadUtils.getUiHandler(), mEditableClient, GoannaEvent.createKeyEvent(event, 0)); return true; } // KeyListener returns true if it handled the event for us. KeyListener is only // safe to use on the UI thread; therefore we need to pass a proxy Editable to it KeyListener keyListener = TextKeyListener.getInstance(); Handler uiHandler = view.getRootView().getHandler(); Editable uiEditable = InputThreadUtils.sInstance.getEditableForUiThread(uiHandler, mEditableClient); boolean skip = shouldSkipKeyListener(keyCode, event); if (down) { mEditableClient.setSuppressKeyUp(true); } if (skip || (down && !keyListener.onKeyDown(view, uiEditable, keyCode, event)) || (!down && !keyListener.onKeyUp(view, uiEditable, keyCode, event))) { InputThreadUtils.sInstance.sendEventFromUiThread( uiHandler, mEditableClient, GoannaEvent.createKeyEvent(event, TextKeyListener.getMetaState(uiEditable))); if (skip && down) { // Usually, the down key listener call above adjusts meta states for us. // However, if we skip that call above, we have to manually adjust meta // states so the meta states remain consistent TextKeyListener.adjustMetaAfterKeypress(uiEditable); } } if (down) { mEditableClient.setSuppressKeyUp(false); } return true; }
public void waitForUiThread(Handler icHandler) { if (DEBUG) { ThreadUtils.assertOnThread(icHandler.getLooper().getThread(), AssertBehavior.THROW); Log.d( LOGTAG, "waitForUiThread() blocking on thread " + icHandler.getLooper().getThread().getName()); } try { Runnable runnable = null; do { runnable = mIcRunnableSync.take(); runnable.run(); } while (runnable != mIcSignalRunnable); } catch (InterruptedException e) { } }
/** * This is a hack. * * <p>Startup results in us writing prefs -- we fetch the Distribution, which caches its state. * Our tests try to wipe those prefs, but apparently sometimes race with startup, which leads to * us not getting one of our expected messages. The test fails. * * <p>This hack waits for any existing background tasks -- such as the one that writes prefs -- to * finish before we begin the test. */ private void waitForBackgroundHappiness() { final Object signal = new Object(); final Runnable done = new Runnable() { @Override public void run() { synchronized (signal) { signal.notify(); } } }; synchronized (signal) { ThreadUtils.postToBackgroundThread(done); try { signal.wait(); } catch (InterruptedException e) { mAsserter.ok(false, "InterruptedException waiting on background thread.", e.toString()); } } mAsserter.dumpLog("Background task completed. Proceeding."); }
public Editable getEditableForUiThread( final Handler uiHandler, final GoannaEditableClient client) { if (DEBUG) { ThreadUtils.assertOnThread(uiHandler.getLooper().getThread(), AssertBehavior.THROW); } final Handler icHandler = client.getInputConnectionHandler(); if (icHandler.getLooper() == uiHandler.getLooper()) { // IC thread is UI thread; safe to use Editable directly return client.getEditable(); } // IC thread is not UI thread; we need to return a proxy Editable in order // to safely use the Editable from the UI thread if (mUiEditable != null) { return mUiEditable; } final InvocationHandler invokeEditable = new InvocationHandler() { @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (DEBUG) { ThreadUtils.assertOnThread(uiHandler.getLooper().getThread(), AssertBehavior.THROW); Log.d(LOGTAG, "UiEditable." + method.getName() + "() blocking"); } synchronized (icHandler) { // Now we are on UI thread mUiEditableReturn = null; mUiEditableException = null; // Post a Runnable that calls the real Editable and saves any // result/exception. Then wait on the Runnable to finish runOnIcThread( icHandler, new Runnable() { @Override public void run() { synchronized (icHandler) { try { mUiEditableReturn = method.invoke(client.getEditable(), args); } catch (Exception e) { mUiEditableException = e; } if (DEBUG) { Log.d(LOGTAG, "UiEditable." + method.getName() + "() returning"); } icHandler.notify(); } } }); // let InterruptedException propagate icHandler.wait(); if (mUiEditableException != null) { throw mUiEditableException; } return mUiEditableReturn; } } }; mUiEditable = (Editable) Proxy.newProxyInstance( Editable.class.getClassLoader(), new Class<?>[] {Editable.class}, invokeEditable); return mUiEditable; }