/** @hide */
  void dispatchTrackballEvent(
      Context context, int seq, MotionEvent motion, IInputMethodCallback callback) {
    synchronized (mH) {
      if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");

      if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
        try {
          callback.finishedEvent(seq, false);
        } catch (RemoteException e) {
        }
        return;
      }

      try {
        if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
        mCurMethod.dispatchTrackballEvent(seq, motion, callback);
      } catch (RemoteException e) {
        Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
        try {
          callback.finishedEvent(seq, false);
        } catch (RemoteException ex) {
        }
      }
    }
  }
  /** @hide */
  public void dispatchKeyEvent(
      Context context, int seq, KeyEvent key, IInputMethodCallback callback) {
    synchronized (mH) {
      if (DEBUG) Log.d(TAG, "dispatchKeyEvent");

      if (mCurMethod == null) {
        try {
          callback.finishedEvent(seq, false);
        } catch (RemoteException e) {
        }
        return;
      }

      if (key.getAction() == KeyEvent.ACTION_DOWN && key.getKeyCode() == KeyEvent.KEYCODE_SYM) {
        showInputMethodPicker();
        try {
          callback.finishedEvent(seq, true);
        } catch (RemoteException e) {
        }
        return;
      }
      try {
        if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod);
        mCurMethod.dispatchKeyEvent(seq, key, callback);
      } catch (RemoteException e) {
        Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
        try {
          callback.finishedEvent(seq, false);
        } catch (RemoteException ex) {
        }
      }
    }
  }
  /** Report the current selection range. */
  public void updateSelection(
      View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd) {
    checkFocus();
    synchronized (mH) {
      if ((mServedView != view
              && (mServedView == null || !mServedView.checkInputConnectionProxy(view)))
          || mCurrentTextBoxAttribute == null
          || mCurMethod == null) {
        return;
      }

      if (mCursorSelStart != selStart
          || mCursorSelEnd != selEnd
          || mCursorCandStart != candidatesStart
          || mCursorCandEnd != candidatesEnd) {
        if (DEBUG) Log.d(TAG, "updateSelection");

        try {
          if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod);
          mCurMethod.updateSelection(
              mCursorSelStart, mCursorSelEnd, selStart, selEnd, candidatesStart, candidatesEnd);
          mCursorSelStart = selStart;
          mCursorSelEnd = selEnd;
          mCursorCandStart = candidatesStart;
          mCursorCandEnd = candidatesEnd;
        } catch (RemoteException e) {
          Log.w(TAG, "IME died: " + mCurId, e);
        }
      }
    }
  }
 /*
  * This method toggles the input method window display.
  * If the input window is already displayed, it gets hidden.
  * If not the input window will be displayed.
  * @param showFlags Provides additional operating flags.  May be
  * 0 or have the {@link #SHOW_IMPLICIT},
  * {@link #SHOW_FORCED} bit set.
  * @param hideFlags Provides additional operating flags.  May be
  * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
  * {@link #HIDE_NOT_ALWAYS} bit set.
  * @hide
  */
 public void toggleSoftInput(int showFlags, int hideFlags) {
   if (mCurMethod != null) {
     try {
       mCurMethod.toggleSoftInput(showFlags, hideFlags);
     } catch (RemoteException e) {
     }
   }
 }
 /**
  * This method toggles the input method window display. If the input window is already displayed,
  * it gets hidden. If not the input window will be displayed.
  *
  * @param windowToken The token of the window that is making the request, as returned by {@link
  *     View#getWindowToken() View.getWindowToken()}.
  * @param showFlags Provides additional operating flags. May be 0 or have the {@link
  *     #SHOW_IMPLICIT}, {@link #SHOW_FORCED} bit set.
  * @param hideFlags Provides additional operating flags. May be 0 or have the {@link
  *     #HIDE_IMPLICIT_ONLY}, {@link #HIDE_NOT_ALWAYS} bit set.
  */
 public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
   synchronized (mH) {
     if (mServedView == null || mServedView.getWindowToken() != windowToken) {
       return;
     }
     if (mCurMethod != null) {
       try {
         mCurMethod.toggleSoftInput(showFlags, hideFlags);
       } catch (RemoteException e) {
       }
     }
   }
 }
  public void updateExtractedText(View view, int token, ExtractedText text) {
    checkFocus();
    synchronized (mH) {
      if (mServedView != view
          && (mServedView == null || !mServedView.checkInputConnectionProxy(view))) {
        return;
      }

      if (mCurMethod != null) {
        try {
          mCurMethod.updateExtractedText(token, text);
        } catch (RemoteException e) {
        }
      }
    }
  }
  public void displayCompletions(View view, CompletionInfo[] completions) {
    checkFocus();
    synchronized (mH) {
      if (mServedView != view
          && (mServedView == null || !mServedView.checkInputConnectionProxy(view))) {
        return;
      }

      mCompletions = completions;
      if (mCurMethod != null) {
        try {
          mCurMethod.displayCompletions(mCompletions);
        } catch (RemoteException e) {
        }
      }
    }
  }
 /**
  * Call {@link InputMethodSession#appPrivateCommand(String, Bundle)
  * InputMethodSession.appPrivateCommand()} on the current Input Method.
  *
  * @param view Optional View that is sending the command, or null if you want to send the command
  *     regardless of the view that is attached to the input method.
  * @param action Name of the command to be performed. This <em>must</em> be a scoped name, i.e.
  *     prefixed with a package name you own, so that different developers will not create
  *     conflicting commands.
  * @param data Any data to include with the command.
  */
 public void sendAppPrivateCommand(View view, String action, Bundle data) {
   checkFocus();
   synchronized (mH) {
     if ((mServedView != view
             && (mServedView == null || !mServedView.checkInputConnectionProxy(view)))
         || mCurrentTextBoxAttribute == null
         || mCurMethod == null) {
       return;
     }
     try {
       if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
       mCurMethod.appPrivateCommand(action, data);
     } catch (RemoteException e) {
       Log.w(TAG, "IME died: " + mCurId, e);
     }
   }
 }
  /** Report the current cursor location in its window. */
  public void updateCursor(View view, int left, int top, int right, int bottom) {
    checkFocus();
    synchronized (mH) {
      if ((mServedView != view
              && (mServedView == null || !mServedView.checkInputConnectionProxy(view)))
          || mCurrentTextBoxAttribute == null
          || mCurMethod == null) {
        return;
      }

      mTmpCursorRect.set(left, top, right, bottom);
      if (!mCursorRect.equals(mTmpCursorRect)) {
        if (DEBUG) Log.d(TAG, "updateCursor");

        try {
          if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
          mCurMethod.updateCursor(mTmpCursorRect);
          mCursorRect.set(mTmpCursorRect);
        } catch (RemoteException e) {
          Log.w(TAG, "IME died: " + mCurId, e);
        }
      }
    }
  }
  void startInputInner() {
    final View view;
    synchronized (mH) {
      view = mServedView;

      // Make sure we have a window token for the served view.
      if (DEBUG) Log.v(TAG, "Starting input: view=" + view);
      if (view == null) {
        if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
        return;
      }
    }

    // Now we need to get an input connection from the served view.
    // This is complicated in a couple ways: we can't be holding our lock
    // when calling out to the view, and we need to make sure we call into
    // the view on the same thread that is driving its view hierarchy.
    Handler vh = view.getHandler();
    if (vh == null) {
      // If the view doesn't have a handler, something has changed out
      // from under us, so just bail.
      if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!");
      return;
    }
    if (vh.getLooper() != Looper.myLooper()) {
      // The view is running on a different thread than our own, so
      // we need to reschedule our work for over there.
      if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
      vh.post(
          new Runnable() {
            public void run() {
              startInputInner();
            }
          });
      return;
    }

    // Okay we are now ready to call into the served view and have it
    // do its stuff.
    // Life is good: let's hook everything up!
    EditorInfo tba = new EditorInfo();
    tba.packageName = view.getContext().getPackageName();
    tba.fieldId = view.getId();
    InputConnection ic = view.onCreateInputConnection(tba);
    if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);

    synchronized (mH) {
      // Now that we are locked again, validate that our state hasn't
      // changed.
      if (mServedView != view || !mServedConnecting) {
        // Something else happened, so abort.
        if (DEBUG)
          Log.v(
              TAG,
              "Starting input: finished by someone else (view="
                  + mServedView
                  + " conn="
                  + mServedConnecting
                  + ")");
        return;
      }

      // If we already have a text box, then this view is already
      // connected so we want to restart it.
      final boolean initial = mCurrentTextBoxAttribute == null;

      // Hook 'em up and let 'er rip.
      mCurrentTextBoxAttribute = tba;
      mServedConnecting = false;
      mServedInputConnection = ic;
      IInputContext servedContext;
      if (ic != null) {
        mCursorSelStart = tba.initialSelStart;
        mCursorSelEnd = tba.initialSelEnd;
        mCursorCandStart = -1;
        mCursorCandEnd = -1;
        mCursorRect.setEmpty();
        servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic);
      } else {
        servedContext = null;
      }

      try {
        if (DEBUG)
          Log.v(TAG, "START INPUT: " + view + " ic=" + ic + " tba=" + tba + " initial=" + initial);
        InputBindResult res =
            mService.startInput(mClient, servedContext, tba, initial, mCurMethod == null);
        if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
        if (res != null) {
          if (res.id != null) {
            mBindSequence = res.sequence;
            mCurMethod = res.method;
          } else {
            // This means there is no input method available.
            if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
            return;
          }
        }
        if (mCurMethod != null && mCompletions != null) {
          try {
            mCurMethod.displayCompletions(mCompletions);
          } catch (RemoteException e) {
          }
        }
      } catch (RemoteException e) {
        Log.w(TAG, "IME died: " + mCurId, e);
      }
    }
  }