@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; }
private void icUpdateGecko(boolean force) { // Skip if receiving a repeated request, or // if suppressing compositions during text selection. if ((!force && mIcUpdateSeqno == mLastIcUpdateSeqno) || mSuppressCompositions) { if (DEBUG) { Log.d(LOGTAG, "icUpdateGecko() skipped"); } return; } mLastIcUpdateSeqno = mIcUpdateSeqno; mActionQueue.syncWithGecko(); if (DEBUG) { Log.d(LOGTAG, "icUpdateGecko()"); } final int selStart = mText.getSpanStart(Selection.SELECTION_START); final int selEnd = mText.getSpanEnd(Selection.SELECTION_END); int composingStart = mText.length(); int composingEnd = 0; Object[] spans = mText.getSpans(0, composingStart, Object.class); for (Object span : spans) { if ((mText.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) { composingStart = Math.min(composingStart, mText.getSpanStart(span)); composingEnd = Math.max(composingEnd, mText.getSpanEnd(span)); } } if (DEBUG) { Log.d(LOGTAG, " range = " + composingStart + "-" + composingEnd); Log.d(LOGTAG, " selection = " + selStart + "-" + selEnd); } if (composingStart >= composingEnd) { if (selStart >= 0 && selEnd >= 0) { onImeSetSelection(selStart, selEnd); } else { onImeRemoveComposition(); } return; } if (selEnd >= composingStart && selEnd <= composingEnd) { onImeAddCompositionRange( selEnd - composingStart, selEnd - composingStart, IME_RANGE_CARETPOSITION, 0, 0, false, 0, 0, 0); } int rangeStart = composingStart; TextPaint tp = new TextPaint(); TextPaint emptyTp = new TextPaint(); // set initial foreground color to 0, because we check for tp.getColor() == 0 // below to decide whether to pass a foreground color to Gecko emptyTp.setColor(0); do { int rangeType, rangeStyles = 0, rangeLineStyle = IME_RANGE_LINE_NONE; boolean rangeBoldLine = false; int rangeForeColor = 0, rangeBackColor = 0, rangeLineColor = 0; int rangeEnd = mText.nextSpanTransition(rangeStart, composingEnd, Object.class); if (selStart > rangeStart && selStart < rangeEnd) { rangeEnd = selStart; } else if (selEnd > rangeStart && selEnd < rangeEnd) { rangeEnd = selEnd; } CharacterStyle[] styleSpans = mText.getSpans(rangeStart, rangeEnd, CharacterStyle.class); if (DEBUG) { Log.d(LOGTAG, " found " + styleSpans.length + " spans @ " + rangeStart + "-" + rangeEnd); } if (styleSpans.length == 0) { rangeType = (selStart == rangeStart && selEnd == rangeEnd) ? IME_RANGE_SELECTEDRAWTEXT : IME_RANGE_RAWINPUT; } else { rangeType = (selStart == rangeStart && selEnd == rangeEnd) ? IME_RANGE_SELECTEDCONVERTEDTEXT : IME_RANGE_CONVERTEDTEXT; tp.set(emptyTp); for (CharacterStyle span : styleSpans) { span.updateDrawState(tp); } int tpUnderlineColor = 0; float tpUnderlineThickness = 0.0f; // These TextPaint fields only exist on Android ICS+ and are not in the SDK. if (Versions.feature14Plus) { tpUnderlineColor = (Integer) getField(tp, "underlineColor", 0); tpUnderlineThickness = (Float) getField(tp, "underlineThickness", 0.0f); } if (tpUnderlineColor != 0) { rangeStyles |= IME_RANGE_UNDERLINE | IME_RANGE_LINECOLOR; rangeLineColor = tpUnderlineColor; // Approximately translate underline thickness to what Gecko understands if (tpUnderlineThickness <= 0.5f) { rangeLineStyle = IME_RANGE_LINE_DOTTED; } else { rangeLineStyle = IME_RANGE_LINE_SOLID; if (tpUnderlineThickness >= 2.0f) { rangeBoldLine = true; } } } else if (tp.isUnderlineText()) { rangeStyles |= IME_RANGE_UNDERLINE; rangeLineStyle = IME_RANGE_LINE_SOLID; } if (tp.getColor() != 0) { rangeStyles |= IME_RANGE_FORECOLOR; rangeForeColor = tp.getColor(); } if (tp.bgColor != 0) { rangeStyles |= IME_RANGE_BACKCOLOR; rangeBackColor = tp.bgColor; } } onImeAddCompositionRange( rangeStart - composingStart, rangeEnd - composingStart, rangeType, rangeStyles, rangeLineStyle, rangeBoldLine, rangeForeColor, rangeBackColor, rangeLineColor); rangeStart = rangeEnd; if (DEBUG) { Log.d( LOGTAG, " added " + rangeType + " : " + Integer.toHexString(rangeStyles) + " : " + Integer.toHexString(rangeForeColor) + " : " + Integer.toHexString(rangeBackColor)); } } while (rangeStart < composingEnd); onImeUpdateComposition(composingStart, composingEnd); }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object target; final Class<?> methodInterface = method.getDeclaringClass(); if (DEBUG) { // Editable methods should all be called from the IC thread assertOnIcThread(); } if (methodInterface == Editable.class || methodInterface == Appendable.class || methodInterface == Spannable.class) { // Method alters the Editable; route calls to our implementation target = this; } else { // Method queries the Editable; must sync with Gecko first // then call on the inner Editable itself mActionQueue.syncWithGecko(); target = mText; } Object ret; try { ret = method.invoke(target, args); } catch (InvocationTargetException e) { // Bug 817386 // Most likely Gecko has changed the text while GeckoInputConnection is // trying to access the text. If we pass through the exception here, Fennec // will crash due to a lack of exception handler. Log the exception and // return an empty value instead. if (!(e.getCause() instanceof IndexOutOfBoundsException)) { // Only handle IndexOutOfBoundsException for now, // as other exceptions might signal other bugs throw e; } Log.w(LOGTAG, "Exception in GeckoEditable." + method.getName(), e.getCause()); Class<?> retClass = method.getReturnType(); if (retClass == Character.TYPE) { ret = '\0'; } else if (retClass == Integer.TYPE) { ret = 0; } else if (retClass == String.class) { ret = ""; } else { ret = null; } } if (DEBUG) { StringBuilder log = new StringBuilder(method.getName()); log.append("("); if (args != null) { for (Object arg : args) { debugAppend(log, arg).append(", "); } if (args.length > 0) { log.setLength(log.length() - 2); } } if (method.getReturnType().equals(Void.TYPE)) { log.append(")"); } else { debugAppend(log.append(") = "), ret); } Log.d(LOGTAG, log.toString()); } return ret; }