InputMethodManagerService.java revision f3db1af8d55ab247b6db67baf4fe772c18f33cab
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.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.Parcelable;
59import android.os.RemoteException;
60import android.os.ResultReceiver;
61import android.os.ServiceManager;
62import android.os.SystemClock;
63import android.provider.Settings;
64import android.provider.Settings.Secure;
65import android.provider.Settings.SettingNotFoundException;
66import android.text.TextUtils;
67import android.util.EventLog;
68import android.util.Pair;
69import android.util.Slog;
70import android.util.PrintWriterPrinter;
71import android.util.Printer;
72import android.view.IWindowManager;
73import android.view.WindowManager;
74import android.view.inputmethod.EditorInfo;
75import android.view.inputmethod.InputBinding;
76import android.view.inputmethod.InputMethod;
77import android.view.inputmethod.InputMethodInfo;
78import android.view.inputmethod.InputMethodManager;
79import android.view.inputmethod.InputMethodSubtype;
80
81import java.io.FileDescriptor;
82import java.io.IOException;
83import java.io.PrintWriter;
84import java.text.Collator;
85import java.util.ArrayList;
86import java.util.HashMap;
87import java.util.HashSet;
88import java.util.List;
89import java.util.Map;
90import java.util.TreeMap;
91
92/**
93 * This class provides a system service that manages input methods.
94 */
95public class InputMethodManagerService extends IInputMethodManager.Stub
96        implements ServiceConnection, Handler.Callback {
97    static final boolean DEBUG = false;
98    static final String TAG = "InputManagerService";
99
100    static final int MSG_SHOW_IM_PICKER = 1;
101    static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
102    static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
103
104    static final int MSG_UNBIND_INPUT = 1000;
105    static final int MSG_BIND_INPUT = 1010;
106    static final int MSG_SHOW_SOFT_INPUT = 1020;
107    static final int MSG_HIDE_SOFT_INPUT = 1030;
108    static final int MSG_ATTACH_TOKEN = 1040;
109    static final int MSG_CREATE_SESSION = 1050;
110
111    static final int MSG_START_INPUT = 2000;
112    static final int MSG_RESTART_INPUT = 2010;
113
114    static final int MSG_UNBIND_METHOD = 3000;
115    static final int MSG_BIND_METHOD = 3010;
116
117    static final long TIME_TO_RECONNECT = 10*1000;
118
119    private static final int NOT_A_SUBTYPE_ID = -1;
120    private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
121    // If IME doesn't support the system locale, the default subtype will be the first defined one.
122    private static final int DEFAULT_SUBTYPE_ID = 0;
123
124    private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
125    private static final String SUBTYPE_MODE_VOICE = "voice";
126
127    final Context mContext;
128    final Handler mHandler;
129    final InputMethodSettings mSettings;
130    final SettingsObserver mSettingsObserver;
131    final StatusBarManagerService mStatusBar;
132    final IWindowManager mIWindowManager;
133    final HandlerCaller mCaller;
134
135    final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
136
137    // All known input methods.  mMethodMap also serves as the global
138    // lock for this class.
139    final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
140    final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
141
142    class SessionState {
143        final ClientState client;
144        final IInputMethod method;
145        final IInputMethodSession session;
146
147        @Override
148        public String toString() {
149            return "SessionState{uid " + client.uid + " pid " + client.pid
150                    + " method " + Integer.toHexString(
151                            System.identityHashCode(method))
152                    + " session " + Integer.toHexString(
153                            System.identityHashCode(session))
154                    + "}";
155        }
156
157        SessionState(ClientState _client, IInputMethod _method,
158                IInputMethodSession _session) {
159            client = _client;
160            method = _method;
161            session = _session;
162        }
163    }
164
165    class ClientState {
166        final IInputMethodClient client;
167        final IInputContext inputContext;
168        final int uid;
169        final int pid;
170        final InputBinding binding;
171
172        boolean sessionRequested;
173        SessionState curSession;
174
175        @Override
176        public String toString() {
177            return "ClientState{" + Integer.toHexString(
178                    System.identityHashCode(this)) + " uid " + uid
179                    + " pid " + pid + "}";
180        }
181
182        ClientState(IInputMethodClient _client, IInputContext _inputContext,
183                int _uid, int _pid) {
184            client = _client;
185            inputContext = _inputContext;
186            uid = _uid;
187            pid = _pid;
188            binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
189        }
190    }
191
192    final HashMap<IBinder, ClientState> mClients
193            = new HashMap<IBinder, ClientState>();
194
195    /**
196     * Set once the system is ready to run third party code.
197     */
198    boolean mSystemReady;
199
200    /**
201     * Id of the currently selected input method.
202     */
203    String mCurMethodId;
204
205    /**
206     * The current binding sequence number, incremented every time there is
207     * a new bind performed.
208     */
209    int mCurSeq;
210
211    /**
212     * The client that is currently bound to an input method.
213     */
214    ClientState mCurClient;
215
216    /**
217     * The last window token that gained focus.
218     */
219    IBinder mCurFocusedWindow;
220
221    /**
222     * The input context last provided by the current client.
223     */
224    IInputContext mCurInputContext;
225
226    /**
227     * The attributes last provided by the current client.
228     */
229    EditorInfo mCurAttribute;
230
231    /**
232     * The input method ID of the input method service that we are currently
233     * connected to or in the process of connecting to.
234     */
235    String mCurId;
236
237    /**
238     * The current subtype of the current input method.
239     */
240    private InputMethodSubtype mCurrentSubtype;
241
242    // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
243    private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
244            mShortcutInputMethodsAndSubtypes =
245                new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
246
247    /**
248     * Set to true if our ServiceConnection is currently actively bound to
249     * a service (whether or not we have gotten its IBinder back yet).
250     */
251    boolean mHaveConnection;
252
253    /**
254     * Set if the client has asked for the input method to be shown.
255     */
256    boolean mShowRequested;
257
258    /**
259     * Set if we were explicitly told to show the input method.
260     */
261    boolean mShowExplicitlyRequested;
262
263    /**
264     * Set if we were forced to be shown.
265     */
266    boolean mShowForced;
267
268    /**
269     * Set if we last told the input method to show itself.
270     */
271    boolean mInputShown;
272
273    /**
274     * The Intent used to connect to the current input method.
275     */
276    Intent mCurIntent;
277
278    /**
279     * The token we have made for the currently active input method, to
280     * identify it in the future.
281     */
282    IBinder mCurToken;
283
284    /**
285     * If non-null, this is the input method service we are currently connected
286     * to.
287     */
288    IInputMethod mCurMethod;
289
290    /**
291     * Time that we last initiated a bind to the input method, to determine
292     * if we should try to disconnect and reconnect to it.
293     */
294    long mLastBindTime;
295
296    /**
297     * Have we called mCurMethod.bindInput()?
298     */
299    boolean mBoundToMethod;
300
301    /**
302     * Currently enabled session.  Only touched by service thread, not
303     * protected by a lock.
304     */
305    SessionState mEnabledSession;
306
307    /**
308     * True if the screen is on.  The value is true initially.
309     */
310    boolean mScreenOn = true;
311
312    AlertDialog.Builder mDialogBuilder;
313    AlertDialog mSwitchingDialog;
314    InputMethodInfo[] mIms;
315    CharSequence[] mItems;
316    int[] mSubtypeIds;
317
318    class SettingsObserver extends ContentObserver {
319        SettingsObserver(Handler handler) {
320            super(handler);
321            ContentResolver resolver = mContext.getContentResolver();
322            resolver.registerContentObserver(Settings.Secure.getUriFor(
323                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
324            resolver.registerContentObserver(Settings.Secure.getUriFor(
325                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
326        }
327
328        @Override public void onChange(boolean selfChange) {
329            synchronized (mMethodMap) {
330                updateFromSettingsLocked();
331            }
332        }
333    }
334
335    class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
336        @Override
337        public void onReceive(Context context, Intent intent) {
338            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
339                mScreenOn = true;
340            } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
341                mScreenOn = false;
342            } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
343                hideInputMethodMenu();
344                return;
345            } else {
346                Slog.w(TAG, "Unexpected intent " + intent);
347            }
348
349            // Inform the current client of the change in active status
350            try {
351                if (mCurClient != null && mCurClient.client != null) {
352                    mCurClient.client.setActive(mScreenOn);
353                }
354            } catch (RemoteException e) {
355                Slog.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid "
356                        + mCurClient.pid + " uid " + mCurClient.uid);
357            }
358        }
359    }
360
361    class MyPackageMonitor extends PackageMonitor {
362
363        @Override
364        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
365            synchronized (mMethodMap) {
366                String curInputMethodId = Settings.Secure.getString(mContext
367                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
368                final int N = mMethodList.size();
369                if (curInputMethodId != null) {
370                    for (int i=0; i<N; i++) {
371                        InputMethodInfo imi = mMethodList.get(i);
372                        if (imi.getId().equals(curInputMethodId)) {
373                            for (String pkg : packages) {
374                                if (imi.getPackageName().equals(pkg)) {
375                                    if (!doit) {
376                                        return true;
377                                    }
378                                    resetSelectedInputMethodAndSubtypeLocked("");
379                                    chooseNewDefaultIMELocked();
380                                    return true;
381                                }
382                            }
383                        }
384                    }
385                }
386            }
387            return false;
388        }
389
390        @Override
391        public void onSomePackagesChanged() {
392            synchronized (mMethodMap) {
393                InputMethodInfo curIm = null;
394                String curInputMethodId = Settings.Secure.getString(mContext
395                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
396                final int N = mMethodList.size();
397                if (curInputMethodId != null) {
398                    for (int i=0; i<N; i++) {
399                        InputMethodInfo imi = mMethodList.get(i);
400                        if (imi.getId().equals(curInputMethodId)) {
401                            curIm = imi;
402                        }
403                        int change = isPackageDisappearing(imi.getPackageName());
404                        if (change == PACKAGE_TEMPORARY_CHANGE
405                                || change == PACKAGE_PERMANENT_CHANGE) {
406                            Slog.i(TAG, "Input method uninstalled, disabling: "
407                                    + imi.getComponent());
408                            setInputMethodEnabledLocked(imi.getId(), false);
409                        }
410                    }
411                }
412
413                buildInputMethodListLocked(mMethodList, mMethodMap);
414
415                boolean changed = false;
416
417                if (curIm != null) {
418                    int change = isPackageDisappearing(curIm.getPackageName());
419                    if (change == PACKAGE_TEMPORARY_CHANGE
420                            || change == PACKAGE_PERMANENT_CHANGE) {
421                        ServiceInfo si = null;
422                        try {
423                            si = mContext.getPackageManager().getServiceInfo(
424                                    curIm.getComponent(), 0);
425                        } catch (PackageManager.NameNotFoundException ex) {
426                        }
427                        if (si == null) {
428                            // Uh oh, current input method is no longer around!
429                            // Pick another one...
430                            Slog.i(TAG, "Current input method removed: " + curInputMethodId);
431                            if (!chooseNewDefaultIMELocked()) {
432                                changed = true;
433                                curIm = null;
434                                Slog.i(TAG, "Unsetting current input method");
435                                resetSelectedInputMethodAndSubtypeLocked("");
436                            }
437                        }
438                    }
439                }
440
441                if (curIm == null) {
442                    // We currently don't have a default input method... is
443                    // one now available?
444                    changed = chooseNewDefaultIMELocked();
445                }
446
447                if (changed) {
448                    updateFromSettingsLocked();
449                }
450            }
451        }
452    }
453
454    class MethodCallback extends IInputMethodCallback.Stub {
455        final IInputMethod mMethod;
456
457        MethodCallback(IInputMethod method) {
458            mMethod = method;
459        }
460
461        public void finishedEvent(int seq, boolean handled) throws RemoteException {
462        }
463
464        public void sessionCreated(IInputMethodSession session) throws RemoteException {
465            onSessionCreated(mMethod, session);
466        }
467    }
468
469    public InputMethodManagerService(Context context, StatusBarManagerService statusBar) {
470        mContext = context;
471        mHandler = new Handler(this);
472        mIWindowManager = IWindowManager.Stub.asInterface(
473                ServiceManager.getService(Context.WINDOW_SERVICE));
474        mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
475            public void executeMessage(Message msg) {
476                handleMessage(msg);
477            }
478        });
479
480        (new MyPackageMonitor()).register(mContext, true);
481
482        IntentFilter screenOnOffFilt = new IntentFilter();
483        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
484        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
485        screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
486        mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
487
488        mStatusBar = statusBar;
489        statusBar.setIconVisibility("ime", false);
490
491        // mSettings should be created before buildInputMethodListLocked
492        mSettings = new InputMethodSettings(context.getContentResolver(), mMethodMap, mMethodList);
493        buildInputMethodListLocked(mMethodList, mMethodMap);
494        mSettings.enableAllIMEsIfThereIsNoEnabledIME();
495
496        if (TextUtils.isEmpty(Settings.Secure.getString(
497                mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
498            InputMethodInfo defIm = null;
499            for (InputMethodInfo imi: mMethodList) {
500                if (defIm == null && imi.getIsDefaultResourceId() != 0) {
501                    try {
502                        Resources res = context.createPackageContext(
503                                imi.getPackageName(), 0).getResources();
504                        if (res.getBoolean(imi.getIsDefaultResourceId())) {
505                            defIm = imi;
506                            Slog.i(TAG, "Selected default: " + imi.getId());
507                        }
508                    } catch (PackageManager.NameNotFoundException ex) {
509                    } catch (Resources.NotFoundException ex) {
510                    }
511                }
512            }
513            if (defIm == null && mMethodList.size() > 0) {
514                defIm = mMethodList.get(0);
515                Slog.i(TAG, "No default found, using " + defIm.getId());
516            }
517            if (defIm != null) {
518                setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
519            }
520        }
521
522        mSettingsObserver = new SettingsObserver(mHandler);
523        updateFromSettingsLocked();
524    }
525
526    @Override
527    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
528            throws RemoteException {
529        try {
530            return super.onTransact(code, data, reply, flags);
531        } catch (RuntimeException e) {
532            // The input method manager only throws security exceptions, so let's
533            // log all others.
534            if (!(e instanceof SecurityException)) {
535                Slog.e(TAG, "Input Method Manager Crash", e);
536            }
537            throw e;
538        }
539    }
540
541    public void systemReady() {
542        synchronized (mMethodMap) {
543            if (!mSystemReady) {
544                mSystemReady = true;
545                try {
546                    startInputInnerLocked();
547                } catch (RuntimeException e) {
548                    Slog.w(TAG, "Unexpected exception", e);
549                }
550            }
551        }
552    }
553
554    public List<InputMethodInfo> getInputMethodList() {
555        synchronized (mMethodMap) {
556            return new ArrayList<InputMethodInfo>(mMethodList);
557        }
558    }
559
560    public List<InputMethodInfo> getEnabledInputMethodList() {
561        synchronized (mMethodMap) {
562            return mSettings.getEnabledInputMethodListLocked();
563        }
564    }
565
566    public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi) {
567        synchronized (mMethodMap) {
568            if (imi == null && mCurMethodId != null) {
569                imi = mMethodMap.get(mCurMethodId);
570            }
571            return mSettings.getEnabledInputMethodSubtypeListLocked(imi);
572        }
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        int uid = Binder.getCallingUid();
935        long ident = Binder.clearCallingIdentity();
936        try {
937            if (token == null || mCurToken != token) {
938                Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
939                return;
940            }
941
942            synchronized (mMethodMap) {
943                if (iconId == 0) {
944                    if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
945                    mStatusBar.setIconVisibility("ime", false);
946                } else if (packageName != null) {
947                    if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
948                    mStatusBar.setIcon("ime", packageName, iconId, 0);
949                    mStatusBar.setIconVisibility("ime", true);
950                }
951            }
952        } finally {
953            Binder.restoreCallingIdentity(ident);
954        }
955    }
956
957    public void setIMEButtonVisible(IBinder token, boolean visible) {
958        int uid = Binder.getCallingUid();
959        long ident = Binder.clearCallingIdentity();
960        try {
961            if (token == null || mCurToken != token) {
962                Slog.w(TAG, "Ignoring setIMEButtonVisible of uid " + uid + " token: " + token);
963                return;
964            }
965
966            synchronized (mMethodMap) {
967                mStatusBar.setIMEButtonVisible(visible);
968            }
969        } finally {
970            Binder.restoreCallingIdentity(ident);
971        }
972    }
973
974    void updateFromSettingsLocked() {
975        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
976        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
977        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
978        // enabled.
979        String id = Settings.Secure.getString(mContext.getContentResolver(),
980                Settings.Secure.DEFAULT_INPUT_METHOD);
981        // There is no input method selected, try to choose new applicable input method.
982        if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
983            id = Settings.Secure.getString(mContext.getContentResolver(),
984                    Settings.Secure.DEFAULT_INPUT_METHOD);
985        }
986        if (!TextUtils.isEmpty(id)) {
987            try {
988                setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
989            } catch (IllegalArgumentException e) {
990                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
991                mCurMethodId = null;
992                unbindCurrentMethodLocked(true);
993            }
994            mShortcutInputMethodsAndSubtypes.clear();
995        } else {
996            // There is no longer an input method set, so stop any current one.
997            mCurMethodId = null;
998            unbindCurrentMethodLocked(true);
999        }
1000    }
1001
1002    /* package */ void setInputMethodLocked(String id, int subtypeId) {
1003        InputMethodInfo info = mMethodMap.get(id);
1004        if (info == null) {
1005            throw new IllegalArgumentException("Unknown id: " + id);
1006        }
1007
1008        if (id.equals(mCurMethodId)) {
1009            ArrayList<InputMethodSubtype> subtypes = info.getSubtypes();
1010            if (subtypeId >= 0 && subtypeId < subtypes.size()) {
1011                InputMethodSubtype subtype = subtypes.get(subtypeId);
1012                if (subtype != mCurrentSubtype) {
1013                    synchronized (mMethodMap) {
1014                        if (mCurMethod != null) {
1015                            try {
1016                                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
1017                                if (mInputShown) {
1018                                    // If mInputShown is false, there is no IME button on the
1019                                    // system bar.
1020                                    // Thus there is no need to make it invisible explicitly.
1021                                    mStatusBar.setIMEButtonVisible(true);
1022                                }
1023                                mCurMethod.changeInputMethodSubtype(subtype);
1024                            } catch (RemoteException e) {
1025                                return;
1026                            }
1027                        }
1028                    }
1029                }
1030            }
1031            return;
1032        }
1033
1034        final long ident = Binder.clearCallingIdentity();
1035        try {
1036            // Set a subtype to this input method.
1037            // subtypeId the name of a subtype which will be set.
1038            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
1039            // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
1040            // because mCurMethodId is stored as a history in
1041            // setSelectedInputMethodAndSubtypeLocked().
1042            mCurMethodId = id;
1043
1044            if (ActivityManagerNative.isSystemReady()) {
1045                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
1046                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1047                intent.putExtra("input_method_id", id);
1048                mContext.sendBroadcast(intent);
1049            }
1050            unbindCurrentClientLocked();
1051        } finally {
1052            Binder.restoreCallingIdentity(ident);
1053        }
1054    }
1055
1056    public boolean showSoftInput(IInputMethodClient client, int flags,
1057            ResultReceiver resultReceiver) {
1058        int uid = Binder.getCallingUid();
1059        long ident = Binder.clearCallingIdentity();
1060        try {
1061            synchronized (mMethodMap) {
1062                if (mCurClient == null || client == null
1063                        || mCurClient.client.asBinder() != client.asBinder()) {
1064                    try {
1065                        // We need to check if this is the current client with
1066                        // focus in the window manager, to allow this call to
1067                        // be made before input is started in it.
1068                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1069                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
1070                            return false;
1071                        }
1072                    } catch (RemoteException e) {
1073                        return false;
1074                    }
1075                }
1076
1077                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
1078                return showCurrentInputLocked(flags, resultReceiver);
1079            }
1080        } finally {
1081            Binder.restoreCallingIdentity(ident);
1082        }
1083    }
1084
1085    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1086        mShowRequested = true;
1087        if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1088            mShowExplicitlyRequested = true;
1089        }
1090        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1091            mShowExplicitlyRequested = true;
1092            mShowForced = true;
1093        }
1094
1095        if (!mSystemReady) {
1096            return false;
1097        }
1098
1099        boolean res = false;
1100        if (mCurMethod != null) {
1101            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1102                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1103                    resultReceiver));
1104            mInputShown = true;
1105            res = true;
1106        } else if (mHaveConnection && SystemClock.uptimeMillis()
1107                < (mLastBindTime+TIME_TO_RECONNECT)) {
1108            // The client has asked to have the input method shown, but
1109            // we have been sitting here too long with a connection to the
1110            // service and no interface received, so let's disconnect/connect
1111            // to try to prod things along.
1112            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
1113                    SystemClock.uptimeMillis()-mLastBindTime,1);
1114            mContext.unbindService(this);
1115            mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE);
1116        }
1117
1118        return res;
1119    }
1120
1121    public boolean hideSoftInput(IInputMethodClient client, int flags,
1122            ResultReceiver resultReceiver) {
1123        int uid = Binder.getCallingUid();
1124        long ident = Binder.clearCallingIdentity();
1125        try {
1126            synchronized (mMethodMap) {
1127                if (mCurClient == null || client == null
1128                        || mCurClient.client.asBinder() != client.asBinder()) {
1129                    try {
1130                        // We need to check if this is the current client with
1131                        // focus in the window manager, to allow this call to
1132                        // be made before input is started in it.
1133                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1134                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
1135                                    + uid + ": " + client);
1136                            return false;
1137                        }
1138                    } catch (RemoteException e) {
1139                        return false;
1140                    }
1141                }
1142
1143                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
1144                return hideCurrentInputLocked(flags, resultReceiver);
1145            }
1146        } finally {
1147            Binder.restoreCallingIdentity(ident);
1148        }
1149    }
1150
1151    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1152        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1153                && (mShowExplicitlyRequested || mShowForced)) {
1154            if (DEBUG) Slog.v(TAG,
1155                    "Not hiding: explicit show not cancelled by non-explicit hide");
1156            return false;
1157        }
1158        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1159            if (DEBUG) Slog.v(TAG,
1160                    "Not hiding: forced show not cancelled by not-always hide");
1161            return false;
1162        }
1163        boolean res;
1164        if (mInputShown && mCurMethod != null) {
1165            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1166                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1167            res = true;
1168        } else {
1169            res = false;
1170        }
1171        mInputShown = false;
1172        mShowRequested = false;
1173        mShowExplicitlyRequested = false;
1174        mShowForced = false;
1175        return res;
1176    }
1177
1178    public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
1179            boolean viewHasFocus, boolean isTextEditor, int softInputMode,
1180            boolean first, int windowFlags) {
1181        long ident = Binder.clearCallingIdentity();
1182        try {
1183            synchronized (mMethodMap) {
1184                if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
1185                        + " viewHasFocus=" + viewHasFocus
1186                        + " isTextEditor=" + isTextEditor
1187                        + " softInputMode=#" + Integer.toHexString(softInputMode)
1188                        + " first=" + first + " flags=#"
1189                        + Integer.toHexString(windowFlags));
1190
1191                if (mCurClient == null || client == null
1192                        || mCurClient.client.asBinder() != client.asBinder()) {
1193                    try {
1194                        // We need to check if this is the current client with
1195                        // focus in the window manager, to allow this call to
1196                        // be made before input is started in it.
1197                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1198                            Slog.w(TAG, "Client not active, ignoring focus gain of: " + client);
1199                            return;
1200                        }
1201                    } catch (RemoteException e) {
1202                    }
1203                }
1204
1205                if (mCurFocusedWindow == windowToken) {
1206                    Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
1207                    return;
1208                }
1209                mCurFocusedWindow = windowToken;
1210
1211                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1212                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
1213                        if (!isTextEditor || (softInputMode &
1214                                WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1215                                != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
1216                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1217                                // There is no focus view, and this window will
1218                                // be behind any soft input window, so hide the
1219                                // soft input window if it is shown.
1220                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
1221                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
1222                            }
1223                        } else if (isTextEditor && (softInputMode &
1224                                WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1225                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1226                                && (softInputMode &
1227                                        WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1228                            // There is a focus view, and we are navigating forward
1229                            // into the window, so show the input window for the user.
1230                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
1231                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1232                        }
1233                        break;
1234                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1235                        // Do nothing.
1236                        break;
1237                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1238                        if ((softInputMode &
1239                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1240                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
1241                            hideCurrentInputLocked(0, null);
1242                        }
1243                        break;
1244                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
1245                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
1246                        hideCurrentInputLocked(0, null);
1247                        break;
1248                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1249                        if ((softInputMode &
1250                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1251                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
1252                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1253                        }
1254                        break;
1255                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
1256                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
1257                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1258                        break;
1259                }
1260            }
1261        } finally {
1262            Binder.restoreCallingIdentity(ident);
1263        }
1264    }
1265
1266    public void showInputMethodPickerFromClient(IInputMethodClient client) {
1267        synchronized (mMethodMap) {
1268            if (mCurClient == null || client == null
1269                    || mCurClient.client.asBinder() != client.asBinder()) {
1270                Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
1271                        + Binder.getCallingUid() + ": " + client);
1272            }
1273
1274            mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
1275        }
1276    }
1277
1278    public void showInputMethodSubtypePickerFromClient(IInputMethodClient client) {
1279        synchronized (mMethodMap) {
1280            if (mCurClient == null || client == null
1281                    || mCurClient.client.asBinder() != client.asBinder()) {
1282                Slog.w(TAG, "Ignoring showInputMethodSubtypePickerFromClient of: " + client);
1283            }
1284
1285            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
1286        }
1287    }
1288
1289    public void showInputMethodAndSubtypeEnablerFromClient(
1290            IInputMethodClient client, String topId) {
1291        // TODO: Handle topId for setting the top position of the list activity
1292        synchronized (mMethodMap) {
1293            if (mCurClient == null || client == null
1294                    || mCurClient.client.asBinder() != client.asBinder()) {
1295                Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
1296            }
1297
1298            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_ENABLER);
1299        }
1300    }
1301
1302    public void setInputMethod(IBinder token, String id) {
1303        setInputMethodWithSubtype(token, id, NOT_A_SUBTYPE_ID);
1304    }
1305
1306    public boolean switchToLastInputMethod(IBinder token) {
1307        synchronized (mMethodMap) {
1308            Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1309            if (lastIme != null) {
1310                InputMethodInfo imi = mMethodMap.get(lastIme.first);
1311                if (imi != null) {
1312                    setInputMethodWithSubtype(token, lastIme.first, getSubtypeIdFromHashCode(
1313                            imi, Integer.valueOf(lastIme.second)));
1314                    return true;
1315                }
1316            }
1317            return false;
1318        }
1319    }
1320
1321    private void setInputMethodWithSubtype(IBinder token, String id, int subtypeId) {
1322        synchronized (mMethodMap) {
1323            if (token == null) {
1324                if (mContext.checkCallingOrSelfPermission(
1325                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
1326                        != PackageManager.PERMISSION_GRANTED) {
1327                    throw new SecurityException(
1328                            "Using null token requires permission "
1329                            + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1330                }
1331            } else if (mCurToken != token) {
1332                Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
1333                        + " token: " + token);
1334                return;
1335            }
1336
1337            long ident = Binder.clearCallingIdentity();
1338            try {
1339                setInputMethodLocked(id, subtypeId);
1340            } finally {
1341                Binder.restoreCallingIdentity(ident);
1342            }
1343        }
1344    }
1345
1346    public void hideMySoftInput(IBinder token, int flags) {
1347        synchronized (mMethodMap) {
1348            if (token == null || mCurToken != token) {
1349                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
1350                        + Binder.getCallingUid() + " token: " + token);
1351                return;
1352            }
1353            long ident = Binder.clearCallingIdentity();
1354            try {
1355                hideCurrentInputLocked(flags, null);
1356            } finally {
1357                Binder.restoreCallingIdentity(ident);
1358            }
1359        }
1360    }
1361
1362    public void showMySoftInput(IBinder token, int flags) {
1363        synchronized (mMethodMap) {
1364            if (token == null || mCurToken != token) {
1365                Slog.w(TAG, "Ignoring showMySoftInput of uid "
1366                        + Binder.getCallingUid() + " token: " + token);
1367                return;
1368            }
1369            long ident = Binder.clearCallingIdentity();
1370            try {
1371                showCurrentInputLocked(flags, null);
1372            } finally {
1373                Binder.restoreCallingIdentity(ident);
1374            }
1375        }
1376    }
1377
1378    void setEnabledSessionInMainThread(SessionState session) {
1379        if (mEnabledSession != session) {
1380            if (mEnabledSession != null) {
1381                try {
1382                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
1383                    mEnabledSession.method.setSessionEnabled(
1384                            mEnabledSession.session, false);
1385                } catch (RemoteException e) {
1386                }
1387            }
1388            mEnabledSession = session;
1389            try {
1390                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
1391                session.method.setSessionEnabled(
1392                        session.session, true);
1393            } catch (RemoteException e) {
1394            }
1395        }
1396    }
1397
1398    public boolean handleMessage(Message msg) {
1399        HandlerCaller.SomeArgs args;
1400        switch (msg.what) {
1401            case MSG_SHOW_IM_PICKER:
1402                showInputMethodMenu();
1403                return true;
1404
1405            case MSG_SHOW_IM_SUBTYPE_PICKER:
1406                showInputMethodSubtypeMenu();
1407                return true;
1408
1409            case MSG_SHOW_IM_SUBTYPE_ENABLER:
1410                showInputMethodAndSubtypeEnabler();
1411                return true;
1412
1413            // ---------------------------------------------------------
1414
1415            case MSG_UNBIND_INPUT:
1416                try {
1417                    ((IInputMethod)msg.obj).unbindInput();
1418                } catch (RemoteException e) {
1419                    // There is nothing interesting about the method dying.
1420                }
1421                return true;
1422            case MSG_BIND_INPUT:
1423                args = (HandlerCaller.SomeArgs)msg.obj;
1424                try {
1425                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1426                } catch (RemoteException e) {
1427                }
1428                return true;
1429            case MSG_SHOW_SOFT_INPUT:
1430                args = (HandlerCaller.SomeArgs)msg.obj;
1431                try {
1432                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
1433                            (ResultReceiver)args.arg2);
1434                } catch (RemoteException e) {
1435                }
1436                return true;
1437            case MSG_HIDE_SOFT_INPUT:
1438                args = (HandlerCaller.SomeArgs)msg.obj;
1439                try {
1440                    ((IInputMethod)args.arg1).hideSoftInput(0,
1441                            (ResultReceiver)args.arg2);
1442                } catch (RemoteException e) {
1443                }
1444                return true;
1445            case MSG_ATTACH_TOKEN:
1446                args = (HandlerCaller.SomeArgs)msg.obj;
1447                try {
1448                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
1449                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1450                } catch (RemoteException e) {
1451                }
1452                return true;
1453            case MSG_CREATE_SESSION:
1454                args = (HandlerCaller.SomeArgs)msg.obj;
1455                try {
1456                    ((IInputMethod)args.arg1).createSession(
1457                            (IInputMethodCallback)args.arg2);
1458                } catch (RemoteException e) {
1459                }
1460                return true;
1461            // ---------------------------------------------------------
1462
1463            case MSG_START_INPUT:
1464                args = (HandlerCaller.SomeArgs)msg.obj;
1465                try {
1466                    SessionState session = (SessionState)args.arg1;
1467                    setEnabledSessionInMainThread(session);
1468                    session.method.startInput((IInputContext)args.arg2,
1469                            (EditorInfo)args.arg3);
1470                } catch (RemoteException e) {
1471                }
1472                return true;
1473            case MSG_RESTART_INPUT:
1474                args = (HandlerCaller.SomeArgs)msg.obj;
1475                try {
1476                    SessionState session = (SessionState)args.arg1;
1477                    setEnabledSessionInMainThread(session);
1478                    session.method.restartInput((IInputContext)args.arg2,
1479                            (EditorInfo)args.arg3);
1480                } catch (RemoteException e) {
1481                }
1482                return true;
1483
1484            // ---------------------------------------------------------
1485
1486            case MSG_UNBIND_METHOD:
1487                try {
1488                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1489                } catch (RemoteException e) {
1490                    // There is nothing interesting about the last client dying.
1491                }
1492                return true;
1493            case MSG_BIND_METHOD:
1494                args = (HandlerCaller.SomeArgs)msg.obj;
1495                try {
1496                    ((IInputMethodClient)args.arg1).onBindMethod(
1497                            (InputBindResult)args.arg2);
1498                } catch (RemoteException e) {
1499                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
1500                }
1501                return true;
1502        }
1503        return false;
1504    }
1505
1506    private boolean isSystemIme(InputMethodInfo inputMethod) {
1507        return (inputMethod.getServiceInfo().applicationInfo.flags
1508                & ApplicationInfo.FLAG_SYSTEM) != 0;
1509    }
1510
1511    private boolean chooseNewDefaultIMELocked() {
1512        List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1513        if (enabled != null && enabled.size() > 0) {
1514            // We'd prefer to fall back on a system IME, since that is safer.
1515            int i=enabled.size();
1516            while (i > 0) {
1517                i--;
1518                if ((enabled.get(i).getServiceInfo().applicationInfo.flags
1519                        & ApplicationInfo.FLAG_SYSTEM) != 0) {
1520                    break;
1521                }
1522            }
1523            InputMethodInfo imi = enabled.get(i);
1524            if (DEBUG) {
1525                Slog.d(TAG, "New default IME was selected: " + imi.getId());
1526            }
1527            resetSelectedInputMethodAndSubtypeLocked(imi.getId());
1528            return true;
1529        }
1530
1531        return false;
1532    }
1533
1534    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
1535            HashMap<String, InputMethodInfo> map) {
1536        list.clear();
1537        map.clear();
1538
1539        PackageManager pm = mContext.getPackageManager();
1540        final Configuration config = mContext.getResources().getConfiguration();
1541        final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
1542        String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
1543                Secure.DISABLED_SYSTEM_INPUT_METHODS);
1544        if (disabledSysImes == null) disabledSysImes = "";
1545
1546        List<ResolveInfo> services = pm.queryIntentServices(
1547                new Intent(InputMethod.SERVICE_INTERFACE),
1548                PackageManager.GET_META_DATA);
1549
1550        for (int i = 0; i < services.size(); ++i) {
1551            ResolveInfo ri = services.get(i);
1552            ServiceInfo si = ri.serviceInfo;
1553            ComponentName compName = new ComponentName(si.packageName, si.name);
1554            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
1555                    si.permission)) {
1556                Slog.w(TAG, "Skipping input method " + compName
1557                        + ": it does not require the permission "
1558                        + android.Manifest.permission.BIND_INPUT_METHOD);
1559                continue;
1560            }
1561
1562            if (DEBUG) Slog.d(TAG, "Checking " + compName);
1563
1564            try {
1565                InputMethodInfo p = new InputMethodInfo(mContext, ri);
1566                list.add(p);
1567                final String id = p.getId();
1568                map.put(id, p);
1569
1570                // System IMEs are enabled by default, unless there's a hard keyboard
1571                // and the system IME was explicitly disabled
1572                if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
1573                    setInputMethodEnabledLocked(id, true);
1574                }
1575
1576                if (DEBUG) {
1577                    Slog.d(TAG, "Found a third-party input method " + p);
1578                }
1579
1580            } catch (XmlPullParserException e) {
1581                Slog.w(TAG, "Unable to load input method " + compName, e);
1582            } catch (IOException e) {
1583                Slog.w(TAG, "Unable to load input method " + compName, e);
1584            }
1585        }
1586
1587        String defaultIme = Settings.Secure.getString(mContext
1588                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1589        if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
1590            if (chooseNewDefaultIMELocked()) {
1591                updateFromSettingsLocked();
1592            }
1593        }
1594    }
1595
1596    // ----------------------------------------------------------------------
1597
1598    private void showInputMethodMenu() {
1599        showInputMethodMenuInternal(false);
1600    }
1601
1602    private void showInputMethodSubtypeMenu() {
1603        showInputMethodMenuInternal(true);
1604    }
1605
1606    private void showInputMethodAndSubtypeEnabler() {
1607        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_AND_SUBTYPE_ENABLER);
1608        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1609                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1610                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1611        mContext.startActivity(intent);
1612    }
1613
1614    private void showInputMethodMenuInternal(boolean showSubtypes) {
1615        if (DEBUG) Slog.v(TAG, "Show switching menu");
1616
1617        final Context context = mContext;
1618
1619        final PackageManager pm = context.getPackageManager();
1620
1621        String lastInputMethodId = Settings.Secure.getString(context
1622                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1623        int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
1624        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
1625
1626        synchronized (mMethodMap) {
1627            final List<Pair<InputMethodInfo, ArrayList<String>>> immis =
1628                    mSettings.getEnabledInputMethodAndSubtypeHashCodeListLocked();
1629            ArrayList<Integer> subtypeIds = new ArrayList<Integer>();
1630
1631            if (immis == null || immis.size() == 0) {
1632                return;
1633            }
1634
1635            hideInputMethodMenuLocked();
1636
1637            int N = immis.size();
1638
1639            final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap =
1640                new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance());
1641
1642            for (int i = 0; i < N; ++i) {
1643                InputMethodInfo property = immis.get(i).first;
1644                final ArrayList<String> enabledSubtypeIds = immis.get(i).second;
1645                HashSet<String> enabledSubtypeSet = new HashSet<String>();
1646                for (String s : enabledSubtypeIds) {
1647                    enabledSubtypeSet.add(s);
1648                }
1649                if (property == null) {
1650                    continue;
1651                }
1652                ArrayList<InputMethodSubtype> subtypes = property.getSubtypes();
1653                CharSequence label = property.loadLabel(pm);
1654                if (showSubtypes && enabledSubtypeSet.size() > 0) {
1655                    for (int j = 0; j < subtypes.size(); ++j) {
1656                        InputMethodSubtype subtype = subtypes.get(j);
1657                        if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) {
1658                            CharSequence title;
1659                            int nameResId = subtype.getNameResId();
1660                            String mode = subtype.getMode();
1661                            if (nameResId != 0) {
1662                                title = pm.getText(property.getPackageName(), nameResId,
1663                                        property.getServiceInfo().applicationInfo);
1664                            } else {
1665                                CharSequence language = subtype.getLocale();
1666                                // TODO: Use more friendly Title and UI
1667                                title = label + "," + (mode == null ? "" : mode) + ","
1668                                        + (language == null ? "" : language);
1669                            }
1670                            imMap.put(title, new Pair<InputMethodInfo, Integer>(property, j));
1671                        }
1672                    }
1673                } else {
1674                    imMap.put(label,
1675                            new Pair<InputMethodInfo, Integer>(property, NOT_A_SUBTYPE_ID));
1676                    subtypeIds.add(0);
1677                }
1678            }
1679
1680            N = imMap.size();
1681            mItems = imMap.keySet().toArray(new CharSequence[N]);
1682            mIms = new InputMethodInfo[N];
1683            mSubtypeIds = new int[N];
1684            int checkedItem = 0;
1685            for (int i = 0; i < N; ++i) {
1686                Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]);
1687                mIms[i] = value.first;
1688                mSubtypeIds[i] = value.second;
1689                if (mIms[i].getId().equals(lastInputMethodId)) {
1690                    int subtypeId = mSubtypeIds[i];
1691                    if ((subtypeId == NOT_A_SUBTYPE_ID)
1692                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
1693                            || (subtypeId == lastInputMethodSubtypeId)) {
1694                        checkedItem = i;
1695                    }
1696                }
1697            }
1698
1699            AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
1700                public void onClick(DialogInterface dialog, int which) {
1701                    hideInputMethodMenu();
1702                }
1703            };
1704
1705            TypedArray a = context.obtainStyledAttributes(null,
1706                    com.android.internal.R.styleable.DialogPreference,
1707                    com.android.internal.R.attr.alertDialogStyle, 0);
1708            mDialogBuilder = new AlertDialog.Builder(context)
1709                    .setTitle(com.android.internal.R.string.select_input_method)
1710                    .setOnCancelListener(new OnCancelListener() {
1711                        public void onCancel(DialogInterface dialog) {
1712                            hideInputMethodMenu();
1713                        }
1714                    })
1715                    .setIcon(a.getDrawable(
1716                            com.android.internal.R.styleable.DialogPreference_dialogTitle));
1717            a.recycle();
1718
1719            mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
1720                    new AlertDialog.OnClickListener() {
1721                        public void onClick(DialogInterface dialog, int which) {
1722                            synchronized (mMethodMap) {
1723                                if (mIms == null || mIms.length <= which
1724                                        || mSubtypeIds == null || mSubtypeIds.length <= which) {
1725                                    return;
1726                                }
1727                                InputMethodInfo im = mIms[which];
1728                                int subtypeId = mSubtypeIds[which];
1729                                hideInputMethodMenu();
1730                                if (im != null) {
1731                                    if ((subtypeId < 0)
1732                                            || (subtypeId >= im.getSubtypes().size())) {
1733                                        subtypeId = NOT_A_SUBTYPE_ID;
1734                                    }
1735                                    setInputMethodLocked(im.getId(), subtypeId);
1736                                }
1737                            }
1738                        }
1739                    });
1740
1741            if (showSubtypes) {
1742                mDialogBuilder.setPositiveButton(com.android.internal.R.string.more_item_label,
1743                        new DialogInterface.OnClickListener() {
1744                            public void onClick(DialogInterface dialog, int whichButton) {
1745                                showInputMethodAndSubtypeEnabler();
1746                            }
1747                        });
1748            }
1749            mDialogBuilder.setNegativeButton(com.android.internal.R.string.cancel,
1750                    new DialogInterface.OnClickListener() {
1751                        public void onClick(DialogInterface dialog, int whichButton) {
1752                            hideInputMethodMenu();
1753                        }
1754                    });
1755            mSwitchingDialog = mDialogBuilder.create();
1756            mSwitchingDialog.getWindow().setType(
1757                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1758            mSwitchingDialog.show();
1759        }
1760    }
1761
1762    void hideInputMethodMenu() {
1763        synchronized (mMethodMap) {
1764            hideInputMethodMenuLocked();
1765        }
1766    }
1767
1768    void hideInputMethodMenuLocked() {
1769        if (DEBUG) Slog.v(TAG, "Hide switching menu");
1770
1771        if (mSwitchingDialog != null) {
1772            mSwitchingDialog.dismiss();
1773            mSwitchingDialog = null;
1774        }
1775
1776        mDialogBuilder = null;
1777        mItems = null;
1778        mIms = null;
1779    }
1780
1781    // ----------------------------------------------------------------------
1782
1783    public boolean setInputMethodEnabled(String id, boolean enabled) {
1784        synchronized (mMethodMap) {
1785            if (mContext.checkCallingOrSelfPermission(
1786                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
1787                    != PackageManager.PERMISSION_GRANTED) {
1788                throw new SecurityException(
1789                        "Requires permission "
1790                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1791            }
1792
1793            long ident = Binder.clearCallingIdentity();
1794            try {
1795                return setInputMethodEnabledLocked(id, enabled);
1796            } finally {
1797                Binder.restoreCallingIdentity(ident);
1798            }
1799        }
1800    }
1801
1802    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
1803        // Make sure this is a valid input method.
1804        InputMethodInfo imm = mMethodMap.get(id);
1805        if (imm == null) {
1806            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1807        }
1808
1809        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
1810                .getEnabledInputMethodsAndSubtypeListLocked();
1811
1812        if (enabled) {
1813            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
1814                if (pair.first.equals(id)) {
1815                    // We are enabling this input method, but it is already enabled.
1816                    // Nothing to do. The previous state was enabled.
1817                    return true;
1818                }
1819            }
1820            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
1821            // Previous state was disabled.
1822            return false;
1823        } else {
1824            StringBuilder builder = new StringBuilder();
1825            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
1826                    builder, enabledInputMethodsList, id)) {
1827                // Disabled input method is currently selected, switch to another one.
1828                String selId = Settings.Secure.getString(mContext.getContentResolver(),
1829                        Settings.Secure.DEFAULT_INPUT_METHOD);
1830                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
1831                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
1832                    resetSelectedInputMethodAndSubtypeLocked("");
1833                }
1834                // Previous state was enabled.
1835                return true;
1836            } else {
1837                // We are disabling the input method but it is already disabled.
1838                // Nothing to do.  The previous state was disabled.
1839                return false;
1840            }
1841        }
1842    }
1843
1844    private void saveCurrentInputMethodAndSubtypeToHistory() {
1845        String subtypeId = NOT_A_SUBTYPE_ID_STR;
1846        if (mCurrentSubtype != null) {
1847            subtypeId = String.valueOf(mCurrentSubtype.hashCode());
1848        }
1849        mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
1850    }
1851
1852    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
1853            boolean setSubtypeOnly) {
1854        // Update the history of InputMethod and Subtype
1855        saveCurrentInputMethodAndSubtypeToHistory();
1856
1857        // Set Subtype here
1858        if (imi == null || subtypeId < 0) {
1859            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
1860            mCurrentSubtype = null;
1861        } else {
1862            final ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
1863            if (subtypeId < subtypes.size()) {
1864                mSettings.putSelectedSubtype(subtypes.get(subtypeId).hashCode());
1865                mCurrentSubtype = subtypes.get(subtypeId);
1866            } else {
1867                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
1868                mCurrentSubtype = null;
1869            }
1870        }
1871
1872        if (!setSubtypeOnly) {
1873            // Set InputMethod here
1874            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
1875        }
1876    }
1877
1878    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
1879        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
1880        int lastSubtypeId = NOT_A_SUBTYPE_ID;
1881        // newDefaultIme is empty when there is no candidate for the selected IME.
1882        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
1883            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
1884            if (subtypeHashCode != null) {
1885                try {
1886                    lastSubtypeId = getSubtypeIdFromHashCode(
1887                            imi, Integer.valueOf(subtypeHashCode));
1888                } catch (NumberFormatException e) {
1889                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
1890                }
1891            }
1892        }
1893        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
1894    }
1895
1896    private int getSelectedInputMethodSubtypeId(String id) {
1897        InputMethodInfo imi = mMethodMap.get(id);
1898        if (imi == null) {
1899            return NOT_A_SUBTYPE_ID;
1900        }
1901        int subtypeId;
1902        try {
1903            subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
1904                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
1905        } catch (SettingNotFoundException e) {
1906            return NOT_A_SUBTYPE_ID;
1907        }
1908        return getSubtypeIdFromHashCode(imi, subtypeId);
1909    }
1910
1911    private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
1912        ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
1913        for (int i = 0; i < subtypes.size(); ++i) {
1914            InputMethodSubtype ims = subtypes.get(i);
1915            if (subtypeHashCode == ims.hashCode()) {
1916                return i;
1917            }
1918        }
1919        return NOT_A_SUBTYPE_ID;
1920    }
1921
1922    /**
1923     * If there are no selected subtypes, tries finding the most applicable one according to the
1924     * given locale.
1925     * @param subtypes this function will search the most applicable subtype in subtypes
1926     * @param mode subtypes will be filtered by mode
1927     * @param locale subtypes will be filtered by locale
1928     * @param defaultSubtypeId if this function can't find the most applicable subtype, it will
1929     * return defaultSubtypeId
1930     * @return the most applicable subtypeId
1931     */
1932    private int findLastResortApplicableSubtypeLocked(
1933            List<InputMethodSubtype> subtypes, String mode, String locale, int defaultSubtypeId) {
1934        if (subtypes == null || subtypes.size() == 0) {
1935            return NOT_A_SUBTYPE_ID;
1936        }
1937        if (TextUtils.isEmpty(locale)) {
1938            locale = mContext.getResources().getConfiguration().locale.toString();
1939        }
1940        final String language = locale.substring(0, 2);
1941        boolean partialMatchFound = false;
1942        int applicableSubtypeId = defaultSubtypeId;
1943        for (int i = 0; i < subtypes.size(); ++i) {
1944            final String subtypeLocale = subtypes.get(i).getLocale();
1945            // An applicable subtype should match "mode".
1946            if (subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
1947                if (locale.equals(subtypeLocale)) {
1948                    // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
1949                    applicableSubtypeId = i;
1950                    break;
1951                } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
1952                    // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
1953                    applicableSubtypeId = i;
1954                    partialMatchFound = true;
1955                }
1956            }
1957        }
1958
1959        // The first subtype applicable to the system locale will be defined as the most applicable
1960        // subtype.
1961        if (DEBUG) {
1962            Slog.d(TAG, "Applicable InputMethodSubtype was found: " + applicableSubtypeId + ","
1963                    + subtypes.get(applicableSubtypeId).getLocale());
1964        }
1965        return applicableSubtypeId;
1966    }
1967
1968    // If there are no selected shortcuts, tries finding the most applicable ones.
1969    private Pair<InputMethodInfo, InputMethodSubtype>
1970            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
1971        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
1972        InputMethodInfo mostApplicableIMI = null;
1973        int mostApplicableSubtypeId = NOT_A_SUBTYPE_ID;
1974        boolean foundInSystemIME = false;
1975
1976        // Search applicable subtype for each InputMethodInfo
1977        for (InputMethodInfo imi: imis) {
1978            int subtypeId = NOT_A_SUBTYPE_ID;
1979            if (mCurrentSubtype != null) {
1980                // 1. Search with the current subtype's locale and the enabled subtypes
1981                subtypeId = findLastResortApplicableSubtypeLocked(
1982                        mSettings.getEnabledInputMethodSubtypeListLocked(
1983                        imi), mode, mCurrentSubtype.getLocale(), NOT_A_SUBTYPE_ID);
1984                if (subtypeId == NOT_A_SUBTYPE_ID) {
1985                    // 2. Search with the current subtype's locale and all subtypes
1986                    subtypeId = findLastResortApplicableSubtypeLocked(imi.getSubtypes(),
1987                            mode, mCurrentSubtype.getLocale(), NOT_A_SUBTYPE_ID);
1988                }
1989            }
1990            // 3. Search with the system locale and the enabled subtypes
1991            if (subtypeId == NOT_A_SUBTYPE_ID) {
1992                subtypeId = findLastResortApplicableSubtypeLocked(
1993                        mSettings.getEnabledInputMethodSubtypeListLocked(
1994                        imi), mode, null, NOT_A_SUBTYPE_ID);
1995            }
1996            if (subtypeId == NOT_A_SUBTYPE_ID) {
1997                // 4. Search with the system locale and all subtypes
1998                subtypeId = findLastResortApplicableSubtypeLocked(imi.getSubtypes(),
1999                        mode, null, NOT_A_SUBTYPE_ID);
2000            }
2001            if (subtypeId != NOT_A_SUBTYPE_ID) {
2002                if (imi.getId().equals(mCurMethodId)) {
2003                    // The current input method is the most applicable IME.
2004                    mostApplicableIMI = imi;
2005                    mostApplicableSubtypeId = subtypeId;
2006                    break;
2007                } else if ((imi.getServiceInfo().applicationInfo.flags
2008                        & ApplicationInfo.FLAG_SYSTEM) != 0) {
2009                    // The system input method is 2nd applicable IME.
2010                    mostApplicableIMI = imi;
2011                    mostApplicableSubtypeId = subtypeId;
2012                    foundInSystemIME = true;
2013                } else if (!foundInSystemIME) {
2014                    mostApplicableIMI = imi;
2015                    mostApplicableSubtypeId = subtypeId;
2016                }
2017            }
2018        }
2019        if (DEBUG) {
2020            Slog.w(TAG, "Most applicable shortcut input method subtype was:"
2021                    + mostApplicableIMI.getId() + "," + mostApplicableSubtypeId);
2022        }
2023        if (mostApplicableIMI != null && mostApplicableSubtypeId != NOT_A_SUBTYPE_ID) {
2024            return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
2025                    mostApplicableIMI.getSubtypes().get(mostApplicableSubtypeId));
2026        } else {
2027            return null;
2028        }
2029    }
2030
2031    /**
2032     * @return Return the current subtype of this input method.
2033     */
2034    public InputMethodSubtype getCurrentInputMethodSubtype() {
2035        boolean subtypeIsSelected = false;
2036        try {
2037            subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
2038                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
2039        } catch (SettingNotFoundException e) {
2040        }
2041        synchronized (mMethodMap) {
2042            if (!subtypeIsSelected || mCurrentSubtype == null) {
2043                String lastInputMethodId = Settings.Secure.getString(
2044                        mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2045                int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2046                if (subtypeId == NOT_A_SUBTYPE_ID) {
2047                    InputMethodInfo imi = mMethodMap.get(lastInputMethodId);
2048                    if (imi != null) {
2049                        // If there are no selected subtypes, the framework will try to find
2050                        // the most applicable subtype from all subtypes whose mode is
2051                        // SUBTYPE_MODE_KEYBOARD. This is an exceptional case, so we will hardcode
2052                        // the mode.
2053                        subtypeId = findLastResortApplicableSubtypeLocked(imi.getSubtypes(),
2054                                SUBTYPE_MODE_KEYBOARD, null, DEFAULT_SUBTYPE_ID);
2055                    }
2056                }
2057                if (subtypeId != NOT_A_SUBTYPE_ID) {
2058                    mCurrentSubtype =
2059                            mMethodMap.get(lastInputMethodId).getSubtypes().get(subtypeId);
2060                } else {
2061                    mCurrentSubtype = null;
2062                }
2063            }
2064            return mCurrentSubtype;
2065        }
2066    }
2067
2068    private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
2069            InputMethodSubtype subtype) {
2070        if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
2071            mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
2072        } else {
2073            ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2074            subtypes.add(subtype);
2075            mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
2076        }
2077    }
2078
2079    // TODO: We should change the return type from List to List<Parcelable>
2080    public List getShortcutInputMethodsAndSubtypes() {
2081        synchronized (mMethodMap) {
2082            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
2083                // If there are no selected shortcut subtypes, the framework will try to find
2084                // the most applicable subtype from all subtypes whose mode is
2085                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
2086                Pair<InputMethodInfo, InputMethodSubtype> info =
2087                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
2088                            SUBTYPE_MODE_VOICE);
2089                addShortcutInputMethodAndSubtypes(info.first, info.second);
2090            }
2091            ArrayList ret = new ArrayList<Object>();
2092            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
2093                ret.add(imi);
2094                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
2095                    ret.add(subtype);
2096                }
2097            }
2098            return ret;
2099        }
2100    }
2101
2102    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
2103        synchronized (mMethodMap) {
2104            if (subtype != null && mCurMethodId != null) {
2105                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2106                int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
2107                if (subtypeId != NOT_A_SUBTYPE_ID) {
2108                    setInputMethodLocked(mCurMethodId, subtypeId);
2109                    return true;
2110                }
2111            }
2112            return false;
2113        }
2114    }
2115
2116    /**
2117     * Utility class for putting and getting settings for InputMethod
2118     * TODO: Move all putters and getters of settings to this class.
2119     */
2120    private static class InputMethodSettings {
2121        // The string for enabled input method is saved as follows:
2122        // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
2123        private static final char INPUT_METHOD_SEPARATER = ':';
2124        private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
2125        private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
2126                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
2127
2128        private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
2129                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
2130
2131        private final ContentResolver mResolver;
2132        private final HashMap<String, InputMethodInfo> mMethodMap;
2133        private final ArrayList<InputMethodInfo> mMethodList;
2134
2135        private String mEnabledInputMethodsStrCache;
2136
2137        private static void buildEnabledInputMethodsSettingString(
2138                StringBuilder builder, Pair<String, ArrayList<String>> pair) {
2139            String id = pair.first;
2140            ArrayList<String> subtypes = pair.second;
2141            builder.append(id);
2142            // Inputmethod and subtypes are saved in the settings as follows:
2143            // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
2144            for (String subtypeId: subtypes) {
2145                builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
2146            }
2147        }
2148
2149        public InputMethodSettings(
2150                ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap,
2151                ArrayList<InputMethodInfo> methodList) {
2152            mResolver = resolver;
2153            mMethodMap = methodMap;
2154            mMethodList = methodList;
2155        }
2156
2157        public List<InputMethodInfo> getEnabledInputMethodListLocked() {
2158            return createEnabledInputMethodListLocked(
2159                    getEnabledInputMethodsAndSubtypeListLocked());
2160        }
2161
2162        public List<Pair<InputMethodInfo, ArrayList<String>>>
2163                getEnabledInputMethodAndSubtypeHashCodeListLocked() {
2164            return createEnabledInputMethodAndSubtypeHashCodeListLocked(
2165                    getEnabledInputMethodsAndSubtypeListLocked());
2166        }
2167
2168        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
2169                InputMethodInfo imi) {
2170            List<Pair<String, ArrayList<String>>> imsList =
2171                    getEnabledInputMethodsAndSubtypeListLocked();
2172            ArrayList<InputMethodSubtype> enabledSubtypes =
2173                    new ArrayList<InputMethodSubtype>();
2174            if (imi != null) {
2175                for (Pair<String, ArrayList<String>> imsPair : imsList) {
2176                    InputMethodInfo info = mMethodMap.get(imsPair.first);
2177                    if (info != null && info.getId().equals(imi.getId())) {
2178                        ArrayList<InputMethodSubtype> subtypes = info.getSubtypes();
2179                        for (InputMethodSubtype ims: subtypes) {
2180                            for (String s: imsPair.second) {
2181                                if (String.valueOf(ims.hashCode()).equals(s)) {
2182                                    enabledSubtypes.add(ims);
2183                                }
2184                            }
2185                        }
2186                        break;
2187                    }
2188                }
2189            }
2190            return enabledSubtypes;
2191        }
2192
2193        // At the initial boot, the settings for input methods are not set,
2194        // so we need to enable IME in that case.
2195        public void enableAllIMEsIfThereIsNoEnabledIME() {
2196            if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
2197                StringBuilder sb = new StringBuilder();
2198                final int N = mMethodList.size();
2199                for (int i = 0; i < N; i++) {
2200                    InputMethodInfo imi = mMethodList.get(i);
2201                    Slog.i(TAG, "Adding: " + imi.getId());
2202                    if (i > 0) sb.append(':');
2203                    sb.append(imi.getId());
2204                }
2205                putEnabledInputMethodsStr(sb.toString());
2206            }
2207        }
2208
2209        public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
2210            ArrayList<Pair<String, ArrayList<String>>> imsList
2211                    = new ArrayList<Pair<String, ArrayList<String>>>();
2212            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
2213            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
2214                return imsList;
2215            }
2216            mInputMethodSplitter.setString(enabledInputMethodsStr);
2217            while (mInputMethodSplitter.hasNext()) {
2218                String nextImsStr = mInputMethodSplitter.next();
2219                mSubtypeSplitter.setString(nextImsStr);
2220                if (mSubtypeSplitter.hasNext()) {
2221                    ArrayList<String> subtypeHashes = new ArrayList<String>();
2222                    // The first element is ime id.
2223                    String imeId = mSubtypeSplitter.next();
2224                    while (mSubtypeSplitter.hasNext()) {
2225                        subtypeHashes.add(mSubtypeSplitter.next());
2226                    }
2227                    imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
2228                }
2229            }
2230            return imsList;
2231        }
2232
2233        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
2234            if (reloadInputMethodStr) {
2235                getEnabledInputMethodsStr();
2236            }
2237            if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
2238                // Add in the newly enabled input method.
2239                putEnabledInputMethodsStr(id);
2240            } else {
2241                putEnabledInputMethodsStr(
2242                        mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
2243            }
2244        }
2245
2246        /**
2247         * Build and put a string of EnabledInputMethods with removing specified Id.
2248         * @return the specified id was removed or not.
2249         */
2250        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2251                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
2252            boolean isRemoved = false;
2253            boolean needsAppendSeparator = false;
2254            for (Pair<String, ArrayList<String>> ims: imsList) {
2255                String curId = ims.first;
2256                if (curId.equals(id)) {
2257                    // We are disabling this input method, and it is
2258                    // currently enabled.  Skip it to remove from the
2259                    // new list.
2260                    isRemoved = true;
2261                } else {
2262                    if (needsAppendSeparator) {
2263                        builder.append(INPUT_METHOD_SEPARATER);
2264                    } else {
2265                        needsAppendSeparator = true;
2266                    }
2267                    buildEnabledInputMethodsSettingString(builder, ims);
2268                }
2269            }
2270            if (isRemoved) {
2271                // Update the setting with the new list of input methods.
2272                putEnabledInputMethodsStr(builder.toString());
2273            }
2274            return isRemoved;
2275        }
2276
2277        private List<InputMethodInfo> createEnabledInputMethodListLocked(
2278                List<Pair<String, ArrayList<String>>> imsList) {
2279            final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
2280            for (Pair<String, ArrayList<String>> ims: imsList) {
2281                InputMethodInfo info = mMethodMap.get(ims.first);
2282                if (info != null) {
2283                    res.add(info);
2284                }
2285            }
2286            return res;
2287        }
2288
2289        private List<Pair<InputMethodInfo, ArrayList<String>>>
2290                createEnabledInputMethodAndSubtypeHashCodeListLocked(
2291                        List<Pair<String, ArrayList<String>>> imsList) {
2292            final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
2293                    = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
2294            for (Pair<String, ArrayList<String>> ims : imsList) {
2295                InputMethodInfo info = mMethodMap.get(ims.first);
2296                if (info != null) {
2297                    res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
2298                }
2299            }
2300            return res;
2301        }
2302
2303        private void putEnabledInputMethodsStr(String str) {
2304            Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
2305            mEnabledInputMethodsStrCache = str;
2306        }
2307
2308        private String getEnabledInputMethodsStr() {
2309            mEnabledInputMethodsStrCache = Settings.Secure.getString(
2310                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
2311            if (DEBUG) {
2312                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
2313            }
2314            return mEnabledInputMethodsStrCache;
2315        }
2316
2317        private void saveSubtypeHistory(
2318                List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
2319            StringBuilder builder = new StringBuilder();
2320            boolean isImeAdded = false;
2321            if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
2322                builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2323                        newSubtypeId);
2324                isImeAdded = true;
2325            }
2326            for (Pair<String, String> ime: savedImes) {
2327                String imeId = ime.first;
2328                String subtypeId = ime.second;
2329                if (TextUtils.isEmpty(subtypeId)) {
2330                    subtypeId = NOT_A_SUBTYPE_ID_STR;
2331                }
2332                if (isImeAdded) {
2333                    builder.append(INPUT_METHOD_SEPARATER);
2334                } else {
2335                    isImeAdded = true;
2336                }
2337                builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2338                        subtypeId);
2339            }
2340            // Remove the last INPUT_METHOD_SEPARATER
2341            putSubtypeHistoryStr(builder.toString());
2342        }
2343
2344        public void addSubtypeToHistory(String imeId, String subtypeId) {
2345            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2346            for (Pair<String, String> ime: subtypeHistory) {
2347                if (ime.first.equals(imeId)) {
2348                    if (DEBUG) {
2349                        Slog.v(TAG, "Subtype found in the history: " + imeId
2350                                + ime.second);
2351                    }
2352                    // We should break here
2353                    subtypeHistory.remove(ime);
2354                    break;
2355                }
2356            }
2357            saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
2358        }
2359
2360        private void putSubtypeHistoryStr(String str) {
2361            if (DEBUG) {
2362                Slog.d(TAG, "putSubtypeHistoryStr: " + str);
2363            }
2364            Settings.Secure.putString(
2365                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
2366        }
2367
2368        public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
2369            // Gets the first one from the history
2370            return getLastSubtypeForInputMethodLockedInternal(null);
2371        }
2372
2373        public String getLastSubtypeForInputMethodLocked(String imeId) {
2374            Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
2375            if (ime != null) {
2376                return ime.second;
2377            } else {
2378                return null;
2379            }
2380        }
2381
2382        private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
2383            List<Pair<String, ArrayList<String>>> enabledImes =
2384                    getEnabledInputMethodsAndSubtypeListLocked();
2385            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2386            for (Pair<String, String> imeAndSubtype: subtypeHistory) {
2387                final String imeInTheHistory = imeAndSubtype.first;
2388                // If imeId is empty, returns the first IME and subtype in the history
2389                if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
2390                    final String subtypeInTheHistory = imeAndSubtype.second;
2391                    final String subtypeHashCode = getEnabledSubtypeForInputMethodAndSubtypeLocked(
2392                            enabledImes, imeInTheHistory, subtypeInTheHistory);
2393                    if (!TextUtils.isEmpty(subtypeHashCode)) {
2394                        if (DEBUG) {
2395                            Slog.d(TAG, "Enabled subtype found in the history:" + subtypeHashCode);
2396                        }
2397                        return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
2398                    }
2399                }
2400            }
2401            if (DEBUG) {
2402                Slog.d(TAG, "No enabled IME found in the history");
2403            }
2404            return null;
2405        }
2406
2407        private String getEnabledSubtypeForInputMethodAndSubtypeLocked(List<Pair<String,
2408                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
2409            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
2410                if (enabledIme.first.equals(imeId)) {
2411                    for (String s: enabledIme.second) {
2412                        if (s.equals(subtypeHashCode)) {
2413                            // If both imeId and subtypeId are enabled, return subtypeId.
2414                            return s;
2415                        }
2416                    }
2417                    // If imeId was enabled but subtypeId was disabled.
2418                    return NOT_A_SUBTYPE_ID_STR;
2419                }
2420            }
2421            // If both imeId and subtypeId are disabled, return null
2422            return null;
2423        }
2424
2425        private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
2426            ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
2427            final String subtypeHistoryStr = getSubtypeHistoryStr();
2428            if (TextUtils.isEmpty(subtypeHistoryStr)) {
2429                return imsList;
2430            }
2431            mInputMethodSplitter.setString(subtypeHistoryStr);
2432            while (mInputMethodSplitter.hasNext()) {
2433                String nextImsStr = mInputMethodSplitter.next();
2434                mSubtypeSplitter.setString(nextImsStr);
2435                if (mSubtypeSplitter.hasNext()) {
2436                    String subtypeId = NOT_A_SUBTYPE_ID_STR;
2437                    // The first element is ime id.
2438                    String imeId = mSubtypeSplitter.next();
2439                    while (mSubtypeSplitter.hasNext()) {
2440                        subtypeId = mSubtypeSplitter.next();
2441                        break;
2442                    }
2443                    imsList.add(new Pair<String, String>(imeId, subtypeId));
2444                }
2445            }
2446            return imsList;
2447        }
2448
2449        private String getSubtypeHistoryStr() {
2450            if (DEBUG) {
2451                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
2452                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
2453            }
2454            return Settings.Secure.getString(
2455                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
2456        }
2457
2458        public void putSelectedInputMethod(String imeId) {
2459            Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
2460        }
2461
2462        public void putSelectedSubtype(int subtypeId) {
2463            Settings.Secure.putInt(
2464                    mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
2465        }
2466    }
2467
2468    // ----------------------------------------------------------------------
2469
2470    @Override
2471    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2472        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
2473                != PackageManager.PERMISSION_GRANTED) {
2474
2475            pw.println("Permission Denial: can't dump InputMethodManager from from pid="
2476                    + Binder.getCallingPid()
2477                    + ", uid=" + Binder.getCallingUid());
2478            return;
2479        }
2480
2481        IInputMethod method;
2482        ClientState client;
2483
2484        final Printer p = new PrintWriterPrinter(pw);
2485
2486        synchronized (mMethodMap) {
2487            p.println("Current Input Method Manager state:");
2488            int N = mMethodList.size();
2489            p.println("  Input Methods:");
2490            for (int i=0; i<N; i++) {
2491                InputMethodInfo info = mMethodList.get(i);
2492                p.println("  InputMethod #" + i + ":");
2493                info.dump(p, "    ");
2494            }
2495            p.println("  Clients:");
2496            for (ClientState ci : mClients.values()) {
2497                p.println("  Client " + ci + ":");
2498                p.println("    client=" + ci.client);
2499                p.println("    inputContext=" + ci.inputContext);
2500                p.println("    sessionRequested=" + ci.sessionRequested);
2501                p.println("    curSession=" + ci.curSession);
2502            }
2503            p.println("  mCurMethodId=" + mCurMethodId);
2504            client = mCurClient;
2505            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
2506            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
2507            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
2508                    + " mBoundToMethod=" + mBoundToMethod);
2509            p.println("  mCurToken=" + mCurToken);
2510            p.println("  mCurIntent=" + mCurIntent);
2511            method = mCurMethod;
2512            p.println("  mCurMethod=" + mCurMethod);
2513            p.println("  mEnabledSession=" + mEnabledSession);
2514            p.println("  mShowRequested=" + mShowRequested
2515                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
2516                    + " mShowForced=" + mShowForced
2517                    + " mInputShown=" + mInputShown);
2518            p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
2519        }
2520
2521        p.println(" ");
2522        if (client != null) {
2523            pw.flush();
2524            try {
2525                client.client.asBinder().dump(fd, args);
2526            } catch (RemoteException e) {
2527                p.println("Input method client dead: " + e);
2528            }
2529        } else {
2530            p.println("No input method client.");
2531        }
2532
2533        p.println(" ");
2534        if (method != null) {
2535            pw.flush();
2536            try {
2537                method.asBinder().dump(fd, args);
2538            } catch (RemoteException e) {
2539                p.println("Input method service dead: " + e);
2540            }
2541        } else {
2542            p.println("No input method service.");
2543        }
2544    }
2545}
2546