@Override public boolean setInputConnectionHandler(Handler handler) { if (handler == mIcPostHandler) { return true; } if (!mFocused) { return false; } if (DEBUG) { assertOnIcThread(); } // There are three threads at this point: Gecko thread, old IC thread, and new IC // thread, and we want to safely switch from old IC thread to new IC thread. // We first send a TYPE_SET_HANDLER action to the Gecko thread; this ensures that // the Gecko thread is stopped at a known point. At the same time, the old IC // thread blocks on the action; this ensures that the old IC thread is stopped at // a known point. Finally, inside the Gecko thread, we post a Runnable to the old // IC thread; this Runnable switches from old IC thread to new IC thread. We // switch IC thread on the old IC thread to ensure any pending Runnables on the // old IC thread are processed before we switch over. Inside the Gecko thread, we // also post a Runnable to the new IC thread; this Runnable blocks until the // switch is complete; this ensures that the new IC thread won't accept // InputConnection calls until after the switch. mActionQueue.offer(Action.newSetHandler(handler)); mActionQueue.syncWithGecko(); return true; }
@Override public void setSpan(Object what, int start, int end, int flags) { if (what == Selection.SELECTION_START) { if ((flags & Spanned.SPAN_INTERMEDIATE) != 0) { // We will get the end offset next, just save the start for now mSavedSelectionStart = start; } else { mActionQueue.offer(Action.newSetSelection(start, -1)); } } else if (what == Selection.SELECTION_END) { mActionQueue.offer(Action.newSetSelection(mSavedSelectionStart, end)); mSavedSelectionStart = -1; } else { mActionQueue.offer(Action.newSetSpan(what, start, end, flags)); } }
@Override public Editable replace(int st, int en, CharSequence source, int start, int end) { CharSequence text = source; if (start < 0 || start > end || end > text.length()) { Log.e( LOGTAG, "invalid replace offsets: " + start + " to " + end + ", length: " + text.length()); throw new IllegalArgumentException("invalid replace offsets"); } if (start != 0 || end != text.length()) { text = text.subSequence(start, end); } if (mFilters != null) { // Filter text before sending the request to Gecko for (int i = 0; i < mFilters.length; ++i) { final CharSequence cs = mFilters[i].filter(text, 0, text.length(), mProxy, st, en); if (cs != null) { text = cs; } } } if (text == source) { // Always create a copy text = new SpannableString(source); } mActionQueue.offer(Action.newReplaceText(text, Math.min(st, en), Math.max(st, en))); return mProxy; }
@Override public void removeSpan(Object what) { if (what == Selection.SELECTION_START || what == Selection.SELECTION_END) { Log.w(LOGTAG, "selection removed with removeSpan()"); } mActionQueue.offer(Action.newRemoveSpan(what)); }
@Override public void sendKeyEvent(final KeyEvent event, int action, int metaState) { if (DEBUG) { assertOnIcThread(); Log.d(LOGTAG, "sendKeyEvent(" + event + ", " + action + ", " + metaState + ")"); } /* We are actually sending two events to Gecko here, 1. Event from the event parameter (key event) 2. Sync event from the mActionQueue.offer call The first event is a normal event that does not reply back to us, the second sync event will have a reply, during which we see that there is a pending event-type action, and update the selection/composition/etc. accordingly. */ onKeyEvent(event, action, metaState, /* isSynthesizedImeKey */ false); mActionQueue.offer(new Action(Action.TYPE_EVENT)); }