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