/** @see BaseInputConnection#performContextMenuAction(int) */
 @Override
 public boolean performContextMenuAction(int id) {
   if (DEBUG) Log.w(TAG, "performContextMenuAction [" + id + "]");
   switch (id) {
     case android.R.id.selectAll:
       return mImeAdapter.selectAll();
     case android.R.id.cut:
       return mImeAdapter.cut();
     case android.R.id.copy:
       return mImeAdapter.copy();
     case android.R.id.paste:
       return mImeAdapter.paste();
     default:
       return false;
   }
 }
  /** @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);
  }
 /** @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#performEditorAction(int) */
 @Override
 public boolean performEditorAction(int actionCode) {
   if (DEBUG) Log.w(TAG, "performEditorAction [" + actionCode + "]");
   if (actionCode == EditorInfo.IME_ACTION_NEXT) {
     restartInput();
     // Send TAB key event
     long timeStampMs = SystemClock.uptimeMillis();
     mImeAdapter.sendSyntheticKeyEvent(
         WebInputEventType.RawKeyDown, timeStampMs, KeyEvent.KEYCODE_TAB, 0, 0);
   } else {
     mImeAdapter.sendKeyEventWithKeyCode(
         KeyEvent.KEYCODE_ENTER,
         KeyEvent.FLAG_SOFT_KEYBOARD
             | KeyEvent.FLAG_KEEP_TOUCH_MODE
             | KeyEvent.FLAG_EDITOR_ACTION);
   }
   return true;
 }
 /** @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);
 }
  /** @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;
  }
示例#7
0
  @SmallTest
  @Feature({"TextInput", "Main"})
  public void testGuessedKeyCodeFromTyping() throws Throwable {
    assertEquals(-1, getTypedKeycodeGuess(null, ""));
    assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess(null, "x"));
    assertEquals(-1, getTypedKeycodeGuess(null, "xyz"));

    assertEquals(-1, getTypedKeycodeGuess("abc", "abc"));
    assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("abc", ""));

    assertEquals(KeyEvent.KEYCODE_H, getTypedKeycodeGuess("", "h"));
    assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("h", ""));
    assertEquals(KeyEvent.KEYCODE_E, getTypedKeycodeGuess("h", "he"));
    assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("he", "hel"));
    assertEquals(KeyEvent.KEYCODE_O, getTypedKeycodeGuess("hel", "helo"));
    assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("helo", "hel"));
    assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("hel", "hell"));
    assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("hell", "helll"));
    assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("helll", "hell"));
    assertEquals(KeyEvent.KEYCODE_O, getTypedKeycodeGuess("hell", "hello"));

    assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess("xxx", "xxxx"));
    assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess("xxx", "xxxxx"));
    assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("xxx", "xx"));
    assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("xxx", "x"));

    assertEquals(KeyEvent.KEYCODE_Y, getTypedKeycodeGuess("xxx", "xxxy"));
    assertEquals(KeyEvent.KEYCODE_Y, getTypedKeycodeGuess("xxx", "xxxxy"));
    assertEquals(-1, getTypedKeycodeGuess("xxx", "xy"));
    assertEquals(-1, getTypedKeycodeGuess("xxx", "y"));

    assertEquals(-1, getTypedKeycodeGuess("foo", "bar"));
    assertEquals(-1, getTypedKeycodeGuess("foo", "bars"));
    assertEquals(-1, getTypedKeycodeGuess("foo", "ba"));

    // Some characters also require modifiers so we have to check the full event.
    KeyEvent ev = ImeAdapter.getTypedKeyEventGuess(null, "!");
    assertEquals(KeyEvent.KEYCODE_1, ev.getKeyCode());
    assertTrue(ev.isShiftPressed());
  }
示例#8
0
 private int getTypedKeycodeGuess(String before, String after) {
   KeyEvent ev = ImeAdapter.getTypedKeyEventGuess(before, after);
   if (ev == null) return -1;
   return ev.getKeyCode();
 }
 private InputMethodManagerWrapper getInputMethodManagerWrapper() {
   return mImeAdapter.getInputMethodManagerWrapper();
 }
  @VisibleForTesting
  AdapterInputConnection(View view, ImeAdapter imeAdapter, Editable editable, EditorInfo outAttrs) {
    super(view, true);
    mInternalView = view;
    mImeAdapter = imeAdapter;
    mImeAdapter.setInputConnection(this);
    mEditable = editable;
    // The editable passed in might have been in use by a prior keyboard and could have had
    // prior composition spans set.  To avoid keyboard conflicts, remove all composing spans
    // when taking ownership of an existing Editable.
    removeComposingSpans(mEditable);
    mSingleLine = true;
    outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
    outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;

    int inputType = imeAdapter.getTextInputType();
    int inputFlags = imeAdapter.getTextInputFlags();
    if ((inputFlags & WebTextInputFlags.AutocompleteOff) != 0) {
      outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
    }

    if (inputType == TextInputType.TEXT) {
      // Normal text field
      outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
      if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) {
        outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
      }
    } else if (inputType == TextInputType.TEXT_AREA
        || inputType == TextInputType.CONTENT_EDITABLE) {
      outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
      if ((inputFlags & WebTextInputFlags.AutocorrectOff) == 0) {
        outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
      }
      outAttrs.imeOptions |= EditorInfo.IME_ACTION_NONE;
      mSingleLine = false;
    } else if (inputType == TextInputType.PASSWORD) {
      // Password
      outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
      outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
    } else if (inputType == TextInputType.SEARCH) {
      // Search
      outAttrs.imeOptions |= EditorInfo.IME_ACTION_SEARCH;
    } else if (inputType == TextInputType.URL) {
      // Url
      outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI;
      outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
    } else if (inputType == TextInputType.EMAIL) {
      // Email
      outAttrs.inputType =
          InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
      outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO;
    } else if (inputType == TextInputType.TELEPHONE) {
      // Telephone
      // Number and telephone do not have both a Tab key and an
      // action in default OSK, so set the action to NEXT
      outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
      outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
    } else if (inputType == TextInputType.NUMBER) {
      // Number
      outAttrs.inputType =
          InputType.TYPE_CLASS_NUMBER
              | InputType.TYPE_NUMBER_VARIATION_NORMAL
              | InputType.TYPE_NUMBER_FLAG_DECIMAL;
      outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
    }

    // Handling of autocapitalize. Blink will send the flag taking into account the element's
    // type. This is not using AutocapitalizeNone because Android does not autocapitalize by
    // default and there is no way to express no capitalization.
    // Autocapitalize is meant as a hint to the virtual keyboard.
    if ((inputFlags & WebTextInputFlags.AutocapitalizeCharacters) != 0) {
      outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
    } else if ((inputFlags & WebTextInputFlags.AutocapitalizeWords) != 0) {
      outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
    } else if ((inputFlags & WebTextInputFlags.AutocapitalizeSentences) != 0) {
      outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
    }
    // Content editable doesn't use autocapitalize so we need to set it manually.
    if (inputType == TextInputType.CONTENT_EDITABLE) {
      outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
    }

    outAttrs.initialSelStart = Selection.getSelectionStart(mEditable);
    outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);
    mLastUpdateSelectionStart = outAttrs.initialSelStart;
    mLastUpdateSelectionEnd = outAttrs.initialSelEnd;

    Selection.setSelection(mEditable, outAttrs.initialSelStart, outAttrs.initialSelEnd);
    updateSelectionIfRequired();
  }
  /** @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) */
  @Override
  public boolean sendKeyEvent(KeyEvent event) {
    if (DEBUG) {
      Log.w(
          TAG,
          "sendKeyEvent ["
              + event.getAction()
              + "] ["
              + event.getKeyCode()
              + "] ["
              + event.getUnicodeChar()
              + "]");
    }

    int action = event.getAction();
    int keycode = event.getKeyCode();
    int unicodeChar = event.getUnicodeChar();

    // If this isn't a KeyDown event, no need to update composition state; just pass the key
    // event through and return. But note that some keys, such as enter, may actually be
    // handled on ACTION_UP in Blink.
    if (action != KeyEvent.ACTION_DOWN) {
      mImeAdapter.translateAndSendNativeEvents(event);
      return true;
    }

    // If this is backspace/del or if the key has a character representation,
    // need to update the underlying Editable (i.e. the local representation of the text
    // being edited).  Some IMEs like Jellybean stock IME and Samsung IME mix in delete
    // KeyPress events instead of calling deleteSurroundingText.
    if (keycode == KeyEvent.KEYCODE_DEL) {
      deleteSurroundingTextImpl(1, 0, true);
    } else if (keycode == KeyEvent.KEYCODE_FORWARD_DEL) {
      deleteSurroundingTextImpl(0, 1, true);
    } else if (keycode == KeyEvent.KEYCODE_ENTER) {
      // Finish text composition when pressing enter, as that may submit a form field.
      // TODO(aurimas): remove this workaround when crbug.com/278584 is fixed.
      finishComposingText();
    } else if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) != 0) {
      // Store a pending accent character and make it the current composition.
      int pendingAccent = unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK;
      StringBuilder builder = new StringBuilder();
      builder.appendCodePoint(pendingAccent);
      setComposingText(builder.toString(), 1);
      mPendingAccent = pendingAccent;
      return true;
    } else if (mPendingAccent != 0 && unicodeChar != 0) {
      int combined = KeyEvent.getDeadChar(mPendingAccent, unicodeChar);
      if (combined != 0) {
        StringBuilder builder = new StringBuilder();
        builder.appendCodePoint(combined);
        commitText(builder.toString(), 1);
        return true;
      }
      // Noncombinable character; commit the accent character and fall through to sending
      // the key event for the character afterwards.
      finishComposingText();
    }
    replaceSelectionWithUnicodeChar(unicodeChar);
    mImeAdapter.translateAndSendNativeEvents(event);
    return true;
  }
  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;
  }