InputMethodManager.java revision 9266c558bf1d21ff647525ff99f7dadbca417309
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 commitCompletion(CompletionInfo text) {
297            return false;
298        }
299
300        public boolean commitText(CharSequence text, int newCursorPosition) {
301            return false;
302        }
303
304        public boolean deleteSurroundingText(int leftLength, int rightLength) {
305            return false;
306        }
307
308        public int getCursorCapsMode(int reqModes) {
309            return 0;
310        }
311
312        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
313            return null;
314        }
315
316        public CharSequence getTextAfterCursor(int n) {
317            return null;
318        }
319
320        public CharSequence getTextBeforeCursor(int n) {
321            return null;
322        }
323
324        public boolean hideStatusIcon() {
325            return false;
326        }
327
328        public boolean performPrivateCommand(String action, Bundle data) {
329            return false;
330        }
331
332        public boolean sendKeyEvent(KeyEvent event) {
333            return false;
334        }
335
336        public boolean setComposingText(CharSequence text, int newCursorPosition) {
337            return false;
338        }
339
340        public boolean finishComposingText() {
341            return false;
342        }
343
344        public boolean showStatusIcon(String packageName, int resId) {
345            return false;
346        }
347    }
348
349    final NoOpInputConnection mNoOpInputConnection = new NoOpInputConnection();
350
351    final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
352        public void setUsingInputMethod(boolean state) {
353
354        }
355
356        public void onBindMethod(InputBindResult res) {
357            synchronized (mH) {
358                if (mBindSequence < 0 || mBindSequence != res.sequence) {
359                    Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
360                            + ", given seq=" + res.sequence);
361                    return;
362                }
363
364                mCurMethod = res.method;
365                mCurId = res.id;
366                mBindSequence = res.sequence;
367            }
368            startInputInner();
369        }
370
371        public void onUnbindMethod(int sequence) {
372            synchronized (mH) {
373                if (mBindSequence == sequence) {
374                    if (false) {
375                        // XXX the server has already unbound!
376                        if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
377                            try {
378                                mCurMethod.finishInput();
379                            } catch (RemoteException e) {
380                                Log.w(TAG, "IME died: " + mCurId, e);
381                            }
382                        }
383                    }
384                    clearBindingLocked();
385
386                    // If we were actively using the last input method, then
387                    // we would like to re-connect to the next input method.
388                    if (mServedView != null && mServedView.isFocused()) {
389                        mServedConnecting = true;
390                    }
391                }
392                startInputInner();
393            }
394        }
395
396        public void setActive(boolean active) {
397            mActive = active;
398            mInputConnectionWrapper.setBaseInputConnection(active
399                    ? mCurrentInputConnection : mNoOpInputConnection);
400        }
401    };
402
403    final InputConnection mDummyInputConnection = new BaseInputConnection(this) {
404        public boolean commitText(CharSequence text, int newCursorPosition) {
405            return false;
406        }
407        public boolean commitCompletion(CompletionInfo text) {
408            return false;
409        }
410        public boolean deleteSurroundingText(int leftLength, int rightLength) {
411            return false;
412        }
413        public ExtractedText getExtractedText(ExtractedTextRequest request,
414                int flags) {
415            return null;
416        }
417        public CharSequence getTextAfterCursor(int n) {
418            return null;
419        }
420        public CharSequence getTextBeforeCursor(int n) {
421            return null;
422        }
423        public int getCursorCapsMode(int reqModes) {
424            return 0;
425        }
426        public boolean clearMetaKeyStates(int states) {
427            return false;
428        }
429        public boolean performPrivateCommand(String action, Bundle data) {
430            return false;
431        }
432        public boolean setComposingText(CharSequence text, int newCursorPosition) {
433            return false;
434        }
435        public boolean finishComposingText() {
436            return false;
437        }
438    };
439
440    InputMethodManager(IInputMethodManager service, Looper looper) {
441        mService = service;
442        mMainLooper = looper;
443        mH = new H(looper);
444        mInputConnectionWrapper = new MutableInputConnectionWrapper(mNoOpInputConnection);
445        mIInputContext = new IInputConnectionWrapper(looper,
446                mInputConnectionWrapper);
447        setCurrentInputConnection(mDummyInputConnection);
448
449        if (mInstance == null) {
450            mInstance = this;
451        }
452    }
453
454    /**
455     * Retrieve the global InputMethodManager instance, creating it if it
456     * doesn't already exist.
457     * @hide
458     */
459    static public InputMethodManager getInstance(Context context) {
460        synchronized (mInstanceSync) {
461            if (mInstance != null) {
462                return mInstance;
463            }
464            IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
465            IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
466            mInstance = new InputMethodManager(service, context.getMainLooper());
467        }
468        return mInstance;
469    }
470
471    /**
472     * Private optimization: retrieve the global InputMethodManager instance,
473     * if it exists.
474     * @hide
475     */
476    static public InputMethodManager peekInstance() {
477        return mInstance;
478    }
479
480    /** @hide */
481    public IInputMethodClient getClient() {
482        return mClient;
483    }
484
485    /** @hide */
486    public IInputContext getInputContext() {
487        return mIInputContext;
488    }
489
490    public List<InputMethodInfo> getInputMethodList() {
491        try {
492            return mService.getInputMethodList();
493        } catch (RemoteException e) {
494            throw new RuntimeException(e);
495        }
496    }
497
498    public List<InputMethodInfo> getEnabledInputMethodList() {
499        try {
500            return mService.getEnabledInputMethodList();
501        } catch (RemoteException e) {
502            throw new RuntimeException(e);
503        }
504    }
505
506    public void updateStatusIcon(int iconId, String iconPackage) {
507        try {
508            mService.updateStatusIcon(iconId, iconPackage);
509        } catch (RemoteException e) {
510            throw new RuntimeException(e);
511        }
512    }
513
514    /**
515     * Return true if the given view is the currently active view for the
516     * input method.
517     */
518    public boolean isActive(View view) {
519        synchronized (mH) {
520            return mServedView == view && mCurrentTextBoxAttribute != null;
521        }
522    }
523
524    /**
525     * Return true if any view is currently active in the input method.
526     */
527    public boolean isActive() {
528        synchronized (mH) {
529            return mServedView != null && mCurrentTextBoxAttribute != null;
530        }
531    }
532
533    /**
534     * Return true if the currently served view is accepting full text edits.
535     * If false, it has no input connection, so can only handle raw key events.
536     */
537    public boolean isAcceptingText() {
538        return mServedInputConnection != null;
539    }
540
541    /**
542     * Reset all of the state associated with being bound to an input method.
543     */
544    void clearBindingLocked() {
545        clearConnectionLocked();
546        mBindSequence = -1;
547        mCurId = null;
548        mCurMethod = null;
549    }
550
551    /**
552     * Record the desired input connection, but only set it if mActive is true.
553     */
554    void setCurrentInputConnection(InputConnection connection) {
555        mCurrentInputConnection = connection;
556        mInputConnectionWrapper.setBaseInputConnection(mActive
557                ? connection : mNoOpInputConnection);
558    }
559
560    /**
561     * Reset all of the state associated with a served view being connected
562     * to an input method
563     */
564    void clearConnectionLocked() {
565        mCurrentTextBoxAttribute = null;
566        mServedInputConnection = null;
567        setCurrentInputConnection(mDummyInputConnection);
568    }
569
570    /**
571     * Disconnect any existing input connection, clearing the served view.
572     */
573    void finishInputLocked() {
574        if (mServedView != null) {
575            if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView);
576            updateStatusIcon(0, null);
577
578            if (mCurrentTextBoxAttribute != null) {
579                try {
580                    mService.finishInput(mClient);
581                } catch (RemoteException e) {
582                }
583            }
584
585            if (mServedInputConnection != null) {
586                // We need to tell the previously served view that it is no
587                // longer the input target, so it can reset its state.  Schedule
588                // this call on its window's Handler so it will be on the correct
589                // thread and outside of our lock.
590                Handler vh = mServedView.getHandler();
591                if (vh != null) {
592                    // This will result in a call to reportFinishInputConnection()
593                    // below.
594                    vh.sendMessage(vh.obtainMessage(ViewRoot.FINISH_INPUT_CONNECTION,
595                            mServedInputConnection));
596                }
597            }
598
599            mServedView = null;
600            mCompletions = null;
601            mServedConnecting = false;
602            clearConnectionLocked();
603        }
604    }
605
606    /**
607     * Called from the FINISH_INPUT_CONNECTION message above.
608     * @hide
609     */
610    public void reportFinishInputConnection(InputConnection ic) {
611        if (mServedInputConnection != ic) {
612            ic.finishComposingText();
613        }
614    }
615
616    public void displayCompletions(View view, CompletionInfo[] completions) {
617        synchronized (mH) {
618            if (mServedView != view) {
619                return;
620            }
621
622            mCompletions = completions;
623            if (mCurMethod != null) {
624                try {
625                    mCurMethod.displayCompletions(mCompletions);
626                } catch (RemoteException e) {
627                }
628            }
629        }
630    }
631
632    public void updateExtractedText(View view, int token, ExtractedText text) {
633        synchronized (mH) {
634            if (mServedView != view) {
635                return;
636            }
637
638            if (mCurMethod != null) {
639                try {
640                    mCurMethod.updateExtractedText(token, text);
641                } catch (RemoteException e) {
642                }
643            }
644        }
645    }
646
647    /**
648     * Flag for {@link #showSoftInput} to indicate that the this is an implicit
649     * request to show the input window, not as the result of a direct request
650     * by the user.  The window may not be shown in this case.
651     */
652    public static final int SHOW_IMPLICIT = 0x0001;
653
654    /**
655     * Explicitly request that the current input method's soft input area be
656     * shown to the user, if needed.  Call this if the user interacts with
657     * your view in such a way that they have expressed they would like to
658     * start performing input into it.
659     *
660     * @param view The currently focused view, which would like to receive
661     * soft keyboard input.
662     * @param flags Provides additional operating flags.  Currently may be
663     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
664     */
665    public void showSoftInput(View view, int flags) {
666        synchronized (mH) {
667            if (mServedView != view) {
668                return;
669            }
670
671            try {
672                mService.showSoftInput(mClient, flags);
673            } catch (RemoteException e) {
674            }
675        }
676    }
677
678    /**
679     * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
680     * input window should only be hidden if it was not explicitly shown
681     * by the user.
682     */
683    public static final int HIDE_IMPLICIT_ONLY = 0x0001;
684
685    /**
686     * Request to hide the soft input window from the context of the window
687     * that is currently accepting input.  This should be called as a result
688     * of the user doing some actually than fairly explicitly requests to
689     * have the input window hidden.
690     *
691     * @param windowToken The token of the window that is making the request,
692     * as returned by {@link View#getWindowToken() View.getWindowToken()}.
693     * @param flags Provides additional operating flags.  Currently may be
694     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
695     */
696    public void hideSoftInputFromWindow(IBinder windowToken, int flags) {
697        synchronized (mH) {
698            if (mServedView == null || mServedView.getWindowToken() != windowToken) {
699                return;
700            }
701
702            try {
703                mService.hideSoftInput(mClient, flags);
704            } catch (RemoteException e) {
705            }
706        }
707    }
708
709    /**
710     * If the input method is currently connected to the given view,
711     * restart it with its new contents.  You should call this when the text
712     * within your view changes outside of the normal input method or key
713     * input flow, such as when an application calls TextView.setText().
714     *
715     * @param view The view whose text has changed.
716     */
717    public void restartInput(View view) {
718        synchronized (mH) {
719            if (mServedView != view) {
720                return;
721            }
722
723            mServedConnecting = true;
724        }
725
726        startInputInner();
727    }
728
729    void startInputInner() {
730        final View view;
731        synchronized (mH) {
732            view = mServedView;
733
734            // Make sure we have a window token for the served view.
735            if (DEBUG) Log.v(TAG, "Starting input: view=" + view);
736            if (view == null) {
737                if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
738                return;
739            }
740        }
741
742        // Now we need to get an input connection from the served view.
743        // This is complicated in a couple ways: we can't be holding our lock
744        // when calling out to the view, and we need to make sure we call into
745        // the view on the same thread that is driving its view hierarchy.
746        Handler vh = view.getHandler();
747        if (vh == null) {
748            // If the view doesn't have a handler, something has changed out
749            // from under us, so just bail.
750            if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!");
751            return;
752        }
753        if (vh.getLooper() != Looper.myLooper()) {
754            // The view is running on a different thread than our own, so
755            // we need to reschedule our work for over there.
756            if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
757            vh.post(new Runnable() {
758                public void run() {
759                    startInputInner();
760                }
761            });
762        }
763
764        // Okay we are now ready to call into the served view and have it
765        // do its stuff.
766        // Life is good: let's hook everything up!
767        EditorInfo tba = new EditorInfo();
768        InputConnection ic = view.onCreateInputConnection(tba);
769        if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
770
771        synchronized (mH) {
772            // Now that we are locked again, validate that our state hasn't
773            // changed.
774            if (mServedView != view || !mServedConnecting) {
775                // Something else happened, so abort.
776                if (DEBUG) Log.v(TAG,
777                        "Starting input: finished by someone else (view="
778                        + mServedView + " conn=" + mServedConnecting + ")");
779                return;
780            }
781
782            // If we already have a text box, then this view is already
783            // connected so we want to restart it.
784            final boolean initial = mCurrentTextBoxAttribute == null;
785
786            // Hook 'em up and let 'er rip.
787            mCurrentTextBoxAttribute = tba;
788            mServedConnecting = false;
789            mServedInputConnection = ic;
790            if (ic != null) {
791                mCursorSelStart = tba.initialSelStart;
792                mCursorSelEnd = tba.initialSelEnd;
793                mCursorCandStart = -1;
794                mCursorCandEnd = -1;
795                mCursorRect.setEmpty();
796                setCurrentInputConnection(ic);
797            } else {
798                setCurrentInputConnection(mDummyInputConnection);
799            }
800
801            try {
802                if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
803                        + ic + " tba=" + tba);
804                InputBindResult res = mService.startInput(mClient, tba, initial,
805                        mCurMethod == null);
806                if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
807                if (res != null) {
808                    if (res.id != null) {
809                        mBindSequence = res.sequence;
810                        mCurMethod = res.method;
811                    } else {
812                        // This means there is no input method available.
813                        if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
814                        return;
815                    }
816                }
817                if (mCurMethod != null && mCompletions != null) {
818                    try {
819                        mCurMethod.displayCompletions(mCompletions);
820                    } catch (RemoteException e) {
821                    }
822                }
823            } catch (RemoteException e) {
824                Log.w(TAG, "IME died: " + mCurId, e);
825            }
826        }
827    }
828
829    /**
830     * When the focused window is dismissed, this method is called to finish the
831     * input method started before.
832     * @hide
833     */
834    public void windowDismissed(IBinder appWindowToken) {
835        synchronized (mH) {
836            if (mServedView != null &&
837                    mServedView.getWindowToken() == appWindowToken) {
838                finishInputLocked();
839            }
840        }
841    }
842
843    /**
844     * Call this when a view receives focus.
845     * @hide
846     */
847    public void focusIn(View view) {
848        synchronized (mH) {
849            if (DEBUG) Log.v(TAG, "focusIn: " + view);
850            // Okay we have a new view that is being served.
851            mServedView = view;
852            mCompletions = null;
853            mServedConnecting = true;
854        }
855
856        startInputInner();
857    }
858
859    /**
860     * Call this when a view loses focus.
861     * @hide
862     */
863    public void focusOut(View view) {
864        InputConnection ic = null;
865        synchronized (mH) {
866            if (DEBUG) Log.v(TAG, "focusOut: " + view
867                    + " mServedView=" + mServedView
868                    + " winFocus=" + view.hasWindowFocus());
869            if (mServedView == view) {
870                ic = mServedInputConnection;
871                if (view.hasWindowFocus()) {
872                    mLastServedView = view;
873                    mH.removeMessages(MSG_CHECK_FOCUS);
874                    mH.sendEmptyMessage(MSG_CHECK_FOCUS);
875                }
876            }
877        }
878
879        if (ic != null) {
880            ic.finishComposingText();
881        }
882    }
883
884    void checkFocus() {
885        synchronized (mH) {
886            if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
887                    + " last=" + mLastServedView);
888            if (mServedView == mLastServedView) {
889                finishInputLocked();
890                // In this case, we used to have a focused view on the window,
891                // but no longer do.  We should make sure the input method is
892                // no longer shown, since it serves no purpose.
893                closeCurrentInput();
894            }
895            mLastServedView = null;
896        }
897    }
898
899    void closeCurrentInput() {
900        try {
901            mService.hideSoftInput(mClient, 0);
902        } catch (RemoteException e) {
903        }
904    }
905
906    /**
907     * Called by ViewRoot the first time it gets window focus.
908     * @hide
909     */
910    public void onWindowFocus(View focusedView, int softInputMode,
911            boolean first, int windowFlags) {
912        synchronized (mH) {
913            if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
914                    + " softInputMode=" + softInputMode
915                    + " first=" + first + " flags=#"
916                    + Integer.toHexString(windowFlags));
917            try {
918                final boolean isTextEditor = focusedView != null &&
919                focusedView.onCheckIsTextEditor();
920                mService.windowGainedFocus(mClient, focusedView != null,
921                        isTextEditor, softInputMode, first, windowFlags);
922            } catch (RemoteException e) {
923            }
924        }
925    }
926
927    /**
928     * Report the current selection range.
929     */
930    public void updateSelection(View view, int selStart, int selEnd,
931            int candidatesStart, int candidatesEnd) {
932        synchronized (mH) {
933            if (mServedView != view || mCurrentTextBoxAttribute == null
934                    || mCurMethod == null) {
935                return;
936            }
937
938            if (mCursorSelStart != selStart || mCursorSelEnd != selEnd
939                    || mCursorCandStart != candidatesStart
940                    || mCursorCandEnd != candidatesEnd) {
941                if (DEBUG) Log.d(TAG, "updateSelection");
942
943                try {
944                    if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod);
945                    mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd,
946                            selStart, selEnd, candidatesStart, candidatesEnd);
947                    mCursorSelStart = selStart;
948                    mCursorSelEnd = selEnd;
949                    mCursorCandStart = candidatesStart;
950                    mCursorCandEnd = candidatesEnd;
951                } catch (RemoteException e) {
952                    Log.w(TAG, "IME died: " + mCurId, e);
953                }
954            }
955        }
956    }
957
958    /**
959     * Returns true if the current input method wants to watch the location
960     * of the input editor's cursor in its window.
961     */
962    public boolean isWatchingCursor(View view) {
963        return false;
964    }
965
966    /**
967     * Report the current cursor location in its window.
968     */
969    public void updateCursor(View view, int left, int top, int right, int bottom) {
970        synchronized (mH) {
971            if (mServedView != view || mCurrentTextBoxAttribute == null
972                    || mCurMethod == null) {
973                return;
974            }
975
976            mTmpCursorRect.set(left, top, right, bottom);
977            if (!mCursorRect.equals(mTmpCursorRect)) {
978                if (DEBUG) Log.d(TAG, "updateCursor");
979
980                try {
981                    if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
982                    mCurMethod.updateCursor(mTmpCursorRect);
983                    mCursorRect.set(mTmpCursorRect);
984                } catch (RemoteException e) {
985                    Log.w(TAG, "IME died: " + mCurId, e);
986                }
987            }
988        }
989    }
990
991    /**
992     * Force switch to a new input method component.  This can only be called
993     * from the currently active input method, as validated by the given token.
994     * @param token Supplies the identifying token given to an input method
995     * when it was started, which allows it to perform this operation on
996     * itself.
997     * @param id The unique identifier for the new input method to be switched to.
998     */
999    public void setInputMethod(IBinder token, String id) {
1000        try {
1001            mService.setInputMethod(token, id);
1002        } catch (RemoteException e) {
1003            throw new RuntimeException(e);
1004        }
1005    }
1006
1007    /**
1008     * Close/hide the input method's soft input area, so the user no longer
1009     * sees it or can interact with it.  This can only be called
1010     * from the currently active input method, as validated by the given token.
1011     *
1012     * @param token Supplies the identifying token given to an input method
1013     * when it was started, which allows it to perform this operation on
1014     * itself.
1015     * @param flags Provides additional operating flags.  Currently may be
1016     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
1017     */
1018    public void hideSoftInputFromInputMethod(IBinder token, int flags) {
1019        try {
1020            mService.hideMySoftInput(token, flags);
1021        } catch (RemoteException e) {
1022            throw new RuntimeException(e);
1023        }
1024    }
1025
1026    /**
1027     * @hide
1028     */
1029    public void dispatchKeyEvent(Context context, int seq, KeyEvent key,
1030            IInputMethodCallback callback) {
1031        synchronized (mH) {
1032            if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
1033
1034            if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
1035                try {
1036                    callback.finishedEvent(seq, false);
1037                } catch (RemoteException e) {
1038                }
1039                return;
1040            }
1041
1042            if (key.getAction() == KeyEvent.ACTION_DOWN
1043                    && key.getKeyCode() == KeyEvent.KEYCODE_SYM) {
1044                showInputMethodPicker();
1045                try {
1046                    callback.finishedEvent(seq, true);
1047                } catch (RemoteException e) {
1048                }
1049                return;
1050            }
1051            try {
1052                if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod);
1053                mCurMethod.dispatchKeyEvent(seq, key, callback);
1054            } catch (RemoteException e) {
1055                Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
1056                try {
1057                    callback.finishedEvent(seq, false);
1058                } catch (RemoteException ex) {
1059                }
1060            }
1061        }
1062    }
1063
1064    /**
1065     * @hide
1066     */
1067    void dispatchTrackballEvent(Context context, int seq, MotionEvent motion,
1068            IInputMethodCallback callback) {
1069        synchronized (mH) {
1070            if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");
1071
1072            if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
1073                try {
1074                    callback.finishedEvent(seq, false);
1075                } catch (RemoteException e) {
1076                }
1077                return;
1078            }
1079
1080            try {
1081                if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
1082                mCurMethod.dispatchTrackballEvent(seq, motion, callback);
1083            } catch (RemoteException e) {
1084                Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
1085                try {
1086                    callback.finishedEvent(seq, false);
1087                } catch (RemoteException ex) {
1088                }
1089            }
1090        }
1091    }
1092
1093    public void showInputMethodPicker() {
1094        synchronized (mH) {
1095            try {
1096                mService.showInputMethodPickerFromClient(mClient);
1097            } catch (RemoteException e) {
1098                Log.w(TAG, "IME died: " + mCurId, e);
1099            }
1100        }
1101    }
1102}
1103