// 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();
  }