@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;
 }