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