InputMethodManagerService.java revision 0cbda99f8721ad9b03ada04d2637fb75a2a0feca
1/*
2 * Copyright (C) 2006-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 com.android.server;
18
19import com.android.internal.content.PackageMonitor;
20import com.android.internal.os.HandlerCaller;
21import com.android.internal.view.IInputContext;
22import com.android.internal.view.IInputMethod;
23import com.android.internal.view.IInputMethodCallback;
24import com.android.internal.view.IInputMethodClient;
25import com.android.internal.view.IInputMethodManager;
26import com.android.internal.view.IInputMethodSession;
27import com.android.internal.view.InputBindResult;
28
29import com.android.server.status.StatusBarManagerService;
30
31import org.xmlpull.v1.XmlPullParserException;
32
33import android.app.ActivityManagerNative;
34import android.app.AlertDialog;
35import android.app.PendingIntent;
36import android.content.ComponentName;
37import android.content.ContentResolver;
38import android.content.Context;
39import android.content.DialogInterface;
40import android.content.IntentFilter;
41import android.content.DialogInterface.OnCancelListener;
42import android.content.Intent;
43import android.content.ServiceConnection;
44import android.content.pm.ApplicationInfo;
45import android.content.pm.PackageManager;
46import android.content.pm.ResolveInfo;
47import android.content.pm.ServiceInfo;
48import android.content.res.Configuration;
49import android.content.res.Resources;
50import android.content.res.TypedArray;
51import android.database.ContentObserver;
52import android.os.Binder;
53import android.os.Handler;
54import android.os.IBinder;
55import android.os.IInterface;
56import android.os.Message;
57import android.os.Parcel;
58import android.os.RemoteException;
59import android.os.ResultReceiver;
60import android.os.ServiceManager;
61import android.os.SystemClock;
62import android.provider.Settings;
63import android.provider.Settings.Secure;
64import android.text.TextUtils;
65import android.util.EventLog;
66import android.util.Slog;
67import android.util.PrintWriterPrinter;
68import android.util.Printer;
69import android.view.IWindowManager;
70import android.view.WindowManager;
71import android.view.inputmethod.InputBinding;
72import android.view.inputmethod.InputMethod;
73import android.view.inputmethod.InputMethodInfo;
74import android.view.inputmethod.InputMethodManager;
75import android.view.inputmethod.EditorInfo;
76
77import java.io.FileDescriptor;
78import java.io.IOException;
79import java.io.PrintWriter;
80import java.util.ArrayList;
81import java.util.HashMap;
82import java.util.List;
83
84/**
85 * This class provides a system service that manages input methods.
86 */
87public class InputMethodManagerService extends IInputMethodManager.Stub
88        implements ServiceConnection, Handler.Callback {
89    static final boolean DEBUG = false;
90    static final String TAG = "InputManagerService";
91
92    static final int MSG_SHOW_IM_PICKER = 1;
93
94    static final int MSG_UNBIND_INPUT = 1000;
95    static final int MSG_BIND_INPUT = 1010;
96    static final int MSG_SHOW_SOFT_INPUT = 1020;
97    static final int MSG_HIDE_SOFT_INPUT = 1030;
98    static final int MSG_ATTACH_TOKEN = 1040;
99    static final int MSG_CREATE_SESSION = 1050;
100
101    static final int MSG_START_INPUT = 2000;
102    static final int MSG_RESTART_INPUT = 2010;
103
104    static final int MSG_UNBIND_METHOD = 3000;
105    static final int MSG_BIND_METHOD = 3010;
106
107    static final long TIME_TO_RECONNECT = 10*1000;
108
109    final Context mContext;
110    final Handler mHandler;
111    final SettingsObserver mSettingsObserver;
112    final StatusBarManagerService mStatusBar;
113    final IWindowManager mIWindowManager;
114    final HandlerCaller mCaller;
115
116    final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
117
118    // All known input methods.  mMethodMap also serves as the global
119    // lock for this class.
120    final ArrayList<InputMethodInfo> mMethodList
121            = new ArrayList<InputMethodInfo>();
122    final HashMap<String, InputMethodInfo> mMethodMap
123            = new HashMap<String, InputMethodInfo>();
124
125    final TextUtils.SimpleStringSplitter mStringColonSplitter
126            = new TextUtils.SimpleStringSplitter(':');
127
128    class SessionState {
129        final ClientState client;
130        final IInputMethod method;
131        final IInputMethodSession session;
132
133        @Override
134        public String toString() {
135            return "SessionState{uid " + client.uid + " pid " + client.pid
136                    + " method " + Integer.toHexString(
137                            System.identityHashCode(method))
138                    + " session " + Integer.toHexString(
139                            System.identityHashCode(session))
140                    + "}";
141        }
142
143        SessionState(ClientState _client, IInputMethod _method,
144                IInputMethodSession _session) {
145            client = _client;
146            method = _method;
147            session = _session;
148        }
149    }
150
151    class ClientState {
152        final IInputMethodClient client;
153        final IInputContext inputContext;
154        final int uid;
155        final int pid;
156        final InputBinding binding;
157
158        boolean sessionRequested;
159        SessionState curSession;
160
161        @Override
162        public String toString() {
163            return "ClientState{" + Integer.toHexString(
164                    System.identityHashCode(this)) + " uid " + uid
165                    + " pid " + pid + "}";
166        }
167
168        ClientState(IInputMethodClient _client, IInputContext _inputContext,
169                int _uid, int _pid) {
170            client = _client;
171            inputContext = _inputContext;
172            uid = _uid;
173            pid = _pid;
174            binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
175        }
176    }
177
178    final HashMap<IBinder, ClientState> mClients
179            = new HashMap<IBinder, ClientState>();
180
181    /**
182     * Set once the system is ready to run third party code.
183     */
184    boolean mSystemReady;
185
186    /**
187     * Id of the currently selected input method.
188     */
189    String mCurMethodId;
190
191    /**
192     * The current binding sequence number, incremented every time there is
193     * a new bind performed.
194     */
195    int mCurSeq;
196
197    /**
198     * The client that is currently bound to an input method.
199     */
200    ClientState mCurClient;
201
202    /**
203     * The last window token that gained focus.
204     */
205    IBinder mCurFocusedWindow;
206
207    /**
208     * The input context last provided by the current client.
209     */
210    IInputContext mCurInputContext;
211
212    /**
213     * The attributes last provided by the current client.
214     */
215    EditorInfo mCurAttribute;
216
217    /**
218     * The input method ID of the input method service that we are currently
219     * connected to or in the process of connecting to.
220     */
221    String mCurId;
222
223    /**
224     * Set to true if our ServiceConnection is currently actively bound to
225     * a service (whether or not we have gotten its IBinder back yet).
226     */
227    boolean mHaveConnection;
228
229    /**
230     * Set if the client has asked for the input method to be shown.
231     */
232    boolean mShowRequested;
233
234    /**
235     * Set if we were explicitly told to show the input method.
236     */
237    boolean mShowExplicitlyRequested;
238
239    /**
240     * Set if we were forced to be shown.
241     */
242    boolean mShowForced;
243
244    /**
245     * Set if we last told the input method to show itself.
246     */
247    boolean mInputShown;
248
249    /**
250     * The Intent used to connect to the current input method.
251     */
252    Intent mCurIntent;
253
254    /**
255     * The token we have made for the currently active input method, to
256     * identify it in the future.
257     */
258    IBinder mCurToken;
259
260    /**
261     * If non-null, this is the input method service we are currently connected
262     * to.
263     */
264    IInputMethod mCurMethod;
265
266    /**
267     * Time that we last initiated a bind to the input method, to determine
268     * if we should try to disconnect and reconnect to it.
269     */
270    long mLastBindTime;
271
272    /**
273     * Have we called mCurMethod.bindInput()?
274     */
275    boolean mBoundToMethod;
276
277    /**
278     * Currently enabled session.  Only touched by service thread, not
279     * protected by a lock.
280     */
281    SessionState mEnabledSession;
282
283    /**
284     * True if the screen is on.  The value is true initially.
285     */
286    boolean mScreenOn = true;
287
288    AlertDialog.Builder mDialogBuilder;
289    AlertDialog mSwitchingDialog;
290    InputMethodInfo[] mIms;
291    CharSequence[] mItems;
292
293    class SettingsObserver extends ContentObserver {
294        SettingsObserver(Handler handler) {
295            super(handler);
296            ContentResolver resolver = mContext.getContentResolver();
297            resolver.registerContentObserver(Settings.Secure.getUriFor(
298                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
299        }
300
301        @Override public void onChange(boolean selfChange) {
302            synchronized (mMethodMap) {
303                updateFromSettingsLocked();
304            }
305        }
306    }
307
308    class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
309        @Override
310        public void onReceive(Context context, Intent intent) {
311            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
312                mScreenOn = true;
313            } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
314                mScreenOn = false;
315            } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
316                hideInputMethodMenu();
317                return;
318            } else {
319                Slog.w(TAG, "Unexpected intent " + intent);
320            }
321
322            // Inform the current client of the change in active status
323            try {
324                if (mCurClient != null && mCurClient.client != null) {
325                    mCurClient.client.setActive(mScreenOn);
326                }
327            } catch (RemoteException e) {
328                Slog.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid "
329                        + mCurClient.pid + " uid " + mCurClient.uid);
330            }
331        }
332    }
333
334    class MyPackageMonitor extends PackageMonitor {
335
336        @Override
337        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
338            synchronized (mMethodMap) {
339                String curInputMethodId = Settings.Secure.getString(mContext
340                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
341                final int N = mMethodList.size();
342                if (curInputMethodId != null) {
343                    for (int i=0; i<N; i++) {
344                        InputMethodInfo imi = mMethodList.get(i);
345                        if (imi.getId().equals(curInputMethodId)) {
346                            for (String pkg : packages) {
347                                if (imi.getPackageName().equals(pkg)) {
348                                    if (!doit) {
349                                        return true;
350                                    }
351
352                                    Settings.Secure.putString(mContext.getContentResolver(),
353                                            Settings.Secure.DEFAULT_INPUT_METHOD, "");
354                                    chooseNewDefaultIMELocked();
355                                    return true;
356                                }
357                            }
358                        }
359                    }
360                }
361            }
362            return false;
363        }
364
365        @Override
366        public void onSomePackagesChanged() {
367            synchronized (mMethodMap) {
368                InputMethodInfo curIm = null;
369                String curInputMethodId = Settings.Secure.getString(mContext
370                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
371                final int N = mMethodList.size();
372                if (curInputMethodId != null) {
373                    for (int i=0; i<N; i++) {
374                        InputMethodInfo imi = mMethodList.get(i);
375                        if (imi.getId().equals(curInputMethodId)) {
376                            curIm = imi;
377                        }
378                        int change = isPackageDisappearing(imi.getPackageName());
379                        if (change == PACKAGE_TEMPORARY_CHANGE
380                                || change == PACKAGE_PERMANENT_CHANGE) {
381                            Slog.i(TAG, "Input method uninstalled, disabling: "
382                                    + imi.getComponent());
383                            setInputMethodEnabledLocked(imi.getId(), false);
384                        }
385                    }
386                }
387
388                buildInputMethodListLocked(mMethodList, mMethodMap);
389
390                boolean changed = false;
391
392                if (curIm != null) {
393                    int change = isPackageDisappearing(curIm.getPackageName());
394                    if (change == PACKAGE_TEMPORARY_CHANGE
395                            || change == PACKAGE_PERMANENT_CHANGE) {
396                        ServiceInfo si = null;
397                        try {
398                            si = mContext.getPackageManager().getServiceInfo(
399                                    curIm.getComponent(), 0);
400                        } catch (PackageManager.NameNotFoundException ex) {
401                        }
402                        if (si == null) {
403                            // Uh oh, current input method is no longer around!
404                            // Pick another one...
405                            Slog.i(TAG, "Current input method removed: " + curInputMethodId);
406                            if (!chooseNewDefaultIMELocked()) {
407                                changed = true;
408                                curIm = null;
409                                curInputMethodId = "";
410                                Slog.i(TAG, "Unsetting current input method");
411                                Settings.Secure.putString(mContext.getContentResolver(),
412                                        Settings.Secure.DEFAULT_INPUT_METHOD,
413                                        curInputMethodId);
414                            }
415                        }
416                    }
417                }
418
419                if (curIm == null) {
420                    // We currently don't have a default input method... is
421                    // one now available?
422                    changed = chooseNewDefaultIMELocked();
423                }
424
425                if (changed) {
426                    updateFromSettingsLocked();
427                }
428            }
429        }
430    }
431
432    class MethodCallback extends IInputMethodCallback.Stub {
433        final IInputMethod mMethod;
434
435        MethodCallback(IInputMethod method) {
436            mMethod = method;
437        }
438
439        public void finishedEvent(int seq, boolean handled) throws RemoteException {
440        }
441
442        public void sessionCreated(IInputMethodSession session) throws RemoteException {
443            onSessionCreated(mMethod, session);
444        }
445    }
446
447    public InputMethodManagerService(Context context, StatusBarManagerService statusBar) {
448        mContext = context;
449        mHandler = new Handler(this);
450        mIWindowManager = IWindowManager.Stub.asInterface(
451                ServiceManager.getService(Context.WINDOW_SERVICE));
452        mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
453            public void executeMessage(Message msg) {
454                handleMessage(msg);
455            }
456        });
457
458        (new MyPackageMonitor()).register(mContext, true);
459
460        IntentFilter screenOnOffFilt = new IntentFilter();
461        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
462        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
463        screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
464        mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
465
466        buildInputMethodListLocked(mMethodList, mMethodMap);
467
468        final String enabledStr = Settings.Secure.getString(
469                mContext.getContentResolver(),
470                Settings.Secure.ENABLED_INPUT_METHODS);
471        Slog.i(TAG, "Enabled input methods: " + enabledStr);
472        if (enabledStr == null) {
473            Slog.i(TAG, "Enabled input methods has not been set, enabling all");
474            InputMethodInfo defIm = null;
475            StringBuilder sb = new StringBuilder(256);
476            final int N = mMethodList.size();
477            for (int i=0; i<N; i++) {
478                InputMethodInfo imi = mMethodList.get(i);
479                Slog.i(TAG, "Adding: " + imi.getId());
480                if (i > 0) sb.append(':');
481                sb.append(imi.getId());
482                if (defIm == null && imi.getIsDefaultResourceId() != 0) {
483                    try {
484                        Resources res = mContext.createPackageContext(
485                                imi.getPackageName(), 0).getResources();
486                        if (res.getBoolean(imi.getIsDefaultResourceId())) {
487                            defIm = imi;
488                            Slog.i(TAG, "Selected default: " + imi.getId());
489                        }
490                    } catch (PackageManager.NameNotFoundException ex) {
491                    } catch (Resources.NotFoundException ex) {
492                    }
493                }
494            }
495            if (defIm == null && N > 0) {
496                defIm = mMethodList.get(0);
497                Slog.i(TAG, "No default found, using " + defIm.getId());
498            }
499            Settings.Secure.putString(mContext.getContentResolver(),
500                    Settings.Secure.ENABLED_INPUT_METHODS, sb.toString());
501            if (defIm != null) {
502                Settings.Secure.putString(mContext.getContentResolver(),
503                        Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId());
504            }
505        }
506
507        mStatusBar = statusBar;
508        statusBar.setIconVisibility("ime", false);
509
510        mSettingsObserver = new SettingsObserver(mHandler);
511        updateFromSettingsLocked();
512    }
513
514    @Override
515    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
516            throws RemoteException {
517        try {
518            return super.onTransact(code, data, reply, flags);
519        } catch (RuntimeException e) {
520            // The input method manager only throws security exceptions, so let's
521            // log all others.
522            if (!(e instanceof SecurityException)) {
523                Slog.e(TAG, "Input Method Manager Crash", e);
524            }
525            throw e;
526        }
527    }
528
529    public void systemReady() {
530        synchronized (mMethodMap) {
531            if (!mSystemReady) {
532                mSystemReady = true;
533                try {
534                    startInputInnerLocked();
535                } catch (RuntimeException e) {
536                    Slog.w(TAG, "Unexpected exception", e);
537                }
538            }
539        }
540    }
541
542    public List<InputMethodInfo> getInputMethodList() {
543        synchronized (mMethodMap) {
544            return new ArrayList<InputMethodInfo>(mMethodList);
545        }
546    }
547
548    public List<InputMethodInfo> getEnabledInputMethodList() {
549        synchronized (mMethodMap) {
550            return getEnabledInputMethodListLocked();
551        }
552    }
553
554    List<InputMethodInfo> getEnabledInputMethodListLocked() {
555        final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
556
557        final String enabledStr = Settings.Secure.getString(
558                mContext.getContentResolver(),
559                Settings.Secure.ENABLED_INPUT_METHODS);
560        if (enabledStr != null) {
561            final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
562            splitter.setString(enabledStr);
563
564            while (splitter.hasNext()) {
565                InputMethodInfo info = mMethodMap.get(splitter.next());
566                if (info != null) {
567                    res.add(info);
568                }
569            }
570        }
571
572        return res;
573    }
574
575    public void addClient(IInputMethodClient client,
576            IInputContext inputContext, int uid, int pid) {
577        synchronized (mMethodMap) {
578            mClients.put(client.asBinder(), new ClientState(client,
579                    inputContext, uid, pid));
580        }
581    }
582
583    public void removeClient(IInputMethodClient client) {
584        synchronized (mMethodMap) {
585            mClients.remove(client.asBinder());
586        }
587    }
588
589    void executeOrSendMessage(IInterface target, Message msg) {
590         if (target.asBinder() instanceof Binder) {
591             mCaller.sendMessage(msg);
592         } else {
593             handleMessage(msg);
594             msg.recycle();
595         }
596    }
597
598    void unbindCurrentClientLocked() {
599        if (mCurClient != null) {
600            if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
601                    + mCurClient.client.asBinder());
602            if (mBoundToMethod) {
603                mBoundToMethod = false;
604                if (mCurMethod != null) {
605                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
606                            MSG_UNBIND_INPUT, mCurMethod));
607                }
608            }
609            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
610                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
611            mCurClient.sessionRequested = false;
612
613            // Call setActive(false) on the old client
614            try {
615                mCurClient.client.setActive(false);
616            } catch (RemoteException e) {
617                Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
618                        + mCurClient.pid + " uid " + mCurClient.uid);
619            }
620            mCurClient = null;
621
622            hideInputMethodMenuLocked();
623        }
624    }
625
626    private int getImeShowFlags() {
627        int flags = 0;
628        if (mShowForced) {
629            flags |= InputMethod.SHOW_FORCED
630                    | InputMethod.SHOW_EXPLICIT;
631        } else if (mShowExplicitlyRequested) {
632            flags |= InputMethod.SHOW_EXPLICIT;
633        }
634        return flags;
635    }
636
637    private int getAppShowFlags() {
638        int flags = 0;
639        if (mShowForced) {
640            flags |= InputMethodManager.SHOW_FORCED;
641        } else if (!mShowExplicitlyRequested) {
642            flags |= InputMethodManager.SHOW_IMPLICIT;
643        }
644        return flags;
645    }
646
647    InputBindResult attachNewInputLocked(boolean initial, boolean needResult) {
648        if (!mBoundToMethod) {
649            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
650                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
651            mBoundToMethod = true;
652        }
653        final SessionState session = mCurClient.curSession;
654        if (initial) {
655            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
656                    MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
657        } else {
658            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
659                    MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
660        }
661        if (mShowRequested) {
662            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
663            showCurrentInputLocked(getAppShowFlags(), null);
664        }
665        return needResult
666                ? new InputBindResult(session.session, mCurId, mCurSeq)
667                : null;
668    }
669
670    InputBindResult startInputLocked(IInputMethodClient client,
671            IInputContext inputContext, EditorInfo attribute,
672            boolean initial, boolean needResult) {
673        // If no method is currently selected, do nothing.
674        if (mCurMethodId == null) {
675            return mNoBinding;
676        }
677
678        ClientState cs = mClients.get(client.asBinder());
679        if (cs == null) {
680            throw new IllegalArgumentException("unknown client "
681                    + client.asBinder());
682        }
683
684        try {
685            if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
686                // Check with the window manager to make sure this client actually
687                // has a window with focus.  If not, reject.  This is thread safe
688                // because if the focus changes some time before or after, the
689                // next client receiving focus that has any interest in input will
690                // be calling through here after that change happens.
691                Slog.w(TAG, "Starting input on non-focused client " + cs.client
692                        + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
693                return null;
694            }
695        } catch (RemoteException e) {
696        }
697
698        if (mCurClient != cs) {
699            // If the client is changing, we need to switch over to the new
700            // one.
701            unbindCurrentClientLocked();
702            if (DEBUG) Slog.v(TAG, "switching to client: client = "
703                    + cs.client.asBinder());
704
705            // If the screen is on, inform the new client it is active
706            if (mScreenOn) {
707                try {
708                    cs.client.setActive(mScreenOn);
709                } catch (RemoteException e) {
710                    Slog.w(TAG, "Got RemoteException sending setActive notification to pid "
711                            + cs.pid + " uid " + cs.uid);
712                }
713            }
714        }
715
716        // Bump up the sequence for this client and attach it.
717        mCurSeq++;
718        if (mCurSeq <= 0) mCurSeq = 1;
719        mCurClient = cs;
720        mCurInputContext = inputContext;
721        mCurAttribute = attribute;
722
723        // Check if the input method is changing.
724        if (mCurId != null && mCurId.equals(mCurMethodId)) {
725            if (cs.curSession != null) {
726                // Fast case: if we are already connected to the input method,
727                // then just return it.
728                return attachNewInputLocked(initial, needResult);
729            }
730            if (mHaveConnection) {
731                if (mCurMethod != null) {
732                    if (!cs.sessionRequested) {
733                        cs.sessionRequested = true;
734                        if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
735                        executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
736                                MSG_CREATE_SESSION, mCurMethod,
737                                new MethodCallback(mCurMethod)));
738                    }
739                    // Return to client, and we will get back with it when
740                    // we have had a session made for it.
741                    return new InputBindResult(null, mCurId, mCurSeq);
742                } else if (SystemClock.uptimeMillis()
743                        < (mLastBindTime+TIME_TO_RECONNECT)) {
744                    // In this case we have connected to the service, but
745                    // don't yet have its interface.  If it hasn't been too
746                    // long since we did the connection, we'll return to
747                    // the client and wait to get the service interface so
748                    // we can report back.  If it has been too long, we want
749                    // to fall through so we can try a disconnect/reconnect
750                    // to see if we can get back in touch with the service.
751                    return new InputBindResult(null, mCurId, mCurSeq);
752                } else {
753                    EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
754                            mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
755                }
756            }
757        }
758
759        return startInputInnerLocked();
760    }
761
762    InputBindResult startInputInnerLocked() {
763        if (mCurMethodId == null) {
764            return mNoBinding;
765        }
766
767        if (!mSystemReady) {
768            // If the system is not yet ready, we shouldn't be running third
769            // party code.
770            return new InputBindResult(null, mCurMethodId, mCurSeq);
771        }
772
773        InputMethodInfo info = mMethodMap.get(mCurMethodId);
774        if (info == null) {
775            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
776        }
777
778        unbindCurrentMethodLocked(false);
779
780        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
781        mCurIntent.setComponent(info.getComponent());
782        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
783                com.android.internal.R.string.input_method_binding_label);
784        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
785                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
786        if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {
787            mLastBindTime = SystemClock.uptimeMillis();
788            mHaveConnection = true;
789            mCurId = info.getId();
790            mCurToken = new Binder();
791            try {
792                if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
793                mIWindowManager.addWindowToken(mCurToken,
794                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
795            } catch (RemoteException e) {
796            }
797            return new InputBindResult(null, mCurId, mCurSeq);
798        } else {
799            mCurIntent = null;
800            Slog.w(TAG, "Failure connecting to input method service: "
801                    + mCurIntent);
802        }
803        return null;
804    }
805
806    public InputBindResult startInput(IInputMethodClient client,
807            IInputContext inputContext, EditorInfo attribute,
808            boolean initial, boolean needResult) {
809        synchronized (mMethodMap) {
810            final long ident = Binder.clearCallingIdentity();
811            try {
812                return startInputLocked(client, inputContext, attribute,
813                        initial, needResult);
814            } finally {
815                Binder.restoreCallingIdentity(ident);
816            }
817        }
818    }
819
820    public void finishInput(IInputMethodClient client) {
821    }
822
823    public void onServiceConnected(ComponentName name, IBinder service) {
824        synchronized (mMethodMap) {
825            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
826                mCurMethod = IInputMethod.Stub.asInterface(service);
827                if (mCurToken == null) {
828                    Slog.w(TAG, "Service connected without a token!");
829                    unbindCurrentMethodLocked(false);
830                    return;
831                }
832                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
833                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
834                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
835                if (mCurClient != null) {
836                    if (DEBUG) Slog.v(TAG, "Creating first session while with client "
837                            + mCurClient);
838                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
839                            MSG_CREATE_SESSION, mCurMethod,
840                            new MethodCallback(mCurMethod)));
841                }
842            }
843        }
844    }
845
846    void onSessionCreated(IInputMethod method, IInputMethodSession session) {
847        synchronized (mMethodMap) {
848            if (mCurMethod != null && method != null
849                    && mCurMethod.asBinder() == method.asBinder()) {
850                if (mCurClient != null) {
851                    mCurClient.curSession = new SessionState(mCurClient,
852                            method, session);
853                    mCurClient.sessionRequested = false;
854                    InputBindResult res = attachNewInputLocked(true, true);
855                    if (res.method != null) {
856                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
857                                MSG_BIND_METHOD, mCurClient.client, res));
858                    }
859                }
860            }
861        }
862    }
863
864    void unbindCurrentMethodLocked(boolean reportToClient) {
865        if (mHaveConnection) {
866            mContext.unbindService(this);
867            mHaveConnection = false;
868        }
869
870        if (mCurToken != null) {
871            try {
872                if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
873                mIWindowManager.removeWindowToken(mCurToken);
874            } catch (RemoteException e) {
875            }
876            mCurToken = null;
877        }
878
879        mCurId = null;
880        clearCurMethodLocked();
881
882        if (reportToClient && mCurClient != null) {
883            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
884                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
885        }
886    }
887
888    private void finishSession(SessionState sessionState) {
889        if (sessionState != null && sessionState.session != null) {
890            try {
891                sessionState.session.finishSession();
892            } catch (RemoteException e) {
893                Slog.w(TAG, "Session failed to close due to remote exception", e);
894            }
895        }
896    }
897
898    void clearCurMethodLocked() {
899        if (mCurMethod != null) {
900            for (ClientState cs : mClients.values()) {
901                cs.sessionRequested = false;
902                finishSession(cs.curSession);
903                cs.curSession = null;
904            }
905
906            finishSession(mEnabledSession);
907            mEnabledSession = null;
908            mCurMethod = null;
909        }
910        mStatusBar.setIconVisibility("ime", false);
911    }
912
913    public void onServiceDisconnected(ComponentName name) {
914        synchronized (mMethodMap) {
915            if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
916                    + " mCurIntent=" + mCurIntent);
917            if (mCurMethod != null && mCurIntent != null
918                    && name.equals(mCurIntent.getComponent())) {
919                clearCurMethodLocked();
920                // We consider this to be a new bind attempt, since the system
921                // should now try to restart the service for us.
922                mLastBindTime = SystemClock.uptimeMillis();
923                mShowRequested = mInputShown;
924                mInputShown = false;
925                if (mCurClient != null) {
926                    executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
927                            MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
928                }
929            }
930        }
931    }
932
933    public void updateStatusIcon(IBinder token, String packageName, int iconId) {
934        long ident = Binder.clearCallingIdentity();
935        try {
936            if (token == null || mCurToken != token) {
937                Slog.w(TAG, "Ignoring setInputMethod of token: " + token);
938                return;
939            }
940
941            synchronized (mMethodMap) {
942                if (iconId == 0) {
943                    if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
944                    mStatusBar.setIconVisibility("ime", false);
945                } else if (packageName != null) {
946                    if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
947                    mStatusBar.setIcon("ime", packageName, iconId, 0);
948                    mStatusBar.setIconVisibility("ime", true);
949                }
950            }
951        } finally {
952            Binder.restoreCallingIdentity(ident);
953        }
954    }
955
956    void updateFromSettingsLocked() {
957        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
958        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
959        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
960        // enabled.
961        String id = Settings.Secure.getString(mContext.getContentResolver(),
962            Settings.Secure.DEFAULT_INPUT_METHOD);
963        if (id != null && id.length() > 0) {
964            try {
965                setInputMethodLocked(id);
966            } catch (IllegalArgumentException e) {
967                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
968                mCurMethodId = null;
969                unbindCurrentMethodLocked(true);
970            }
971        } else {
972            // There is no longer an input method set, so stop any current one.
973            mCurMethodId = null;
974            unbindCurrentMethodLocked(true);
975        }
976    }
977
978    void setInputMethodLocked(String id) {
979        InputMethodInfo info = mMethodMap.get(id);
980        if (info == null) {
981            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
982        }
983
984        if (id.equals(mCurMethodId)) {
985            return;
986        }
987
988        final long ident = Binder.clearCallingIdentity();
989        try {
990            mCurMethodId = id;
991            Settings.Secure.putString(mContext.getContentResolver(),
992                Settings.Secure.DEFAULT_INPUT_METHOD, id);
993
994            if (ActivityManagerNative.isSystemReady()) {
995                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
996                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
997                intent.putExtra("input_method_id", id);
998                mContext.sendBroadcast(intent);
999            }
1000            unbindCurrentClientLocked();
1001        } finally {
1002            Binder.restoreCallingIdentity(ident);
1003        }
1004    }
1005
1006    public boolean showSoftInput(IInputMethodClient client, int flags,
1007            ResultReceiver resultReceiver) {
1008        long ident = Binder.clearCallingIdentity();
1009        try {
1010            synchronized (mMethodMap) {
1011                if (mCurClient == null || client == null
1012                        || mCurClient.client.asBinder() != client.asBinder()) {
1013                    try {
1014                        // We need to check if this is the current client with
1015                        // focus in the window manager, to allow this call to
1016                        // be made before input is started in it.
1017                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1018                            Slog.w(TAG, "Ignoring showSoftInput of: " + client);
1019                            return false;
1020                        }
1021                    } catch (RemoteException e) {
1022                        return false;
1023                    }
1024                }
1025
1026                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
1027                return showCurrentInputLocked(flags, resultReceiver);
1028            }
1029        } finally {
1030            Binder.restoreCallingIdentity(ident);
1031        }
1032    }
1033
1034    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1035        mShowRequested = true;
1036        if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1037            mShowExplicitlyRequested = true;
1038        }
1039        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1040            mShowExplicitlyRequested = true;
1041            mShowForced = true;
1042        }
1043
1044        if (!mSystemReady) {
1045            return false;
1046        }
1047
1048        boolean res = false;
1049        if (mCurMethod != null) {
1050            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1051                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1052                    resultReceiver));
1053            mInputShown = true;
1054            res = true;
1055        } else if (mHaveConnection && SystemClock.uptimeMillis()
1056                < (mLastBindTime+TIME_TO_RECONNECT)) {
1057            // The client has asked to have the input method shown, but
1058            // we have been sitting here too long with a connection to the
1059            // service and no interface received, so let's disconnect/connect
1060            // to try to prod things along.
1061            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
1062                    SystemClock.uptimeMillis()-mLastBindTime,1);
1063            mContext.unbindService(this);
1064            mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE);
1065        }
1066
1067        return res;
1068    }
1069
1070    public boolean hideSoftInput(IInputMethodClient client, int flags,
1071            ResultReceiver resultReceiver) {
1072        long ident = Binder.clearCallingIdentity();
1073        try {
1074            synchronized (mMethodMap) {
1075                if (mCurClient == null || client == null
1076                        || mCurClient.client.asBinder() != client.asBinder()) {
1077                    try {
1078                        // We need to check if this is the current client with
1079                        // focus in the window manager, to allow this call to
1080                        // be made before input is started in it.
1081                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1082                            Slog.w(TAG, "Ignoring hideSoftInput of: " + client);
1083                            return false;
1084                        }
1085                    } catch (RemoteException e) {
1086                        return false;
1087                    }
1088                }
1089
1090                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
1091                return hideCurrentInputLocked(flags, resultReceiver);
1092            }
1093        } finally {
1094            Binder.restoreCallingIdentity(ident);
1095        }
1096    }
1097
1098    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1099        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1100                && (mShowExplicitlyRequested || mShowForced)) {
1101            if (DEBUG) Slog.v(TAG,
1102                    "Not hiding: explicit show not cancelled by non-explicit hide");
1103            return false;
1104        }
1105        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1106            if (DEBUG) Slog.v(TAG,
1107                    "Not hiding: forced show not cancelled by not-always hide");
1108            return false;
1109        }
1110        boolean res;
1111        if (mInputShown && mCurMethod != null) {
1112            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1113                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1114            res = true;
1115        } else {
1116            res = false;
1117        }
1118        mInputShown = false;
1119        mShowRequested = false;
1120        mShowExplicitlyRequested = false;
1121        mShowForced = false;
1122        return res;
1123    }
1124
1125    public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
1126            boolean viewHasFocus, boolean isTextEditor, int softInputMode,
1127            boolean first, int windowFlags) {
1128        long ident = Binder.clearCallingIdentity();
1129        try {
1130            synchronized (mMethodMap) {
1131                if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
1132                        + " viewHasFocus=" + viewHasFocus
1133                        + " isTextEditor=" + isTextEditor
1134                        + " softInputMode=#" + Integer.toHexString(softInputMode)
1135                        + " first=" + first + " flags=#"
1136                        + Integer.toHexString(windowFlags));
1137
1138                if (mCurClient == null || client == null
1139                        || mCurClient.client.asBinder() != client.asBinder()) {
1140                    try {
1141                        // We need to check if this is the current client with
1142                        // focus in the window manager, to allow this call to
1143                        // be made before input is started in it.
1144                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1145                            Slog.w(TAG, "Client not active, ignoring focus gain of: " + client);
1146                            return;
1147                        }
1148                    } catch (RemoteException e) {
1149                    }
1150                }
1151
1152                if (mCurFocusedWindow == windowToken) {
1153                    Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
1154                    return;
1155                }
1156                mCurFocusedWindow = windowToken;
1157
1158                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1159                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
1160                        if (!isTextEditor || (softInputMode &
1161                                WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1162                                != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
1163                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1164                                // There is no focus view, and this window will
1165                                // be behind any soft input window, so hide the
1166                                // soft input window if it is shown.
1167                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
1168                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
1169                            }
1170                        } else if (isTextEditor && (softInputMode &
1171                                WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1172                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1173                                && (softInputMode &
1174                                        WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1175                            // There is a focus view, and we are navigating forward
1176                            // into the window, so show the input window for the user.
1177                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
1178                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1179                        }
1180                        break;
1181                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1182                        // Do nothing.
1183                        break;
1184                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1185                        if ((softInputMode &
1186                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1187                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
1188                            hideCurrentInputLocked(0, null);
1189                        }
1190                        break;
1191                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
1192                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
1193                        hideCurrentInputLocked(0, null);
1194                        break;
1195                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1196                        if ((softInputMode &
1197                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1198                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
1199                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1200                        }
1201                        break;
1202                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
1203                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
1204                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1205                        break;
1206                }
1207            }
1208        } finally {
1209            Binder.restoreCallingIdentity(ident);
1210        }
1211    }
1212
1213    public void showInputMethodPickerFromClient(IInputMethodClient client) {
1214        synchronized (mMethodMap) {
1215            if (mCurClient == null || client == null
1216                    || mCurClient.client.asBinder() != client.asBinder()) {
1217                Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of: " + client);
1218            }
1219
1220            mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
1221        }
1222    }
1223
1224    public void setInputMethod(IBinder token, String id) {
1225        synchronized (mMethodMap) {
1226            if (token == null) {
1227                if (mContext.checkCallingOrSelfPermission(
1228                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
1229                        != PackageManager.PERMISSION_GRANTED) {
1230                    throw new SecurityException(
1231                            "Using null token requires permission "
1232                            + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1233                }
1234            } else if (mCurToken != token) {
1235                Slog.w(TAG, "Ignoring setInputMethod of token: " + token);
1236                return;
1237            }
1238
1239            long ident = Binder.clearCallingIdentity();
1240            try {
1241                setInputMethodLocked(id);
1242            } finally {
1243                Binder.restoreCallingIdentity(ident);
1244            }
1245        }
1246    }
1247
1248    public void hideMySoftInput(IBinder token, int flags) {
1249        synchronized (mMethodMap) {
1250            if (token == null || mCurToken != token) {
1251                Slog.w(TAG, "Ignoring hideInputMethod of token: " + token);
1252                return;
1253            }
1254            long ident = Binder.clearCallingIdentity();
1255            try {
1256                hideCurrentInputLocked(flags, null);
1257            } finally {
1258                Binder.restoreCallingIdentity(ident);
1259            }
1260        }
1261    }
1262
1263    public void showMySoftInput(IBinder token, int flags) {
1264        synchronized (mMethodMap) {
1265            if (token == null || mCurToken != token) {
1266                Slog.w(TAG, "Ignoring hideInputMethod of token: " + token);
1267                return;
1268            }
1269            long ident = Binder.clearCallingIdentity();
1270            try {
1271                showCurrentInputLocked(flags, null);
1272            } finally {
1273                Binder.restoreCallingIdentity(ident);
1274            }
1275        }
1276    }
1277
1278    void setEnabledSessionInMainThread(SessionState session) {
1279        if (mEnabledSession != session) {
1280            if (mEnabledSession != null) {
1281                try {
1282                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
1283                    mEnabledSession.method.setSessionEnabled(
1284                            mEnabledSession.session, false);
1285                } catch (RemoteException e) {
1286                }
1287            }
1288            mEnabledSession = session;
1289            try {
1290                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
1291                session.method.setSessionEnabled(
1292                        session.session, true);
1293            } catch (RemoteException e) {
1294            }
1295        }
1296    }
1297
1298    public boolean handleMessage(Message msg) {
1299        HandlerCaller.SomeArgs args;
1300        switch (msg.what) {
1301            case MSG_SHOW_IM_PICKER:
1302                showInputMethodMenu();
1303                return true;
1304
1305            // ---------------------------------------------------------
1306
1307            case MSG_UNBIND_INPUT:
1308                try {
1309                    ((IInputMethod)msg.obj).unbindInput();
1310                } catch (RemoteException e) {
1311                    // There is nothing interesting about the method dying.
1312                }
1313                return true;
1314            case MSG_BIND_INPUT:
1315                args = (HandlerCaller.SomeArgs)msg.obj;
1316                try {
1317                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1318                } catch (RemoteException e) {
1319                }
1320                return true;
1321            case MSG_SHOW_SOFT_INPUT:
1322                args = (HandlerCaller.SomeArgs)msg.obj;
1323                try {
1324                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
1325                            (ResultReceiver)args.arg2);
1326                } catch (RemoteException e) {
1327                }
1328                return true;
1329            case MSG_HIDE_SOFT_INPUT:
1330                args = (HandlerCaller.SomeArgs)msg.obj;
1331                try {
1332                    ((IInputMethod)args.arg1).hideSoftInput(0,
1333                            (ResultReceiver)args.arg2);
1334                } catch (RemoteException e) {
1335                }
1336                return true;
1337            case MSG_ATTACH_TOKEN:
1338                args = (HandlerCaller.SomeArgs)msg.obj;
1339                try {
1340                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
1341                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1342                } catch (RemoteException e) {
1343                }
1344                return true;
1345            case MSG_CREATE_SESSION:
1346                args = (HandlerCaller.SomeArgs)msg.obj;
1347                try {
1348                    ((IInputMethod)args.arg1).createSession(
1349                            (IInputMethodCallback)args.arg2);
1350                } catch (RemoteException e) {
1351                }
1352                return true;
1353            // ---------------------------------------------------------
1354
1355            case MSG_START_INPUT:
1356                args = (HandlerCaller.SomeArgs)msg.obj;
1357                try {
1358                    SessionState session = (SessionState)args.arg1;
1359                    setEnabledSessionInMainThread(session);
1360                    session.method.startInput((IInputContext)args.arg2,
1361                            (EditorInfo)args.arg3);
1362                } catch (RemoteException e) {
1363                }
1364                return true;
1365            case MSG_RESTART_INPUT:
1366                args = (HandlerCaller.SomeArgs)msg.obj;
1367                try {
1368                    SessionState session = (SessionState)args.arg1;
1369                    setEnabledSessionInMainThread(session);
1370                    session.method.restartInput((IInputContext)args.arg2,
1371                            (EditorInfo)args.arg3);
1372                } catch (RemoteException e) {
1373                }
1374                return true;
1375
1376            // ---------------------------------------------------------
1377
1378            case MSG_UNBIND_METHOD:
1379                try {
1380                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1381                } catch (RemoteException e) {
1382                    // There is nothing interesting about the last client dying.
1383                }
1384                return true;
1385            case MSG_BIND_METHOD:
1386                args = (HandlerCaller.SomeArgs)msg.obj;
1387                try {
1388                    ((IInputMethodClient)args.arg1).onBindMethod(
1389                            (InputBindResult)args.arg2);
1390                } catch (RemoteException e) {
1391                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
1392                }
1393                return true;
1394        }
1395        return false;
1396    }
1397
1398    private boolean isSystemIme(InputMethodInfo inputMethod) {
1399        return (inputMethod.getServiceInfo().applicationInfo.flags
1400                & ApplicationInfo.FLAG_SYSTEM) != 0;
1401    }
1402
1403    private boolean chooseNewDefaultIMELocked() {
1404        List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
1405        if (enabled != null && enabled.size() > 0) {
1406            // We'd prefer to fall back on a system IME, since that is safer.
1407            int i=enabled.size();
1408            while (i > 0) {
1409                i--;
1410                if ((enabled.get(i).getServiceInfo().applicationInfo.flags
1411                        & ApplicationInfo.FLAG_SYSTEM) != 0) {
1412                    break;
1413                }
1414            }
1415            Settings.Secure.putString(mContext.getContentResolver(),
1416                    Settings.Secure.DEFAULT_INPUT_METHOD,
1417                    enabled.get(i).getId());
1418            return true;
1419        }
1420
1421        return false;
1422    }
1423
1424    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
1425            HashMap<String, InputMethodInfo> map) {
1426        list.clear();
1427        map.clear();
1428
1429        PackageManager pm = mContext.getPackageManager();
1430        final Configuration config = mContext.getResources().getConfiguration();
1431        final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
1432        String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
1433                Secure.DISABLED_SYSTEM_INPUT_METHODS);
1434        if (disabledSysImes == null) disabledSysImes = "";
1435
1436        List<ResolveInfo> services = pm.queryIntentServices(
1437                new Intent(InputMethod.SERVICE_INTERFACE),
1438                PackageManager.GET_META_DATA);
1439
1440        for (int i = 0; i < services.size(); ++i) {
1441            ResolveInfo ri = services.get(i);
1442            ServiceInfo si = ri.serviceInfo;
1443            ComponentName compName = new ComponentName(si.packageName, si.name);
1444            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
1445                    si.permission)) {
1446                Slog.w(TAG, "Skipping input method " + compName
1447                        + ": it does not require the permission "
1448                        + android.Manifest.permission.BIND_INPUT_METHOD);
1449                continue;
1450            }
1451
1452            if (DEBUG) Slog.d(TAG, "Checking " + compName);
1453
1454            try {
1455                InputMethodInfo p = new InputMethodInfo(mContext, ri);
1456                list.add(p);
1457                final String id = p.getId();
1458                map.put(id, p);
1459
1460                // System IMEs are enabled by default, unless there's a hard keyboard
1461                // and the system IME was explicitly disabled
1462                if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
1463                    setInputMethodEnabledLocked(id, true);
1464                }
1465
1466                if (DEBUG) {
1467                    Slog.d(TAG, "Found a third-party input method " + p);
1468                }
1469
1470            } catch (XmlPullParserException e) {
1471                Slog.w(TAG, "Unable to load input method " + compName, e);
1472            } catch (IOException e) {
1473                Slog.w(TAG, "Unable to load input method " + compName, e);
1474            }
1475        }
1476
1477        String defaultIme = Settings.Secure.getString(mContext
1478                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1479        if (!map.containsKey(defaultIme)) {
1480            if (chooseNewDefaultIMELocked()) {
1481                updateFromSettingsLocked();
1482            }
1483        }
1484    }
1485
1486    // ----------------------------------------------------------------------
1487
1488    void showInputMethodMenu() {
1489        if (DEBUG) Slog.v(TAG, "Show switching menu");
1490
1491        final Context context = mContext;
1492
1493        final PackageManager pm = context.getPackageManager();
1494
1495        String lastInputMethodId = Settings.Secure.getString(context
1496                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1497        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
1498
1499        final List<InputMethodInfo> immis = getEnabledInputMethodList();
1500
1501        if (immis == null) {
1502            return;
1503        }
1504
1505        synchronized (mMethodMap) {
1506            hideInputMethodMenuLocked();
1507
1508            int N = immis.size();
1509
1510            mItems = new CharSequence[N];
1511            mIms = new InputMethodInfo[N];
1512
1513            int j = 0;
1514            for (int i = 0; i < N; ++i) {
1515                InputMethodInfo property = immis.get(i);
1516                if (property == null) {
1517                    continue;
1518                }
1519                mItems[j] = property.loadLabel(pm);
1520                mIms[j] = property;
1521                j++;
1522            }
1523
1524            int checkedItem = 0;
1525            for (int i = 0; i < N; ++i) {
1526                if (mIms[i].getId().equals(lastInputMethodId)) {
1527                    checkedItem = i;
1528                    break;
1529                }
1530            }
1531
1532            AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
1533                public void onClick(DialogInterface dialog, int which) {
1534                    hideInputMethodMenu();
1535                }
1536            };
1537
1538            TypedArray a = context.obtainStyledAttributes(null,
1539                    com.android.internal.R.styleable.DialogPreference,
1540                    com.android.internal.R.attr.alertDialogStyle, 0);
1541            mDialogBuilder = new AlertDialog.Builder(context)
1542                    .setTitle(com.android.internal.R.string.select_input_method)
1543                    .setOnCancelListener(new OnCancelListener() {
1544                        public void onCancel(DialogInterface dialog) {
1545                            hideInputMethodMenu();
1546                        }
1547                    })
1548                    .setIcon(a.getDrawable(
1549                            com.android.internal.R.styleable.DialogPreference_dialogTitle));
1550            a.recycle();
1551
1552            mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
1553                    new AlertDialog.OnClickListener() {
1554                        public void onClick(DialogInterface dialog, int which) {
1555                            synchronized (mMethodMap) {
1556                                if (mIms == null || mIms.length <= which) {
1557                                    return;
1558                                }
1559                                InputMethodInfo im = mIms[which];
1560                                hideInputMethodMenu();
1561                                if (im != null) {
1562                                    setInputMethodLocked(im.getId());
1563                                }
1564                            }
1565                        }
1566                    });
1567
1568            mSwitchingDialog = mDialogBuilder.create();
1569            mSwitchingDialog.getWindow().setType(
1570                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1571            mSwitchingDialog.show();
1572        }
1573    }
1574
1575    void hideInputMethodMenu() {
1576        synchronized (mMethodMap) {
1577            hideInputMethodMenuLocked();
1578        }
1579    }
1580
1581    void hideInputMethodMenuLocked() {
1582        if (DEBUG) Slog.v(TAG, "Hide switching menu");
1583
1584        if (mSwitchingDialog != null) {
1585            mSwitchingDialog.dismiss();
1586            mSwitchingDialog = null;
1587        }
1588
1589        mDialogBuilder = null;
1590        mItems = null;
1591        mIms = null;
1592    }
1593
1594    // ----------------------------------------------------------------------
1595
1596    public boolean setInputMethodEnabled(String id, boolean enabled) {
1597        synchronized (mMethodMap) {
1598            if (mContext.checkCallingOrSelfPermission(
1599                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
1600                    != PackageManager.PERMISSION_GRANTED) {
1601                throw new SecurityException(
1602                        "Requires permission "
1603                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1604            }
1605
1606            long ident = Binder.clearCallingIdentity();
1607            try {
1608                return setInputMethodEnabledLocked(id, enabled);
1609            } finally {
1610                Binder.restoreCallingIdentity(ident);
1611            }
1612        }
1613    }
1614
1615    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
1616        // Make sure this is a valid input method.
1617        InputMethodInfo imm = mMethodMap.get(id);
1618        if (imm == null) {
1619            if (imm == null) {
1620                throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1621            }
1622        }
1623
1624        StringBuilder builder = new StringBuilder(256);
1625
1626        boolean removed = false;
1627        String firstId = null;
1628
1629        // Look through the currently enabled input methods.
1630        String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
1631                Settings.Secure.ENABLED_INPUT_METHODS);
1632        if (enabledStr != null) {
1633            final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
1634            splitter.setString(enabledStr);
1635            while (splitter.hasNext()) {
1636                String curId = splitter.next();
1637                if (curId.equals(id)) {
1638                    if (enabled) {
1639                        // We are enabling this input method, but it is
1640                        // already enabled.  Nothing to do.  The previous
1641                        // state was enabled.
1642                        return true;
1643                    }
1644                    // We are disabling this input method, and it is
1645                    // currently enabled.  Skip it to remove from the
1646                    // new list.
1647                    removed = true;
1648                } else if (!enabled) {
1649                    // We are building a new list of input methods that
1650                    // doesn't contain the given one.
1651                    if (firstId == null) firstId = curId;
1652                    if (builder.length() > 0) builder.append(':');
1653                    builder.append(curId);
1654                }
1655            }
1656        }
1657
1658        if (!enabled) {
1659            if (!removed) {
1660                // We are disabling the input method but it is already
1661                // disabled.  Nothing to do.  The previous state was
1662                // disabled.
1663                return false;
1664            }
1665            // Update the setting with the new list of input methods.
1666            Settings.Secure.putString(mContext.getContentResolver(),
1667                    Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
1668            // We the disabled input method is currently selected, switch
1669            // to another one.
1670            String selId = Settings.Secure.getString(mContext.getContentResolver(),
1671                    Settings.Secure.DEFAULT_INPUT_METHOD);
1672            if (id.equals(selId)) {
1673                Settings.Secure.putString(mContext.getContentResolver(),
1674                        Settings.Secure.DEFAULT_INPUT_METHOD,
1675                        firstId != null ? firstId : "");
1676            }
1677            // Previous state was enabled.
1678            return true;
1679        }
1680
1681        // Add in the newly enabled input method.
1682        if (enabledStr == null || enabledStr.length() == 0) {
1683            enabledStr = id;
1684        } else {
1685            enabledStr = enabledStr + ':' + id;
1686        }
1687
1688        Settings.Secure.putString(mContext.getContentResolver(),
1689                Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
1690
1691        // Previous state was disabled.
1692        return false;
1693    }
1694
1695    // ----------------------------------------------------------------------
1696
1697    @Override
1698    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1699        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1700                != PackageManager.PERMISSION_GRANTED) {
1701
1702            pw.println("Permission Denial: can't dump InputMethodManager from from pid="
1703                    + Binder.getCallingPid()
1704                    + ", uid=" + Binder.getCallingUid());
1705            return;
1706        }
1707
1708        IInputMethod method;
1709        ClientState client;
1710
1711        final Printer p = new PrintWriterPrinter(pw);
1712
1713        synchronized (mMethodMap) {
1714            p.println("Current Input Method Manager state:");
1715            int N = mMethodList.size();
1716            p.println("  Input Methods:");
1717            for (int i=0; i<N; i++) {
1718                InputMethodInfo info = mMethodList.get(i);
1719                p.println("  InputMethod #" + i + ":");
1720                info.dump(p, "    ");
1721            }
1722            p.println("  Clients:");
1723            for (ClientState ci : mClients.values()) {
1724                p.println("  Client " + ci + ":");
1725                p.println("    client=" + ci.client);
1726                p.println("    inputContext=" + ci.inputContext);
1727                p.println("    sessionRequested=" + ci.sessionRequested);
1728                p.println("    curSession=" + ci.curSession);
1729            }
1730            p.println("  mCurMethodId=" + mCurMethodId);
1731            client = mCurClient;
1732            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
1733            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
1734            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
1735                    + " mBoundToMethod=" + mBoundToMethod);
1736            p.println("  mCurToken=" + mCurToken);
1737            p.println("  mCurIntent=" + mCurIntent);
1738            method = mCurMethod;
1739            p.println("  mCurMethod=" + mCurMethod);
1740            p.println("  mEnabledSession=" + mEnabledSession);
1741            p.println("  mShowRequested=" + mShowRequested
1742                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
1743                    + " mShowForced=" + mShowForced
1744                    + " mInputShown=" + mInputShown);
1745            p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
1746        }
1747
1748        if (client != null) {
1749            p.println(" ");
1750            pw.flush();
1751            try {
1752                client.client.asBinder().dump(fd, args);
1753            } catch (RemoteException e) {
1754                p.println("Input method client dead: " + e);
1755            }
1756        }
1757
1758        if (method != null) {
1759            p.println(" ");
1760            pw.flush();
1761            try {
1762                method.asBinder().dump(fd, args);
1763            } catch (RemoteException e) {
1764                p.println("Input method service dead: " + e);
1765            }
1766        }
1767    }
1768}
1769