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