@Override
  public boolean setSelection(int start, int end) {
    GeckoAppShell.sendEventToGecko(
        GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, start, end - start));

    return super.setSelection(start, end);
  }
  private boolean processKeyDown(int keyCode, KeyEvent event, boolean isPreIme) {
    if (DEBUG) {
      Log.d(
          LOGTAG,
          "IME: processKeyDown(keyCode=" + keyCode + ", event=" + event + ", " + isPreIme + ")");
    }

    clampSelection();

    switch (keyCode) {
      case KeyEvent.KEYCODE_MENU:
      case KeyEvent.KEYCODE_BACK:
      case KeyEvent.KEYCODE_VOLUME_UP:
      case KeyEvent.KEYCODE_VOLUME_DOWN:
      case KeyEvent.KEYCODE_SEARCH:
        return false;
      case KeyEvent.KEYCODE_DEL:
        // See comments in GeckoInputConnection.onKeyDel
        if (onKeyDel()) {
          return true;
        }
        break;
      case KeyEvent.KEYCODE_ENTER:
        if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
            && mIMEActionHint.equalsIgnoreCase("next"))
          event = new KeyEvent(event.getAction(), KeyEvent.KEYCODE_TAB);
        break;
      default:
        break;
    }

    if (isPreIme
        && mIMEState != IME_STATE_DISABLED
        && (event.getMetaState() & KeyEvent.META_ALT_ON) != 0)
      // Let active IME process pre-IME key events
      return false;

    View view = GeckoApp.mAppContext.getLayerController().getView();
    KeyListener keyListener = TextKeyListener.getInstance();

    // KeyListener returns true if it handled the event for us.
    if (mIMEState == IME_STATE_DISABLED
        || keyCode == KeyEvent.KEYCODE_ENTER
        || keyCode == KeyEvent.KEYCODE_DEL
        || keyCode == KeyEvent.KEYCODE_TAB
        || (event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0
        || !keyListener.onKeyDown(view, mEditable, keyCode, event)) {
      // Make sure selection in Gecko is up-to-date
      final Editable content = getEditable();
      int a = Selection.getSelectionStart(content);
      int b = Selection.getSelectionEnd(content);
      GeckoAppShell.sendEventToGecko(
          GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, a, b - a));

      GeckoAppShell.sendEventToGecko(GeckoEvent.createKeyEvent(event));
    }
    return true;
  }
  @Override
  public boolean performContextMenuAction(int id) {
    final Editable content = getEditable();
    if (content == null) return false;

    String text = content.toString();

    clampSelection();
    int a = Selection.getSelectionStart(content);
    int b = Selection.getSelectionEnd(content);

    switch (id) {
      case R.id.selectAll:
        setSelection(0, text.length());
        break;
      case R.id.cut:
        // Fill the clipboard
        GeckoAppShell.setClipboardText(text);
        // If selection is empty, we'll select everything
        if (a >= b)
          GeckoAppShell.sendEventToGecko(
              GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, 0, text.length()));
        GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(GeckoEvent.IME_DELETE_TEXT, 0, 0));
        break;
      case R.id.paste:
        commitText(GeckoAppShell.getClipboardText(), 1);
        break;
      case R.id.copy:
        // If there is no selection set, we must be doing "Copy All",
        // otherwise get the selection
        if (a < b) text = text.substring(a, b);
        GeckoAppShell.setClipboardText(text.substring(a, b));
        break;
    }
    return true;
  }
 private void endComposition() {
   if (DEBUG) Log.d(LOGTAG, "IME: endComposition: IME_COMPOSITION_END");
   GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0));
   mCompositionStart = NO_COMPOSITION_STRING;
 }
  // TextWatcher
  public void onTextChanged(CharSequence s, int start, int before, int count) {
    if (hasCompositionString() && mCompositionStart != start) {
      // Changed range is different from the composition, need to reset the composition
      endComposition();
    }

    CharSequence changedText = s.subSequence(start, start + count);
    if (changedText.length() == 1) {
      char changedChar = changedText.charAt(0);

      // Some IMEs (e.g. SwiftKey X) send a string with '\n' when Enter is pressed
      // Such string cannot be handled by Gecko, so we convert it to a key press instead
      if (changedChar == '\n') {
        processKeyDown(
            KeyEvent.KEYCODE_ENTER,
            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER),
            false);
        processKeyUp(
            KeyEvent.KEYCODE_ENTER,
            new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER),
            false);
        return;
      }

      // If we are committing a single character and didn't have an active composition string,
      // we can send Gecko keydown/keyup events instead of composition events.
      if (mCommittingText && !hasCompositionString() && synthesizeKeyEvents(changedChar)) {
        // Block this thread until all pending events are processed
        GeckoAppShell.geckoEventSync();
        return;
      }
    }

    if (!hasCompositionString()) {
      if (DEBUG) Log.d(LOGTAG, ". . . onTextChanged: IME_COMPOSITION_BEGIN");
      GeckoAppShell.sendEventToGecko(
          GeckoEvent.createIMEEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0));
      mCompositionStart = start;

      if (DEBUG) {
        Log.d(LOGTAG, ". . . onTextChanged: IME_SET_SELECTION, start=" + start + ", len=" + before);
      }

      GeckoAppShell.sendEventToGecko(
          GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, start, before));
    }

    sendTextToGecko(changedText, start + count);

    if (DEBUG) {
      Log.d(LOGTAG, ". . . onTextChanged: IME_SET_SELECTION, start=" + (start + count) + ", 0");
    }

    GeckoAppShell.sendEventToGecko(
        GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION, start + count, 0));

    // End composition if all characters in the word have been deleted.
    // This fixes autocomplete results not appearing.
    if (count == 0) endComposition();

    // Block this thread until all pending events are processed
    GeckoAppShell.geckoEventSync();
  }