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