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