@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();
 }
  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;
  }
 @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
 }
  @Override
  public synchronized boolean endBatchEdit() {
    if (mBatchEditCount > 0) {
      mBatchEditCount--;
      if (mBatchEditCount == 0) {
        // Force Goanna update for cancelled auto-correction of single-
        // character words to prevent character duplication. (bug 1133802)
        boolean forceUpdate = !mBatchTextChanged && !mBatchSelectionChanged;

        if (mBatchTextChanged) {
          notifyTextChange();
          mBatchTextChanged = false;
        }
        if (mBatchSelectionChanged) {
          Editable editable = getEditable();
          notifySelectionChange(
              Selection.getSelectionStart(editable), Selection.getSelectionEnd(editable));
          mBatchSelectionChanged = false;
        }
        mEditableClient.setUpdateGoanna(true, forceUpdate);
      }
    } else {
      Log.w(LOGTAG, "endBatchEdit() called, but mBatchEditCount == 0?!");
    }
    return true;
  }
 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 Editable getEditable() {
   return mEditableClient.getEditable();
 }
 @Override
 public synchronized boolean beginBatchEdit() {
   mBatchEditCount++;
   mEditableClient.setUpdateGoanna(false, false);
   return true;
 }
 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;
 }