InputMethodManager.java revision f1e484acb594a726fb57ad0ae4cfe902c7f35858
1/*
2 * Copyright (C) 2007-2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package android.view.inputmethod;
18
19import android.content.Context;
20import android.graphics.Rect;
21import android.os.Bundle;
22import android.os.Handler;
23import android.os.IBinder;
24import android.os.Looper;
25import android.os.Message;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.util.Log;
29import android.view.KeyEvent;
30import android.view.MotionEvent;
31import android.view.View;
32import android.view.ViewRoot;
33
34import com.android.internal.view.IInputConnectionWrapper;
35import com.android.internal.view.IInputContext;
36import com.android.internal.view.IInputMethodCallback;
37import com.android.internal.view.IInputMethodClient;
38import com.android.internal.view.IInputMethodManager;
39import com.android.internal.view.IInputMethodSession;
40import com.android.internal.view.InputBindResult;
41
42import java.util.List;
43
44/**
45 * Central system API to the overall input method framework (IMF) architecture,
46 * which arbitrates interaction between applications and the current input method.
47 * You can retrieve an instance of this interface with
48 * {@link Context#getSystemService(String) Context.getSystemService()}.
49 *
50 * <p>Topics covered here:
51 * <ol>
52 * <li><a href="#ArchitectureOverview">Architecture Overview</a>
53 * </ol>
54 *
55 * <a name="ArchitectureOverview"></a>
56 * <h3>Architecture Overview</h3>
57 *
58 * <p>There are three primary parties involved in the input method
59 * framework (IMF) architecture:</p>
60 *
61 * <ul>
62 * <li> The <strong>input method manager</strong> as expressed by this class
63 * is the central point of the system that manages interaction between all
64 * other parts.  It is expressed as the client-side API here which exists
65 * in each application context and communicates with a global system service
66 * that manages the interaction across all processes.
67 * <li> An <strong>input method (IME)</strong> implements a particular
68 * interaction model allowing the user to generate text.  The system binds
69 * to the current input method that is use, causing it to be created and run,
70 * and tells it when to hide and show its UI.  Only one IME is running at a time.
71 * <li> Multiple <strong>client applications</strong> arbitrate with the input
72 * method manager for input focus and control over the state of the IME.  Only
73 * one such client is ever active (working with the IME) at a time.
74 * </ul>
75 *
76 *
77 * <a name="Applications"></a>
78 * <h3>Applications</h3>
79 *
80 * <p>In most cases, applications that are using the standard
81 * {@link android.widget.TextView} or its subclasses will have little they need
82 * to do to work well with soft input methods.  The main things you need to
83 * be aware of are:</p>
84 *
85 * <ul>
86 * <li> Properly set the {@link android.R.attr#inputType} if your editable
87 * text views, so that the input method will have enough context to help the
88 * user in entering text into them.
89 * <li> Deal well with losing screen space when the input method is
90 * displayed.  Ideally an application should handle its window being resized
91 * smaller, but it can rely on the system performing panning of the window
92 * if needed.  You should set the {@link android.R.attr#windowSoftInputMode}
93 * attribute on your activity or the corresponding values on windows you
94 * create to help the system determine whether to pan or resize (it will
95 * try to determine this automatically but may get it wrong).
96 * <li> You can also control the preferred soft input state (open, closed, etc)
97 * for your window using the same {@link android.R.attr#windowSoftInputMode}
98 * attribute.
99 * </ul>
100 *
101 * <p>More finer-grained control is available through the APIs here to directly
102 * interact with the IMF and its IME -- either showing or hiding the input
103 * area, letting the user pick an input method, etc.</p>
104 *
105 * <p>For the rare people amongst us writing their own text editors, you
106 * will need to implement {@link android.view.View#onCreateInputConnection}
107 * to return a new instance of your own {@link InputConnection} interface
108 * allowing the IME to interact with your editor.</p>
109 *
110 *
111 * <a name="InputMethods"></a>
112 * <h3>Input Methods</h3>
113 *
114 * <p>An input method (IME) is implemented
115 * as a {@link android.app.Service}, typically deriving from
116 * {@link android.inputmethodservice.InputMethodService}.  It must provide
117 * the core {@link InputMethod} interface, though this is normally handled by
118 * {@link android.inputmethodservice.InputMethodService} and implementors will
119 * only need to deal with the higher-level API there.</p>
120 *
121 * See the {@link android.inputmethodservice.InputMethodService} class for
122 * more information on implementing IMEs.
123 *
124 *
125 * <a name="Security"></a>
126 * <h3>Security</h3>
127 *
128 * <p>There are a lot of security issues associated with input methods,
129 * since they essentially have freedom to completely drive the UI and monitor
130 * everything the user enters.  The Android input method framework also allows
131 * arbitrary third party IMEs, so care must be taken to restrict their
132 * selection and interactions.</p>
133 *
134 * <p>Here are some key points about the security architecture behind the
135 * IMF:</p>
136 *
137 * <ul>
138 * <li> <p>Only the system is allowed to directly access an IME's
139 * {@link InputMethod} interface, via the
140 * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission.  This is
141 * enforced in the system by not binding to an input method service that does
142 * not require this permission, so the system can guarantee no other untrusted
143 * clients are accessing the current input method outside of its control.</p>
144 *
145 * <li> <p>There may be many client processes of the IMF, but only one may
146 * be active at a time.  The inactive clients can not interact with key
147 * parts of the IMF through the mechanisms described below.</p>
148 *
149 * <li> <p>Clients of an input method are only given access to its
150 * {@link InputMethodSession} interface.  One instance of this interface is
151 * created for each client, and only calls from the session associated with
152 * the active client will be processed by the current IME.  This is enforced
153 * by {@link android.inputmethodservice.AbstractInputMethodService} for normal
154 * IMEs, but must be explicitly handled by an IME that is customizing the
155 * raw {@link InputMethodSession} implementation.</p>
156 *
157 * <li> <p>Only the active client's {@link InputConnection} will accept
158 * operations.  The IMF tells each client process whether it is active, and
159 * the framework enforces that in inactive processes calls on to the current
160 * InputConnection will be ignored.  This ensures that the current IME can
161 * only deliver events and text edits to the UI that the user sees as
162 * being in focus.</p>
163 *
164 * <li> <p>An IME can never interact with an {@link InputConnection} while
165 * the screen is off.  This is enforced by making all clients inactive while
166 * the screen is off, and prevents bad IMEs from driving the UI when the user
167 * can not be aware of its behavior.</p>
168 *
169 * <li> <p>A client application can ask that the system let the user pick a
170 * new IME, but can not programmatically switch to one itself.  This avoids
171 * malicious applications from switching the user to their own IME, which
172 * remains running when the user navigates away to another application.  An
173 * IME, on the other hand, <em>is</em> allowed to programmatically switch
174 * the system to another IME, since it already has full control of user
175 * input.</p>
176 *
177 * <li> <p>The user must explicitly enable a new IME in settings before
178 * they can switch to it, to confirm with the system that they know about it
179 * and want to make it available for use.</p>
180 * </ul>
181 */
182public final class InputMethodManager {
183    static final boolean DEBUG = false;
184    static final String TAG = "InputMethodManager";
185
186    /**
187     * The package name of the build-in input method.
188     * {@hide}
189     */
190    public static final String BUILDIN_INPUTMETHOD_PACKAGE = "android.text.inputmethod";
191
192    static final Object mInstanceSync = new Object();
193    static InputMethodManager mInstance;
194
195    final IInputMethodManager mService;
196    final Looper mMainLooper;
197
198    // For scheduling work on the main thread.  This also serves as our
199    // global lock.
200    final H mH;
201
202    // The currently active input connection.
203    final MutableInputConnectionWrapper mInputConnectionWrapper;
204    final IInputContext mIInputContext;
205
206    /**
207     * True if this input method client is active, initially false.
208     */
209    boolean mActive = false;
210
211    /**
212     * The current base input connection, used when mActive is true.
213     */
214    InputConnection mCurrentInputConnection;
215
216    // -----------------------------------------------------------
217
218    /**
219     * This is the view that should currently be served by an input method,
220     * regardless of the state of setting that up.
221     */
222    View mServedView;
223    /**
224     * For evaluating the state after a focus change, this is the view that
225     * had focus.
226     */
227    View mLastServedView;
228    /**
229     * This is set when we are in the process of connecting, to determine
230     * when we have actually finished.
231     */
232    boolean mServedConnecting;
233    /**
234     * This is non-null when we have connected the served view; it holds
235     * the attributes that were last retrieved from the served view and given
236     * to the input connection.
237     */
238    EditorInfo mCurrentTextBoxAttribute;
239    /**
240     * The InputConnection that was last retrieved from the served view.
241     */
242    InputConnection mServedInputConnection;
243    /**
244     * The completions that were last provided by the served view.
245     */
246    CompletionInfo[] mCompletions;
247
248    // Cursor position on the screen.
249    Rect mTmpCursorRect = new Rect();
250    Rect mCursorRect = new Rect();
251    int mCursorSelStart;
252    int mCursorSelEnd;
253    int mCursorCandStart;
254    int mCursorCandEnd;
255
256    // -----------------------------------------------------------
257
258    /**
259     * Sequence number of this binding, as returned by the server.
260     */
261    int mBindSequence = -1;
262    /**
263     * ID of the method we are bound to.
264     */
265    String mCurId;
266    /**
267     * The actual instance of the method to make calls on it.
268     */
269    IInputMethodSession mCurMethod;
270
271    // -----------------------------------------------------------
272
273    static final int MSG_CHECK_FOCUS = 1;
274
275    class H extends Handler {
276        H(Looper looper) {
277            super(looper);
278        }
279
280        @Override
281        public void handleMessage(Message msg) {
282            switch (msg.what) {
283                case MSG_CHECK_FOCUS:
284                    checkFocus();
285                    return;
286            }
287        }
288    }
289
290    static class NoOpInputConnection implements InputConnection {
291
292        public boolean clearMetaKeyStates(int states) {
293            return false;
294        }
295
296        public boolean beginBatchEdit() {
297            return false;
298        }
299
300        public boolean endBatchEdit() {
301            return false;
302        }
303
304        public boolean commitCompletion(CompletionInfo text) {
305            return false;
306        }
307
308        public boolean commitText(CharSequence text, int newCursorPosition) {
309            return false;
310        }
311
312        public boolean deleteSurroundingText(int leftLength, int rightLength) {
313            return false;
314        }
315
316        public int getCursorCapsMode(int reqModes) {
317            return 0;
318        }
319
320        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
321            return null;
322        }
323
324        public CharSequence getTextAfterCursor(int n) {
325            return null;
326        }
327
328        public CharSequence getTextBeforeCursor(int n) {
329            return null;
330        }
331
332        public boolean hideStatusIcon() {
333            return false;
334        }
335
336        public boolean performPrivateCommand(String action, Bundle data) {
337            return false;
338        }
339
340        public boolean sendKeyEvent(KeyEvent event) {
341            return false;
342        }
343
344        public boolean setComposingText(CharSequence text, int newCursorPosition) {
345            return false;
346        }
347
348        public boolean finishComposingText() {
349            return false;
350        }
351
352        public boolean showStatusIcon(String packageName, int resId) {
353            return false;
354        }
355    }
356
357    final NoOpInputConnection mNoOpInputConnection = new NoOpInputConnection();
358
359    final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
360        public void setUsingInputMethod(boolean state) {
361
362        }
363
364        public void onBindMethod(InputBindResult res) {
365            synchronized (mH) {
366                if (mBindSequence < 0 || mBindSequence != res.sequence) {
367                    Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
368                            + ", given seq=" + res.sequence);
369                    return;
370                }
371
372                mCurMethod = res.method;
373                mCurId = res.id;
374                mBindSequence = res.sequence;
375            }
376            startInputInner();
377        }
378
379        public void onUnbindMethod(int sequence) {
380            synchronized (mH) {
381                if (mBindSequence == sequence) {
382                    if (false) {
383                        // XXX the server has already unbound!
384                        if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
385                            try {
386                                mCurMethod.finishInput();
387                            } catch (RemoteException e) {
388                                Log.w(TAG, "IME died: " + mCurId, e);
389                            }
390                        }
391                    }
392                    clearBindingLocked();
393
394                    // If we were actively using the last input method, then
395                    // we would like to re-connect to the next input method.
396                    if (mServedView != null && mServedView.isFocused()) {
397                        mServedConnecting = true;
398                    }
399                }
400                startInputInner();
401            }
402        }
403
404        public void setActive(boolean active) {
405            mActive = active;
406            mInputConnectionWrapper.setBaseInputConnection(active
407                    ? mCurrentInputConnection : mNoOpInputConnection);
408        }
409    };
410
411    final InputConnection mDummyInputConnection = new BaseInputConnection(this) {
412        public boolean beginBatchEdit() {
413            return false;
414        }
415        public boolean endBatchEdit() {
416            return false;
417        }
418        public boolean commitText(CharSequence text, int newCursorPosition) {
419            return false;
420        }
421        public boolean commitCompletion(CompletionInfo text) {
422            return false;
423        }
424        public boolean deleteSurroundingText(int leftLength, int rightLength) {
425            return false;
426        }
427        public ExtractedText getExtractedText(ExtractedTextRequest request,
428                int flags) {
429            return null;
430        }
431        public CharSequence getTextAfterCursor(int n) {
432            return null;
433        }
434        public CharSequence getTextBeforeCursor(int n) {
435            return null;
436        }
437        public int getCursorCapsMode(int reqModes) {
438            return 0;
439        }
440        public boolean clearMetaKeyStates(int states) {
441            return false;
442        }
443        public boolean performPrivateCommand(String action, Bundle data) {
444            return false;
445        }
446        public boolean setComposingText(CharSequence text, int newCursorPosition) {
447            return false;
448        }
449        public boolean finishComposingText() {
450            return false;
451        }
452    };
453
454    InputMethodManager(IInputMethodManager service, Looper looper) {
455        mService = service;
456        mMainLooper = looper;
457        mH = new H(looper);
458        mInputConnectionWrapper = new MutableInputConnectionWrapper(mNoOpInputConnection);
459        mIInputContext = new IInputConnectionWrapper(looper,
460                mInputConnectionWrapper);
461        setCurrentInputConnection(mDummyInputConnection);
462
463        if (mInstance == null) {
464            mInstance = this;
465        }
466    }
467
468    /**
469     * Retrieve the global InputMethodManager instance, creating it if it
470     * doesn't already exist.
471     * @hide
472     */
473    static public InputMethodManager getInstance(Context context) {
474        synchronized (mInstanceSync) {
475            if (mInstance != null) {
476                return mInstance;
477            }
478            IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
479            IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
480            mInstance = new InputMethodManager(service, context.getMainLooper());
481        }
482        return mInstance;
483    }
484
485    /**
486     * Private optimization: retrieve the global InputMethodManager instance,
487     * if it exists.
488     * @hide
489     */
490    static public InputMethodManager peekInstance() {
491        return mInstance;
492    }
493
494    /** @hide */
495    public IInputMethodClient getClient() {
496        return mClient;
497    }
498
499    /** @hide */
500    public IInputContext getInputContext() {
501        return mIInputContext;
502    }
503
504    public List<InputMethodInfo> getInputMethodList() {
505        try {
506            return mService.getInputMethodList();
507        } catch (RemoteException e) {
508            throw new RuntimeException(e);
509        }
510    }
511
512    public List<InputMethodInfo> getEnabledInputMethodList() {
513        try {
514            return mService.getEnabledInputMethodList();
515        } catch (RemoteException e) {
516            throw new RuntimeException(e);
517        }
518    }
519
520    public void updateStatusIcon(int iconId, String iconPackage) {
521        try {
522            mService.updateStatusIcon(iconId, iconPackage);
523        } catch (RemoteException e) {
524            throw new RuntimeException(e);
525        }
526    }
527
528    /**
529     * Return true if the given view is the currently active view for the
530     * input method.
531     */
532    public boolean isActive(View view) {
533        synchronized (mH) {
534            return mServedView == view && mCurrentTextBoxAttribute != null;
535        }
536    }
537
538    /**
539     * Return true if any view is currently active in the input method.
540     */
541    public boolean isActive() {
542        synchronized (mH) {
543            return mServedView != null && mCurrentTextBoxAttribute != null;
544        }
545    }
546
547    /**
548     * Return true if the currently served view is accepting full text edits.
549     * If false, it has no input connection, so can only handle raw key events.
550     */
551    public boolean isAcceptingText() {
552        return mServedInputConnection != null;
553    }
554
555    /**
556     * Reset all of the state associated with being bound to an input method.
557     */
558    void clearBindingLocked() {
559        clearConnectionLocked();
560        mBindSequence = -1;
561        mCurId = null;
562        mCurMethod = null;
563    }
564
565    /**
566     * Record the desired input connection, but only set it if mActive is true.
567     */
568    void setCurrentInputConnection(InputConnection connection) {
569        mCurrentInputConnection = connection;
570        mInputConnectionWrapper.setBaseInputConnection(mActive
571                ? connection : mNoOpInputConnection);
572    }
573
574    /**
575     * Reset all of the state associated with a served view being connected
576     * to an input method
577     */
578    void clearConnectionLocked() {
579        mCurrentTextBoxAttribute = null;
580        mServedInputConnection = null;
581        setCurrentInputConnection(mDummyInputConnection);
582    }
583
584    /**
585     * Disconnect any existing input connection, clearing the served view.
586     */
587    void finishInputLocked() {
588        if (mServedView != null) {
589            if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView);
590            updateStatusIcon(0, null);
591
592            if (mCurrentTextBoxAttribute != null) {
593                try {
594                    mService.finishInput(mClient);
595                } catch (RemoteException e) {
596                }
597            }
598
599            if (mServedInputConnection != null) {
600                // We need to tell the previously served view that it is no
601                // longer the input target, so it can reset its state.  Schedule
602                // this call on its window's Handler so it will be on the correct
603                // thread and outside of our lock.
604                Handler vh = mServedView.getHandler();
605                if (vh != null) {
606                    // This will result in a call to reportFinishInputConnection()
607                    // below.
608                    vh.sendMessage(vh.obtainMessage(ViewRoot.FINISH_INPUT_CONNECTION,
609                            mServedInputConnection));
610                }
611            }
612
613            mServedView = null;
614            mCompletions = null;
615            mServedConnecting = false;
616            clearConnectionLocked();
617        }
618    }
619
620    /**
621     * Called from the FINISH_INPUT_CONNECTION message above.
622     * @hide
623     */
624    public void reportFinishInputConnection(InputConnection ic) {
625        if (mServedInputConnection != ic) {
626            ic.finishComposingText();
627        }
628    }
629
630    public void displayCompletions(View view, CompletionInfo[] completions) {
631        synchronized (mH) {
632            if (mServedView != view) {
633                return;
634            }
635
636            mCompletions = completions;
637            if (mCurMethod != null) {
638                try {
639                    mCurMethod.displayCompletions(mCompletions);
640                } catch (RemoteException e) {
641                }
642            }
643        }
644    }
645
646    public void updateExtractedText(View view, int token, ExtractedText text) {
647        synchronized (mH) {
648            if (mServedView != view) {
649                return;
650            }
651
652            if (mCurMethod != null) {
653                try {
654                    mCurMethod.updateExtractedText(token, text);
655                } catch (RemoteException e) {
656                }
657            }
658        }
659    }
660
661    /**
662     * Flag for {@link #showSoftInput} to indicate that the this is an implicit
663     * request to show the input window, not as the result of a direct request
664     * by the user.  The window may not be shown in this case.
665     */
666    public static final int SHOW_IMPLICIT = 0x0001;
667
668    /**
669     * Explicitly request that the current input method's soft input area be
670     * shown to the user, if needed.  Call this if the user interacts with
671     * your view in such a way that they have expressed they would like to
672     * start performing input into it.
673     *
674     * @param view The currently focused view, which would like to receive
675     * soft keyboard input.
676     * @param flags Provides additional operating flags.  Currently may be
677     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
678     */
679    public void showSoftInput(View view, int flags) {
680        synchronized (mH) {
681            if (mServedView != view) {
682                return;
683            }
684
685            try {
686                mService.showSoftInput(mClient, flags);
687            } catch (RemoteException e) {
688            }
689        }
690    }
691
692    /**
693     * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
694     * input window should only be hidden if it was not explicitly shown
695     * by the user.
696     */
697    public static final int HIDE_IMPLICIT_ONLY = 0x0001;
698
699    /**
700     * Request to hide the soft input window from the context of the window
701     * that is currently accepting input.  This should be called as a result
702     * of the user doing some actually than fairly explicitly requests to
703     * have the input window hidden.
704     *
705     * @param windowToken The token of the window that is making the request,
706     * as returned by {@link View#getWindowToken() View.getWindowToken()}.
707     * @param flags Provides additional operating flags.  Currently may be
708     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
709     */
710    public void hideSoftInputFromWindow(IBinder windowToken, int flags) {
711        synchronized (mH) {
712            if (mServedView == null || mServedView.getWindowToken() != windowToken) {
713                return;
714            }
715
716            try {
717                mService.hideSoftInput(mClient, flags);
718            } catch (RemoteException e) {
719            }
720        }
721    }
722
723    /**
724     * If the input method is currently connected to the given view,
725     * restart it with its new contents.  You should call this when the text
726     * within your view changes outside of the normal input method or key
727     * input flow, such as when an application calls TextView.setText().
728     *
729     * @param view The view whose text has changed.
730     */
731    public void restartInput(View view) {
732        synchronized (mH) {
733            if (mServedView != view) {
734                return;
735            }
736
737            mServedConnecting = true;
738        }
739
740        startInputInner();
741    }
742
743    void startInputInner() {
744        final View view;
745        synchronized (mH) {
746            view = mServedView;
747
748            // Make sure we have a window token for the served view.
749            if (DEBUG) Log.v(TAG, "Starting input: view=" + view);
750            if (view == null) {
751                if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
752                return;
753            }
754        }
755
756        // Now we need to get an input connection from the served view.
757        // This is complicated in a couple ways: we can't be holding our lock
758        // when calling out to the view, and we need to make sure we call into
759        // the view on the same thread that is driving its view hierarchy.
760        Handler vh = view.getHandler();
761        if (vh == null) {
762            // If the view doesn't have a handler, something has changed out
763            // from under us, so just bail.
764            if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!");
765            return;
766        }
767        if (vh.getLooper() != Looper.myLooper()) {
768            // The view is running on a different thread than our own, so
769            // we need to reschedule our work for over there.
770            if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
771            vh.post(new Runnable() {
772                public void run() {
773                    startInputInner();
774                }
775            });
776        }
777
778        // Okay we are now ready to call into the served view and have it
779        // do its stuff.
780        // Life is good: let's hook everything up!
781        EditorInfo tba = new EditorInfo();
782        InputConnection ic = view.onCreateInputConnection(tba);
783        if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
784
785        synchronized (mH) {
786            // Now that we are locked again, validate that our state hasn't
787            // changed.
788            if (mServedView != view || !mServedConnecting) {
789                // Something else happened, so abort.
790                if (DEBUG) Log.v(TAG,
791                        "Starting input: finished by someone else (view="
792                        + mServedView + " conn=" + mServedConnecting + ")");
793                return;
794            }
795
796            // If we already have a text box, then this view is already
797            // connected so we want to restart it.
798            final boolean initial = mCurrentTextBoxAttribute == null;
799
800            // Hook 'em up and let 'er rip.
801            mCurrentTextBoxAttribute = tba;
802            mServedConnecting = false;
803            mServedInputConnection = ic;
804            if (ic != null) {
805                mCursorSelStart = tba.initialSelStart;
806                mCursorSelEnd = tba.initialSelEnd;
807                mCursorCandStart = -1;
808                mCursorCandEnd = -1;
809                mCursorRect.setEmpty();
810                setCurrentInputConnection(ic);
811            } else {
812                setCurrentInputConnection(mDummyInputConnection);
813            }
814
815            try {
816                if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
817                        + ic + " tba=" + tba + " initial=" + initial);
818                InputBindResult res = mService.startInput(mClient, tba, initial,
819                        mCurMethod == null);
820                if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
821                if (res != null) {
822                    if (res.id != null) {
823                        mBindSequence = res.sequence;
824                        mCurMethod = res.method;
825                    } else {
826                        // This means there is no input method available.
827                        if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
828                        return;
829                    }
830                }
831                if (mCurMethod != null && mCompletions != null) {
832                    try {
833                        mCurMethod.displayCompletions(mCompletions);
834                    } catch (RemoteException e) {
835                    }
836                }
837            } catch (RemoteException e) {
838                Log.w(TAG, "IME died: " + mCurId, e);
839            }
840        }
841    }
842
843    /**
844     * When the focused window is dismissed, this method is called to finish the
845     * input method started before.
846     * @hide
847     */
848    public void windowDismissed(IBinder appWindowToken) {
849        synchronized (mH) {
850            if (mServedView != null &&
851                    mServedView.getWindowToken() == appWindowToken) {
852                finishInputLocked();
853            }
854        }
855    }
856
857    /**
858     * Call this when a view receives focus.
859     * @hide
860     */
861    public void focusIn(View view) {
862        synchronized (mH) {
863            if (DEBUG) Log.v(TAG, "focusIn: " + view);
864            // Okay we have a new view that is being served.
865            if (mServedView != view) {
866                mCurrentTextBoxAttribute = null;
867            }
868            mServedView = view;
869            mCompletions = null;
870            mServedConnecting = true;
871        }
872
873        startInputInner();
874    }
875
876    /**
877     * Call this when a view loses focus.
878     * @hide
879     */
880    public void focusOut(View view) {
881        InputConnection ic = null;
882        synchronized (mH) {
883            if (DEBUG) Log.v(TAG, "focusOut: " + view
884                    + " mServedView=" + mServedView
885                    + " winFocus=" + view.hasWindowFocus());
886            if (mServedView == view) {
887                ic = mServedInputConnection;
888                if (view.hasWindowFocus()) {
889                    mLastServedView = view;
890                    mH.removeMessages(MSG_CHECK_FOCUS);
891                    mH.sendEmptyMessage(MSG_CHECK_FOCUS);
892                }
893            }
894        }
895
896        if (ic != null) {
897            ic.finishComposingText();
898        }
899    }
900
901    void checkFocus() {
902        synchronized (mH) {
903            if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
904                    + " last=" + mLastServedView);
905            if (mServedView == mLastServedView) {
906                finishInputLocked();
907                // In this case, we used to have a focused view on the window,
908                // but no longer do.  We should make sure the input method is
909                // no longer shown, since it serves no purpose.
910                closeCurrentInput();
911            }
912            mLastServedView = null;
913        }
914    }
915
916    void closeCurrentInput() {
917        try {
918            mService.hideSoftInput(mClient, 0);
919        } catch (RemoteException e) {
920        }
921    }
922
923    /**
924     * Called by ViewRoot the first time it gets window focus.
925     * @hide
926     */
927    public void onWindowFocus(View focusedView, int softInputMode,
928            boolean first, int windowFlags) {
929        synchronized (mH) {
930            if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
931                    + " softInputMode=" + softInputMode
932                    + " first=" + first + " flags=#"
933                    + Integer.toHexString(windowFlags));
934            try {
935                final boolean isTextEditor = focusedView != null &&
936                focusedView.onCheckIsTextEditor();
937                mService.windowGainedFocus(mClient, focusedView != null,
938                        isTextEditor, softInputMode, first, windowFlags);
939            } catch (RemoteException e) {
940            }
941        }
942    }
943
944    /**
945     * Report the current selection range.
946     */
947    public void updateSelection(View view, int selStart, int selEnd,
948            int candidatesStart, int candidatesEnd) {
949        synchronized (mH) {
950            if (mServedView != view || mCurrentTextBoxAttribute == null
951                    || mCurMethod == null) {
952                return;
953            }
954
955            if (mCursorSelStart != selStart || mCursorSelEnd != selEnd
956                    || mCursorCandStart != candidatesStart
957                    || mCursorCandEnd != candidatesEnd) {
958                if (DEBUG) Log.d(TAG, "updateSelection");
959
960                try {
961                    if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod);
962                    mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd,
963                            selStart, selEnd, candidatesStart, candidatesEnd);
964                    mCursorSelStart = selStart;
965                    mCursorSelEnd = selEnd;
966                    mCursorCandStart = candidatesStart;
967                    mCursorCandEnd = candidatesEnd;
968                } catch (RemoteException e) {
969                    Log.w(TAG, "IME died: " + mCurId, e);
970                }
971            }
972        }
973    }
974
975    /**
976     * Returns true if the current input method wants to watch the location
977     * of the input editor's cursor in its window.
978     */
979    public boolean isWatchingCursor(View view) {
980        return false;
981    }
982
983    /**
984     * Report the current cursor location in its window.
985     */
986    public void updateCursor(View view, int left, int top, int right, int bottom) {
987        synchronized (mH) {
988            if (mServedView != view || mCurrentTextBoxAttribute == null
989                    || mCurMethod == null) {
990                return;
991            }
992
993            mTmpCursorRect.set(left, top, right, bottom);
994            if (!mCursorRect.equals(mTmpCursorRect)) {
995                if (DEBUG) Log.d(TAG, "updateCursor");
996
997                try {
998                    if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
999                    mCurMethod.updateCursor(mTmpCursorRect);
1000                    mCursorRect.set(mTmpCursorRect);
1001                } catch (RemoteException e) {
1002                    Log.w(TAG, "IME died: " + mCurId, e);
1003                }
1004            }
1005        }
1006    }
1007
1008    /**
1009     * Force switch to a new input method component.  This can only be called
1010     * from the currently active input method, as validated by the given token.
1011     * @param token Supplies the identifying token given to an input method
1012     * when it was started, which allows it to perform this operation on
1013     * itself.
1014     * @param id The unique identifier for the new input method to be switched to.
1015     */
1016    public void setInputMethod(IBinder token, String id) {
1017        try {
1018            mService.setInputMethod(token, id);
1019        } catch (RemoteException e) {
1020            throw new RuntimeException(e);
1021        }
1022    }
1023
1024    /**
1025     * Close/hide the input method's soft input area, so the user no longer
1026     * sees it or can interact with it.  This can only be called
1027     * from the currently active input method, as validated by the given token.
1028     *
1029     * @param token Supplies the identifying token given to an input method
1030     * when it was started, which allows it to perform this operation on
1031     * itself.
1032     * @param flags Provides additional operating flags.  Currently may be
1033     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
1034     */
1035    public void hideSoftInputFromInputMethod(IBinder token, int flags) {
1036        try {
1037            mService.hideMySoftInput(token, flags);
1038        } catch (RemoteException e) {
1039            throw new RuntimeException(e);
1040        }
1041    }
1042
1043    /**
1044     * @hide
1045     */
1046    public void dispatchKeyEvent(Context context, int seq, KeyEvent key,
1047            IInputMethodCallback callback) {
1048        synchronized (mH) {
1049            if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
1050
1051            if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
1052                try {
1053                    callback.finishedEvent(seq, false);
1054                } catch (RemoteException e) {
1055                }
1056                return;
1057            }
1058
1059            if (key.getAction() == KeyEvent.ACTION_DOWN
1060                    && key.getKeyCode() == KeyEvent.KEYCODE_SYM) {
1061                showInputMethodPicker();
1062                try {
1063                    callback.finishedEvent(seq, true);
1064                } catch (RemoteException e) {
1065                }
1066                return;
1067            }
1068            try {
1069                if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod);
1070                mCurMethod.dispatchKeyEvent(seq, key, callback);
1071            } catch (RemoteException e) {
1072                Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
1073                try {
1074                    callback.finishedEvent(seq, false);
1075                } catch (RemoteException ex) {
1076                }
1077            }
1078        }
1079    }
1080
1081    /**
1082     * @hide
1083     */
1084    void dispatchTrackballEvent(Context context, int seq, MotionEvent motion,
1085            IInputMethodCallback callback) {
1086        synchronized (mH) {
1087            if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");
1088
1089            if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
1090                try {
1091                    callback.finishedEvent(seq, false);
1092                } catch (RemoteException e) {
1093                }
1094                return;
1095            }
1096
1097            try {
1098                if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
1099                mCurMethod.dispatchTrackballEvent(seq, motion, callback);
1100            } catch (RemoteException e) {
1101                Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
1102                try {
1103                    callback.finishedEvent(seq, false);
1104                } catch (RemoteException ex) {
1105                }
1106            }
1107        }
1108    }
1109
1110    public void showInputMethodPicker() {
1111        synchronized (mH) {
1112            try {
1113                mService.showInputMethodPickerFromClient(mClient);
1114            } catch (RemoteException e) {
1115                Log.w(TAG, "IME died: " + mCurId, e);
1116            }
1117        }
1118    }
1119}
1120