/** @see BaseInputConnection#setComposingRegion(int, int) */ @Override public boolean setComposingRegion(int start, int end) { if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]"); int textLength = mEditable.length(); int a = Math.min(start, end); int b = Math.max(start, end); if (a < 0) a = 0; if (b < 0) b = 0; if (a > textLength) a = textLength; if (b > textLength) b = textLength; CharSequence regionText = null; if (a == b) { removeComposingSpans(mEditable); } else { if (a == 0 && b == mEditable.length()) { regionText = mEditable.subSequence(a, b); // If setting composing region that matches, at least in length, of the entire // editable region then check it for image placeholders. If any are found, // don't continue this operation. // This fixes the problem where, on Android 4.3, pasting an image is followed // by setting the composing region which then causes the image to be deleted. // http://crbug.com/466755 for (int i = a; i < b; ++i) { if (regionText.charAt(i) == '\uFFFC') return true; } } super.setComposingRegion(a, b); } updateSelectionIfRequired(); return mImeAdapter.setComposingRegion(regionText, a, b); }
@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 }
/** @see BaseInputConnection#setSelection(int, int) */ @Override public boolean setSelection(int start, int end) { if (DEBUG) Log.w(TAG, "setSelection [" + start + " " + end + "]"); int textLength = mEditable.length(); if (start < 0 || end < 0 || start > textLength || end > textLength) return true; super.setSelection(start, end); updateSelectionIfRequired(); return mImeAdapter.setEditableSelectionOffsets(start, end); }
/** @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) */ @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPosition + "]"); if (maybePerformEmptyCompositionWorkaround(text)) return true; mPendingAccent = 0; super.setComposingText(text, newCursorPosition); updateSelectionIfRequired(); return mImeAdapter.checkCompositionQueueAndCallNative(text, newCursorPosition, false); }
/** * Updates the AdapterInputConnection's internal representation of the text being edited and its * selection and composition properties. The resulting Editable is accessible through the * getEditable() method. If the text has not changed, this also calls updateSelection on the * InputMethodManager. * * @param text The String contents of the field being edited. * @param selectionStart The character offset of the selection start, or the caret position if * there is no selection. * @param selectionEnd The character offset of the selection end, or the caret position if there * is no selection. * @param compositionStart The character offset of the composition start, or -1 if there is no * composition. * @param compositionEnd The character offset of the composition end, or -1 if there is no * selection. * @param isNonImeChange True when the update was caused by non-IME (e.g. Javascript). */ @VisibleForTesting public void updateState( String text, int selectionStart, int selectionEnd, int compositionStart, int compositionEnd, boolean isNonImeChange) { if (DEBUG) { Log.w( TAG, "updateState [" + text + "] [" + selectionStart + " " + selectionEnd + "] [" + compositionStart + " " + compositionEnd + "] [" + isNonImeChange + "]"); } // If this update is from the IME, no further state modification is necessary because the // state should have been updated already by the IM framework directly. if (!isNonImeChange) return; // Non-breaking spaces can cause the IME to get confused. Replace with normal spaces. text = text.replace('\u00A0', ' '); selectionStart = Math.min(selectionStart, text.length()); selectionEnd = Math.min(selectionEnd, text.length()); compositionStart = Math.min(compositionStart, text.length()); compositionEnd = Math.min(compositionEnd, text.length()); String prevText = mEditable.toString(); boolean textUnchanged = prevText.equals(text); if (!textUnchanged) { mEditable.replace(0, mEditable.length(), text); } Selection.setSelection(mEditable, selectionStart, selectionEnd); if (compositionStart == compositionEnd) { removeComposingSpans(mEditable); } else { super.setComposingRegion(compositionStart, compositionEnd); } updateSelectionIfRequired(); }
public void testViaBackButtonOnLayout() { testView.loadUrl("file:///android_asset/www/backbuttonmultipage/sample2.html"); sleep(); String url = mUiThread.getUrl(); assertTrue(url.endsWith("sample2.html")); testView.loadUrl("file:///android_asset/www/backbuttonmultipage/sample3.html"); sleep(); url = mUiThread.getUrl(); assertTrue(url.endsWith("sample3.html")); BaseInputConnection viewConnection = new BaseInputConnection(containerView, true); KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); viewConnection.sendKeyEvent(backDown); viewConnection.sendKeyEvent(backUp); sleep(); url = mUiThread.getUrl(); assertTrue(url.endsWith("sample2.html")); viewConnection.sendKeyEvent(backDown); viewConnection.sendKeyEvent(backUp); sleep(); url = mUiThread.getUrl(); assertTrue(url.endsWith("index.html")); }
/** @see BaseInputConnection#finishComposingText() */ @Override public boolean finishComposingText() { if (DEBUG) Log.w(TAG, "finishComposingText"); mPendingAccent = 0; if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable)) { return true; } super.finishComposingText(); updateSelectionIfRequired(); mImeAdapter.finishComposingText(); return true; }
public void notifySelectionChange(InputMethodManager imm, int start, int end) { if (!mBatchMode) { final Editable content = getEditable(); start = clampContentIndex(content, start); end = clampContentIndex(content, end); clampSelection(); int a = Selection.getSelectionStart(content); int b = Selection.getSelectionEnd(content); if (start != a || end != b) { if (DEBUG) { Log.d( LOGTAG, String.format( ". . . notifySelectionChange: current editable selection: [%d, %d]", a, b)); } super.setSelection(start, end); // Check if the selection is inside composing span int ca = getComposingSpanStart(content); int cb = getComposingSpanEnd(content); if (cb < ca) { int tmp = ca; ca = cb; cb = tmp; } if (start < ca || start > cb || end < ca || end > cb) { if (DEBUG) Log.d(LOGTAG, ". . . notifySelectionChange: removeComposingSpans"); removeComposingSpans(content); } } } if (imm != null && imm.isFullscreenMode()) { View v = GeckoApp.mAppContext.getLayerController().getView(); imm.updateSelection(v, start, end, -1, -1); } }
private boolean deleteSurroundingTextImpl( int beforeLength, int afterLength, boolean fromPhysicalKey) { if (DEBUG) { Log.w( TAG, "deleteSurroundingText [" + beforeLength + " " + afterLength + " " + fromPhysicalKey + "]"); } if (mPendingAccent != 0) { finishComposingText(); } int originalBeforeLength = beforeLength; int originalAfterLength = afterLength; int selectionStart = Selection.getSelectionStart(mEditable); int selectionEnd = Selection.getSelectionEnd(mEditable); int availableBefore = selectionStart; int availableAfter = mEditable.length() - selectionEnd; beforeLength = Math.min(beforeLength, availableBefore); afterLength = Math.min(afterLength, availableAfter); // Adjust these values even before calling super.deleteSurroundingText() to be consistent // with the super class. if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionStart - beforeLength)) { beforeLength += 1; } if (isIndexBetweenUtf16SurrogatePair(mEditable, selectionEnd + afterLength)) { afterLength += 1; } super.deleteSurroundingText(beforeLength, afterLength); updateSelectionIfRequired(); // If this was called due to a physical key, no need to generate a key event here as // the caller will take care of forwarding the original. if (fromPhysicalKey) { return true; } // For single-char deletion calls |ImeAdapter.sendKeyEventWithKeyCode| with the real key // code. For multi-character deletion, executes deletion by calling // |ImeAdapter.deleteSurroundingText| and sends synthetic key events with a dummy key code. int keyCode = KeyEvent.KEYCODE_UNKNOWN; if (originalBeforeLength == 1 && originalAfterLength == 0) { keyCode = KeyEvent.KEYCODE_DEL; } else if (originalBeforeLength == 0 && originalAfterLength == 1) { keyCode = KeyEvent.KEYCODE_FORWARD_DEL; } boolean result = true; if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { result = mImeAdapter.sendSyntheticKeyEvent( WebInputEventType.RawKeyDown, SystemClock.uptimeMillis(), keyCode, 0, 0); result &= mImeAdapter.deleteSurroundingText(beforeLength, afterLength); result &= mImeAdapter.sendSyntheticKeyEvent( WebInputEventType.KeyUp, SystemClock.uptimeMillis(), keyCode, 0, 0); } else { mImeAdapter.sendKeyEventWithKeyCode( keyCode, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE); } return result; }