@Override public boolean sendKeyEvent(KeyEvent event) { // BaseInputConnection.sendKeyEvent() dispatches the key event to the main thread. // In order to ensure events are processed in the proper order, we must block the // IC thread until the main thread finishes processing the key event super.sendKeyEvent(event); final View v = getView(); if (v == null) { return false; } final Handler icHandler = mEditableClient.getInputConnectionHandler(); final Handler mainHandler = v.getRootView().getHandler(); if (icHandler.getLooper() != mainHandler.getLooper()) { // We are on separate IC thread but the event is queued on the main thread; // wait on IC thread until the main thread processes our posted Runnable. At // that point the key event has already been processed. mainHandler.post( new Runnable() { @Override public void run() { InputThreadUtils.sInstance.endWaitForUiThread(); } }); InputThreadUtils.sInstance.waitForUiThread(icHandler); } return false; // seems to always return false }
public void runOnIcThread( final Handler uiHandler, final GoannaEditableClient client, final Runnable runnable) { final Handler icHandler = client.getInputConnectionHandler(); if (icHandler.getLooper() == uiHandler.getLooper()) { // IC thread is UI thread; safe to run directly runnable.run(); return; } runOnIcThread(icHandler, runnable); }
@Override public Handler getHandler(Handler defHandler) { if (!canReturnCustomHandler()) { return defHandler; } final Handler newHandler = getBackgroundHandler(); if (mEditableClient.setInputConnectionHandler(newHandler)) { return newHandler; } // Setting new IC handler failed; return old IC handler return mEditableClient.getInputConnectionHandler(); }
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; }