InputMethodManagerService.java revision e3a7f628c6d9fef42be24999b3137ebe5c6f3525
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.text.Collator;
86import java.util.ArrayList;
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 Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap =
1754                new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance());
1755
1756            for (InputMethodInfo imi: immis.keySet()) {
1757                if (imi == null) continue;
1758                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
1759                HashSet<String> enabledSubtypeSet = new HashSet<String>();
1760                for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
1761                    enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
1762                }
1763                ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi);
1764                CharSequence label = imi.loadLabel(pm);
1765                if (showSubtypes && enabledSubtypeSet.size() > 0) {
1766                    final int subtypeCount = imi.getSubtypeCount();
1767                    for (int j = 0; j < subtypeCount; ++j) {
1768                        InputMethodSubtype subtype = imi.getSubtypeAt(j);
1769                        if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) {
1770                            CharSequence title;
1771                            int nameResId = subtype.getNameResId();
1772                            String mode = subtype.getMode();
1773                            if (nameResId != 0) {
1774                                title = pm.getText(imi.getPackageName(), nameResId,
1775                                        imi.getServiceInfo().applicationInfo);
1776                            } else {
1777                                CharSequence language = subtype.getLocale();
1778                                // TODO: Use more friendly Title and UI
1779                                title = label + "," + (mode == null ? "" : mode) + ","
1780                                        + (language == null ? "" : language);
1781                            }
1782                            imMap.put(title, new Pair<InputMethodInfo, Integer>(imi, j));
1783                        }
1784                    }
1785                } else {
1786                    imMap.put(label,
1787                            new Pair<InputMethodInfo, Integer>(imi, NOT_A_SUBTYPE_ID));
1788                }
1789            }
1790
1791            final int N = imMap.size();
1792            mItems = imMap.keySet().toArray(new CharSequence[N]);
1793            mIms = new InputMethodInfo[N];
1794            mSubtypeIds = new int[N];
1795            int checkedItem = 0;
1796            for (int i = 0; i < N; ++i) {
1797                Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]);
1798                mIms[i] = value.first;
1799                mSubtypeIds[i] = value.second;
1800                if (mIms[i].getId().equals(lastInputMethodId)) {
1801                    int subtypeId = mSubtypeIds[i];
1802                    if ((subtypeId == NOT_A_SUBTYPE_ID)
1803                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
1804                            || (subtypeId == lastInputMethodSubtypeId)) {
1805                        checkedItem = i;
1806                    }
1807                }
1808            }
1809
1810            AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
1811                public void onClick(DialogInterface dialog, int which) {
1812                    hideInputMethodMenu();
1813                }
1814            };
1815
1816            TypedArray a = context.obtainStyledAttributes(null,
1817                    com.android.internal.R.styleable.DialogPreference,
1818                    com.android.internal.R.attr.alertDialogStyle, 0);
1819            mDialogBuilder = new AlertDialog.Builder(context)
1820                    .setTitle(com.android.internal.R.string.select_input_method)
1821                    .setOnCancelListener(new OnCancelListener() {
1822                        public void onCancel(DialogInterface dialog) {
1823                            hideInputMethodMenu();
1824                        }
1825                    })
1826                    .setIcon(a.getDrawable(
1827                            com.android.internal.R.styleable.DialogPreference_dialogTitle));
1828            a.recycle();
1829
1830            mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
1831                    new AlertDialog.OnClickListener() {
1832                        public void onClick(DialogInterface dialog, int which) {
1833                            synchronized (mMethodMap) {
1834                                if (mIms == null || mIms.length <= which
1835                                        || mSubtypeIds == null || mSubtypeIds.length <= which) {
1836                                    return;
1837                                }
1838                                InputMethodInfo im = mIms[which];
1839                                int subtypeId = mSubtypeIds[which];
1840                                hideInputMethodMenu();
1841                                if (im != null) {
1842                                    if ((subtypeId < 0)
1843                                            || (subtypeId >= im.getSubtypeCount())) {
1844                                        subtypeId = NOT_A_SUBTYPE_ID;
1845                                    }
1846                                    setInputMethodLocked(im.getId(), subtypeId);
1847                                }
1848                            }
1849                        }
1850                    });
1851
1852            if (showSubtypes) {
1853                mDialogBuilder.setPositiveButton(
1854                        com.android.internal.R.string.configure_input_methods,
1855                        new DialogInterface.OnClickListener() {
1856                            public void onClick(DialogInterface dialog, int whichButton) {
1857                                showConfigureInputMethods();
1858                            }
1859                        });
1860            }
1861            mSwitchingDialog = mDialogBuilder.create();
1862            mSwitchingDialog.setCanceledOnTouchOutside(true);
1863            mSwitchingDialog.getWindow().setType(
1864                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1865            mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
1866            mSwitchingDialog.show();
1867        }
1868    }
1869
1870    void hideInputMethodMenu() {
1871        synchronized (mMethodMap) {
1872            hideInputMethodMenuLocked();
1873        }
1874    }
1875
1876    void hideInputMethodMenuLocked() {
1877        if (DEBUG) Slog.v(TAG, "Hide switching menu");
1878
1879        if (mSwitchingDialog != null) {
1880            mSwitchingDialog.dismiss();
1881            mSwitchingDialog = null;
1882        }
1883
1884        mDialogBuilder = null;
1885        mItems = null;
1886        mIms = null;
1887    }
1888
1889    // ----------------------------------------------------------------------
1890
1891    public boolean setInputMethodEnabled(String id, boolean enabled) {
1892        synchronized (mMethodMap) {
1893            if (mContext.checkCallingOrSelfPermission(
1894                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
1895                    != PackageManager.PERMISSION_GRANTED) {
1896                throw new SecurityException(
1897                        "Requires permission "
1898                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1899            }
1900
1901            long ident = Binder.clearCallingIdentity();
1902            try {
1903                return setInputMethodEnabledLocked(id, enabled);
1904            } finally {
1905                Binder.restoreCallingIdentity(ident);
1906            }
1907        }
1908    }
1909
1910    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
1911        // Make sure this is a valid input method.
1912        InputMethodInfo imm = mMethodMap.get(id);
1913        if (imm == null) {
1914            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1915        }
1916
1917        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
1918                .getEnabledInputMethodsAndSubtypeListLocked();
1919
1920        if (enabled) {
1921            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
1922                if (pair.first.equals(id)) {
1923                    // We are enabling this input method, but it is already enabled.
1924                    // Nothing to do. The previous state was enabled.
1925                    return true;
1926                }
1927            }
1928            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
1929            // Previous state was disabled.
1930            return false;
1931        } else {
1932            StringBuilder builder = new StringBuilder();
1933            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
1934                    builder, enabledInputMethodsList, id)) {
1935                // Disabled input method is currently selected, switch to another one.
1936                String selId = Settings.Secure.getString(mContext.getContentResolver(),
1937                        Settings.Secure.DEFAULT_INPUT_METHOD);
1938                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
1939                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
1940                    resetSelectedInputMethodAndSubtypeLocked("");
1941                }
1942                // Previous state was enabled.
1943                return true;
1944            } else {
1945                // We are disabling the input method but it is already disabled.
1946                // Nothing to do.  The previous state was disabled.
1947                return false;
1948            }
1949        }
1950    }
1951
1952    private boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
1953        if (subtype == null) return true;
1954        return !subtype.containsExtraValueKey(SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME);
1955    }
1956
1957    private void saveCurrentInputMethodAndSubtypeToHistory() {
1958        String subtypeId = NOT_A_SUBTYPE_ID_STR;
1959        if (mCurrentSubtype != null) {
1960            subtypeId = String.valueOf(mCurrentSubtype.hashCode());
1961        }
1962        if (canAddToLastInputMethod(mCurrentSubtype)) {
1963            mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
1964        }
1965    }
1966
1967    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
1968            boolean setSubtypeOnly) {
1969        mOldSystemSettingsVersion = getSystemSettingsVersion();
1970        // Update the history of InputMethod and Subtype
1971        saveCurrentInputMethodAndSubtypeToHistory();
1972
1973        // Set Subtype here
1974        if (imi == null || subtypeId < 0) {
1975            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
1976            mCurrentSubtype = null;
1977        } else {
1978            if (subtypeId < imi.getSubtypeCount()) {
1979                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
1980                mSettings.putSelectedSubtype(subtype.hashCode());
1981                mCurrentSubtype = subtype;
1982            } else {
1983                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
1984                mCurrentSubtype = null;
1985            }
1986        }
1987
1988        if (!setSubtypeOnly) {
1989            // Set InputMethod here
1990            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
1991        }
1992    }
1993
1994    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
1995        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
1996        int lastSubtypeId = NOT_A_SUBTYPE_ID;
1997        // newDefaultIme is empty when there is no candidate for the selected IME.
1998        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
1999            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
2000            if (subtypeHashCode != null) {
2001                try {
2002                    lastSubtypeId = getSubtypeIdFromHashCode(
2003                            imi, Integer.valueOf(subtypeHashCode));
2004                } catch (NumberFormatException e) {
2005                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
2006                }
2007            }
2008        }
2009        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
2010    }
2011
2012    private int getSelectedInputMethodSubtypeId(String id) {
2013        InputMethodInfo imi = mMethodMap.get(id);
2014        if (imi == null) {
2015            return NOT_A_SUBTYPE_ID;
2016        }
2017        int subtypeId;
2018        try {
2019            subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
2020                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
2021        } catch (SettingNotFoundException e) {
2022            return NOT_A_SUBTYPE_ID;
2023        }
2024        return getSubtypeIdFromHashCode(imi, subtypeId);
2025    }
2026
2027    private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
2028        if (imi != null) {
2029            final int subtypeCount = imi.getSubtypeCount();
2030            for (int i = 0; i < subtypeCount; ++i) {
2031                InputMethodSubtype ims = imi.getSubtypeAt(i);
2032                if (subtypeHashCode == ims.hashCode()) {
2033                    return i;
2034                }
2035            }
2036        }
2037        return NOT_A_SUBTYPE_ID;
2038    }
2039
2040    private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked(
2041            Resources res, List<InputMethodSubtype> subtypes) {
2042        final String systemLocale = res.getConfiguration().locale.toString();
2043        if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
2044        HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
2045                new HashMap<String, InputMethodSubtype>();
2046        final int N = subtypes.size();
2047        boolean containsKeyboardSubtype = false;
2048        for (int i = 0; i < N; ++i) {
2049            InputMethodSubtype subtype = subtypes.get(i);
2050            final String locale = subtype.getLocale();
2051            final String mode = subtype.getMode();
2052            // When system locale starts with subtype's locale, that subtype will be applicable
2053            // for system locale
2054            // For instance, it's clearly applicable for cases like system locale = en_US and
2055            // subtype = en, but it is not necessarily considered applicable for cases like system
2056            // locale = en and subtype = en_US.
2057            // We just call systemLocale.startsWith(locale) in this function because there is no
2058            // need to find applicable subtypes aggressively unlike
2059            // findLastResortApplicableSubtypeLocked.
2060            if (systemLocale.startsWith(locale)) {
2061                InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
2062                // If more applicable subtypes are contained, skip.
2063                if (applicableSubtype != null
2064                        && systemLocale.equals(applicableSubtype.getLocale())) continue;
2065                applicableModeAndSubtypesMap.put(mode, subtype);
2066                if (!containsKeyboardSubtype
2067                        && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) {
2068                    containsKeyboardSubtype = true;
2069                }
2070            }
2071        }
2072        ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
2073                applicableModeAndSubtypesMap.values());
2074        if (!containsKeyboardSubtype) {
2075            InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
2076                    res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
2077            if (lastResortKeyboardSubtype != null) {
2078                applicableSubtypes.add(lastResortKeyboardSubtype);
2079            }
2080        }
2081        return applicableSubtypes;
2082    }
2083
2084    /**
2085     * If there are no selected subtypes, tries finding the most applicable one according to the
2086     * given locale.
2087     * @param subtypes this function will search the most applicable subtype in subtypes
2088     * @param mode subtypes will be filtered by mode
2089     * @param locale subtypes will be filtered by locale
2090     * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
2091     * it will return the first subtype matched with mode
2092     * @return the most applicable subtypeId
2093     */
2094    private static InputMethodSubtype findLastResortApplicableSubtypeLocked(
2095            Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
2096            boolean canIgnoreLocaleAsLastResort) {
2097        if (subtypes == null || subtypes.size() == 0) {
2098            return null;
2099        }
2100        if (TextUtils.isEmpty(locale)) {
2101            locale = res.getConfiguration().locale.toString();
2102        }
2103        final String language = locale.substring(0, 2);
2104        boolean partialMatchFound = false;
2105        InputMethodSubtype applicableSubtype = null;
2106        InputMethodSubtype firstMatchedModeSubtype = null;
2107        final int N = subtypes.size();
2108        for (int i = 0; i < N; ++i) {
2109            InputMethodSubtype subtype = subtypes.get(i);
2110            final String subtypeLocale = subtype.getLocale();
2111            // An applicable subtype should match "mode". If mode is null, mode will be ignored,
2112            // and all subtypes with all modes can be candidates.
2113            if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
2114                if (firstMatchedModeSubtype == null) {
2115                    firstMatchedModeSubtype = subtype;
2116                }
2117                if (locale.equals(subtypeLocale)) {
2118                    // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
2119                    applicableSubtype = subtype;
2120                    break;
2121                } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
2122                    // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
2123                    applicableSubtype = subtype;
2124                    partialMatchFound = true;
2125                }
2126            }
2127        }
2128
2129        if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
2130            return firstMatchedModeSubtype;
2131        }
2132
2133        // The first subtype applicable to the system locale will be defined as the most applicable
2134        // subtype.
2135        if (DEBUG) {
2136            if (applicableSubtype != null) {
2137                Slog.d(TAG, "Applicable InputMethodSubtype was found: "
2138                        + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
2139            }
2140        }
2141        return applicableSubtype;
2142    }
2143
2144    // If there are no selected shortcuts, tries finding the most applicable ones.
2145    private Pair<InputMethodInfo, InputMethodSubtype>
2146            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
2147        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
2148        InputMethodInfo mostApplicableIMI = null;
2149        InputMethodSubtype mostApplicableSubtype = null;
2150        boolean foundInSystemIME = false;
2151
2152        // Search applicable subtype for each InputMethodInfo
2153        for (InputMethodInfo imi: imis) {
2154            final String imiId = imi.getId();
2155            if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
2156                continue;
2157            }
2158            InputMethodSubtype subtype = null;
2159            final List<InputMethodSubtype> enabledSubtypes =
2160                    getEnabledInputMethodSubtypeList(imi, true);
2161            // 1. Search by the current subtype's locale from enabledSubtypes.
2162            if (mCurrentSubtype != null) {
2163                subtype = findLastResortApplicableSubtypeLocked(
2164                        mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
2165            }
2166            // 2. Search by the system locale from enabledSubtypes.
2167            // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
2168            if (subtype == null) {
2169                subtype = findLastResortApplicableSubtypeLocked(
2170                        mRes, enabledSubtypes, mode, null, true);
2171            }
2172            // 4. Search by the current subtype's locale from all subtypes.
2173            if (subtype == null && mCurrentSubtype != null) {
2174                subtype = findLastResortApplicableSubtypeLocked(
2175                        mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false);
2176            }
2177            // 5. Search by the system locale from all subtypes.
2178            // 6. Search the first enabled subtype matched with mode from all subtypes.
2179            if (subtype == null) {
2180                subtype = findLastResortApplicableSubtypeLocked(
2181                        mRes, getSubtypes(imi), mode, null, true);
2182            }
2183            if (subtype != null) {
2184                if (imiId.equals(mCurMethodId)) {
2185                    // The current input method is the most applicable IME.
2186                    mostApplicableIMI = imi;
2187                    mostApplicableSubtype = subtype;
2188                    break;
2189                } else if (!foundInSystemIME) {
2190                    // The system input method is 2nd applicable IME.
2191                    mostApplicableIMI = imi;
2192                    mostApplicableSubtype = subtype;
2193                    if ((imi.getServiceInfo().applicationInfo.flags
2194                            & ApplicationInfo.FLAG_SYSTEM) != 0) {
2195                        foundInSystemIME = true;
2196                    }
2197                }
2198            }
2199        }
2200        if (DEBUG) {
2201            if (mostApplicableIMI != null) {
2202                Slog.w(TAG, "Most applicable shortcut input method was:"
2203                        + mostApplicableIMI.getId());
2204                if (mostApplicableSubtype != null) {
2205                    Slog.w(TAG, "Most applicable shortcut input method subtype was:"
2206                            + "," + mostApplicableSubtype.getMode() + ","
2207                            + mostApplicableSubtype.getLocale());
2208                }
2209            }
2210        }
2211        if (mostApplicableIMI != null) {
2212            return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
2213                    mostApplicableSubtype);
2214        } else {
2215            return null;
2216        }
2217    }
2218
2219    private static long getSystemSettingsVersion() {
2220        return SystemProperties.getLong(Settings.Secure.SYS_PROP_SETTING_VERSION, 0);
2221    }
2222
2223    /**
2224     * @return Return the current subtype of this input method.
2225     */
2226    public InputMethodSubtype getCurrentInputMethodSubtype() {
2227        boolean subtypeIsSelected = false;
2228        try {
2229            subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
2230                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
2231        } catch (SettingNotFoundException e) {
2232        }
2233        synchronized (mMethodMap) {
2234            if (!subtypeIsSelected || mCurrentSubtype == null) {
2235                String lastInputMethodId = Settings.Secure.getString(
2236                        mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2237                int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2238                if (subtypeId == NOT_A_SUBTYPE_ID) {
2239                    InputMethodInfo imi = mMethodMap.get(lastInputMethodId);
2240                    if (imi != null) {
2241                        // If there are no selected subtypes, the framework will try to find
2242                        // the most applicable subtype from explicitly or implicitly enabled
2243                        // subtypes.
2244                        List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
2245                                getEnabledInputMethodSubtypeList(imi, true);
2246                        // If there is only one explicitly or implicitly enabled subtype,
2247                        // just returns it.
2248                        if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
2249                            mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
2250                        } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
2251                            mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2252                                    mRes, explicitlyOrImplicitlyEnabledSubtypes,
2253                                    SUBTYPE_MODE_KEYBOARD, null, true);
2254                            if (mCurrentSubtype == null) {
2255                                mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2256                                        mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
2257                                        true);
2258                            }
2259                        }
2260                    }
2261                } else {
2262                    mCurrentSubtype =
2263                            getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId);
2264                }
2265            }
2266            return mCurrentSubtype;
2267        }
2268    }
2269
2270    private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
2271            InputMethodSubtype subtype) {
2272        if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
2273            mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
2274        } else {
2275            ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2276            subtypes.add(subtype);
2277            mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
2278        }
2279    }
2280
2281    // TODO: We should change the return type from List to List<Parcelable>
2282    public List getShortcutInputMethodsAndSubtypes() {
2283        synchronized (mMethodMap) {
2284            ArrayList<Object> ret = new ArrayList<Object>();
2285            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
2286                // If there are no selected shortcut subtypes, the framework will try to find
2287                // the most applicable subtype from all subtypes whose mode is
2288                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
2289                Pair<InputMethodInfo, InputMethodSubtype> info =
2290                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
2291                            SUBTYPE_MODE_VOICE);
2292                if (info != null) {
2293                    ret.add(info.first);
2294                    ret.add(info.second);
2295                }
2296                return ret;
2297            }
2298            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
2299                ret.add(imi);
2300                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
2301                    ret.add(subtype);
2302                }
2303            }
2304            return ret;
2305        }
2306    }
2307
2308    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
2309        synchronized (mMethodMap) {
2310            if (subtype != null && mCurMethodId != null) {
2311                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2312                int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
2313                if (subtypeId != NOT_A_SUBTYPE_ID) {
2314                    setInputMethodLocked(mCurMethodId, subtypeId);
2315                    return true;
2316                }
2317            }
2318            return false;
2319        }
2320    }
2321
2322    /**
2323     * Utility class for putting and getting settings for InputMethod
2324     * TODO: Move all putters and getters of settings to this class.
2325     */
2326    private static class InputMethodSettings {
2327        // The string for enabled input method is saved as follows:
2328        // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
2329        private static final char INPUT_METHOD_SEPARATER = ':';
2330        private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
2331        private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
2332                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
2333
2334        private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
2335                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
2336
2337        private final Resources mRes;
2338        private final ContentResolver mResolver;
2339        private final HashMap<String, InputMethodInfo> mMethodMap;
2340        private final ArrayList<InputMethodInfo> mMethodList;
2341
2342        private String mEnabledInputMethodsStrCache;
2343
2344        private static void buildEnabledInputMethodsSettingString(
2345                StringBuilder builder, Pair<String, ArrayList<String>> pair) {
2346            String id = pair.first;
2347            ArrayList<String> subtypes = pair.second;
2348            builder.append(id);
2349            // Inputmethod and subtypes are saved in the settings as follows:
2350            // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
2351            for (String subtypeId: subtypes) {
2352                builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
2353            }
2354        }
2355
2356        public InputMethodSettings(
2357                Resources res, ContentResolver resolver,
2358                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
2359            mRes = res;
2360            mResolver = resolver;
2361            mMethodMap = methodMap;
2362            mMethodList = methodList;
2363        }
2364
2365        public List<InputMethodInfo> getEnabledInputMethodListLocked() {
2366            return createEnabledInputMethodListLocked(
2367                    getEnabledInputMethodsAndSubtypeListLocked());
2368        }
2369
2370        public List<Pair<InputMethodInfo, ArrayList<String>>>
2371                getEnabledInputMethodAndSubtypeHashCodeListLocked() {
2372            return createEnabledInputMethodAndSubtypeHashCodeListLocked(
2373                    getEnabledInputMethodsAndSubtypeListLocked());
2374        }
2375
2376        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
2377                InputMethodInfo imi) {
2378            List<Pair<String, ArrayList<String>>> imsList =
2379                    getEnabledInputMethodsAndSubtypeListLocked();
2380            ArrayList<InputMethodSubtype> enabledSubtypes =
2381                    new ArrayList<InputMethodSubtype>();
2382            if (imi != null) {
2383                for (Pair<String, ArrayList<String>> imsPair : imsList) {
2384                    InputMethodInfo info = mMethodMap.get(imsPair.first);
2385                    if (info != null && info.getId().equals(imi.getId())) {
2386                        final int subtypeCount = info.getSubtypeCount();
2387                        for (int i = 0; i < subtypeCount; ++i) {
2388                            InputMethodSubtype ims = info.getSubtypeAt(i);
2389                            for (String s: imsPair.second) {
2390                                if (String.valueOf(ims.hashCode()).equals(s)) {
2391                                    enabledSubtypes.add(ims);
2392                                }
2393                            }
2394                        }
2395                        break;
2396                    }
2397                }
2398            }
2399            return enabledSubtypes;
2400        }
2401
2402        // At the initial boot, the settings for input methods are not set,
2403        // so we need to enable IME in that case.
2404        public void enableAllIMEsIfThereIsNoEnabledIME() {
2405            if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
2406                StringBuilder sb = new StringBuilder();
2407                final int N = mMethodList.size();
2408                for (int i = 0; i < N; i++) {
2409                    InputMethodInfo imi = mMethodList.get(i);
2410                    Slog.i(TAG, "Adding: " + imi.getId());
2411                    if (i > 0) sb.append(':');
2412                    sb.append(imi.getId());
2413                }
2414                putEnabledInputMethodsStr(sb.toString());
2415            }
2416        }
2417
2418        private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
2419            ArrayList<Pair<String, ArrayList<String>>> imsList
2420                    = new ArrayList<Pair<String, ArrayList<String>>>();
2421            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
2422            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
2423                return imsList;
2424            }
2425            mInputMethodSplitter.setString(enabledInputMethodsStr);
2426            while (mInputMethodSplitter.hasNext()) {
2427                String nextImsStr = mInputMethodSplitter.next();
2428                mSubtypeSplitter.setString(nextImsStr);
2429                if (mSubtypeSplitter.hasNext()) {
2430                    ArrayList<String> subtypeHashes = new ArrayList<String>();
2431                    // The first element is ime id.
2432                    String imeId = mSubtypeSplitter.next();
2433                    while (mSubtypeSplitter.hasNext()) {
2434                        subtypeHashes.add(mSubtypeSplitter.next());
2435                    }
2436                    imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
2437                }
2438            }
2439            return imsList;
2440        }
2441
2442        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
2443            if (reloadInputMethodStr) {
2444                getEnabledInputMethodsStr();
2445            }
2446            if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
2447                // Add in the newly enabled input method.
2448                putEnabledInputMethodsStr(id);
2449            } else {
2450                putEnabledInputMethodsStr(
2451                        mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
2452            }
2453        }
2454
2455        /**
2456         * Build and put a string of EnabledInputMethods with removing specified Id.
2457         * @return the specified id was removed or not.
2458         */
2459        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2460                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
2461            boolean isRemoved = false;
2462            boolean needsAppendSeparator = false;
2463            for (Pair<String, ArrayList<String>> ims: imsList) {
2464                String curId = ims.first;
2465                if (curId.equals(id)) {
2466                    // We are disabling this input method, and it is
2467                    // currently enabled.  Skip it to remove from the
2468                    // new list.
2469                    isRemoved = true;
2470                } else {
2471                    if (needsAppendSeparator) {
2472                        builder.append(INPUT_METHOD_SEPARATER);
2473                    } else {
2474                        needsAppendSeparator = true;
2475                    }
2476                    buildEnabledInputMethodsSettingString(builder, ims);
2477                }
2478            }
2479            if (isRemoved) {
2480                // Update the setting with the new list of input methods.
2481                putEnabledInputMethodsStr(builder.toString());
2482            }
2483            return isRemoved;
2484        }
2485
2486        private List<InputMethodInfo> createEnabledInputMethodListLocked(
2487                List<Pair<String, ArrayList<String>>> imsList) {
2488            final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
2489            for (Pair<String, ArrayList<String>> ims: imsList) {
2490                InputMethodInfo info = mMethodMap.get(ims.first);
2491                if (info != null) {
2492                    res.add(info);
2493                }
2494            }
2495            return res;
2496        }
2497
2498        private List<Pair<InputMethodInfo, ArrayList<String>>>
2499                createEnabledInputMethodAndSubtypeHashCodeListLocked(
2500                        List<Pair<String, ArrayList<String>>> imsList) {
2501            final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
2502                    = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
2503            for (Pair<String, ArrayList<String>> ims : imsList) {
2504                InputMethodInfo info = mMethodMap.get(ims.first);
2505                if (info != null) {
2506                    res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
2507                }
2508            }
2509            return res;
2510        }
2511
2512        private void putEnabledInputMethodsStr(String str) {
2513            Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
2514            mEnabledInputMethodsStrCache = str;
2515        }
2516
2517        private String getEnabledInputMethodsStr() {
2518            mEnabledInputMethodsStrCache = Settings.Secure.getString(
2519                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
2520            if (DEBUG) {
2521                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
2522            }
2523            return mEnabledInputMethodsStrCache;
2524        }
2525
2526        private void saveSubtypeHistory(
2527                List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
2528            StringBuilder builder = new StringBuilder();
2529            boolean isImeAdded = false;
2530            if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
2531                builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2532                        newSubtypeId);
2533                isImeAdded = true;
2534            }
2535            for (Pair<String, String> ime: savedImes) {
2536                String imeId = ime.first;
2537                String subtypeId = ime.second;
2538                if (TextUtils.isEmpty(subtypeId)) {
2539                    subtypeId = NOT_A_SUBTYPE_ID_STR;
2540                }
2541                if (isImeAdded) {
2542                    builder.append(INPUT_METHOD_SEPARATER);
2543                } else {
2544                    isImeAdded = true;
2545                }
2546                builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2547                        subtypeId);
2548            }
2549            // Remove the last INPUT_METHOD_SEPARATER
2550            putSubtypeHistoryStr(builder.toString());
2551        }
2552
2553        public void addSubtypeToHistory(String imeId, String subtypeId) {
2554            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2555            for (Pair<String, String> ime: subtypeHistory) {
2556                if (ime.first.equals(imeId)) {
2557                    if (DEBUG) {
2558                        Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
2559                                + ime.second);
2560                    }
2561                    // We should break here
2562                    subtypeHistory.remove(ime);
2563                    break;
2564                }
2565            }
2566            if (DEBUG) {
2567                Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
2568            }
2569            saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
2570        }
2571
2572        private void putSubtypeHistoryStr(String str) {
2573            if (DEBUG) {
2574                Slog.d(TAG, "putSubtypeHistoryStr: " + str);
2575            }
2576            Settings.Secure.putString(
2577                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
2578        }
2579
2580        public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
2581            // Gets the first one from the history
2582            return getLastSubtypeForInputMethodLockedInternal(null);
2583        }
2584
2585        public String getLastSubtypeForInputMethodLocked(String imeId) {
2586            Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
2587            if (ime != null) {
2588                return ime.second;
2589            } else {
2590                return null;
2591            }
2592        }
2593
2594        private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
2595            List<Pair<String, ArrayList<String>>> enabledImes =
2596                    getEnabledInputMethodsAndSubtypeListLocked();
2597            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2598            for (Pair<String, String> imeAndSubtype: subtypeHistory) {
2599                final String imeInTheHistory = imeAndSubtype.first;
2600                // If imeId is empty, returns the first IME and subtype in the history
2601                if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
2602                    final String subtypeInTheHistory = imeAndSubtype.second;
2603                    final String subtypeHashCode =
2604                            getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
2605                                    enabledImes, imeInTheHistory, subtypeInTheHistory);
2606                    if (!TextUtils.isEmpty(subtypeHashCode)) {
2607                        if (DEBUG) {
2608                            Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
2609                        }
2610                        return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
2611                    }
2612                }
2613            }
2614            if (DEBUG) {
2615                Slog.d(TAG, "No enabled IME found in the history");
2616            }
2617            return null;
2618        }
2619
2620        private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
2621                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
2622            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
2623                if (enabledIme.first.equals(imeId)) {
2624                    final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
2625                    if (explicitlyEnabledSubtypes.size() == 0) {
2626                        // If there are no explicitly enabled subtypes, applicable subtypes are
2627                        // enabled implicitly.
2628                        InputMethodInfo ime = mMethodMap.get(imeId);
2629                        // If IME is enabled and no subtypes are enabled, applicable subtypes
2630                        // are enabled implicitly, so needs to treat them to be enabled.
2631                        if (ime != null && ime.getSubtypeCount() > 0) {
2632                            List<InputMethodSubtype> implicitlySelectedSubtypes =
2633                                    getApplicableSubtypesLocked(mRes, getSubtypes(ime));
2634                            if (implicitlySelectedSubtypes != null) {
2635                                final int N = implicitlySelectedSubtypes.size();
2636                                for (int i = 0; i < N; ++i) {
2637                                    final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
2638                                    if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
2639                                        return subtypeHashCode;
2640                                    }
2641                                }
2642                            }
2643                        }
2644                    } else {
2645                        for (String s: explicitlyEnabledSubtypes) {
2646                            if (s.equals(subtypeHashCode)) {
2647                                // If both imeId and subtypeId are enabled, return subtypeId.
2648                                return s;
2649                            }
2650                        }
2651                    }
2652                    // If imeId was enabled but subtypeId was disabled.
2653                    return NOT_A_SUBTYPE_ID_STR;
2654                }
2655            }
2656            // If both imeId and subtypeId are disabled, return null
2657            return null;
2658        }
2659
2660        private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
2661            ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
2662            final String subtypeHistoryStr = getSubtypeHistoryStr();
2663            if (TextUtils.isEmpty(subtypeHistoryStr)) {
2664                return imsList;
2665            }
2666            mInputMethodSplitter.setString(subtypeHistoryStr);
2667            while (mInputMethodSplitter.hasNext()) {
2668                String nextImsStr = mInputMethodSplitter.next();
2669                mSubtypeSplitter.setString(nextImsStr);
2670                if (mSubtypeSplitter.hasNext()) {
2671                    String subtypeId = NOT_A_SUBTYPE_ID_STR;
2672                    // The first element is ime id.
2673                    String imeId = mSubtypeSplitter.next();
2674                    while (mSubtypeSplitter.hasNext()) {
2675                        subtypeId = mSubtypeSplitter.next();
2676                        break;
2677                    }
2678                    imsList.add(new Pair<String, String>(imeId, subtypeId));
2679                }
2680            }
2681            return imsList;
2682        }
2683
2684        private String getSubtypeHistoryStr() {
2685            if (DEBUG) {
2686                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
2687                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
2688            }
2689            return Settings.Secure.getString(
2690                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
2691        }
2692
2693        public void putSelectedInputMethod(String imeId) {
2694            Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
2695        }
2696
2697        public void putSelectedSubtype(int subtypeId) {
2698            Settings.Secure.putInt(
2699                    mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
2700        }
2701    }
2702
2703    // ----------------------------------------------------------------------
2704
2705    @Override
2706    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2707        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
2708                != PackageManager.PERMISSION_GRANTED) {
2709
2710            pw.println("Permission Denial: can't dump InputMethodManager from from pid="
2711                    + Binder.getCallingPid()
2712                    + ", uid=" + Binder.getCallingUid());
2713            return;
2714        }
2715
2716        IInputMethod method;
2717        ClientState client;
2718
2719        final Printer p = new PrintWriterPrinter(pw);
2720
2721        synchronized (mMethodMap) {
2722            p.println("Current Input Method Manager state:");
2723            int N = mMethodList.size();
2724            p.println("  Input Methods:");
2725            for (int i=0; i<N; i++) {
2726                InputMethodInfo info = mMethodList.get(i);
2727                p.println("  InputMethod #" + i + ":");
2728                info.dump(p, "    ");
2729            }
2730            p.println("  Clients:");
2731            for (ClientState ci : mClients.values()) {
2732                p.println("  Client " + ci + ":");
2733                p.println("    client=" + ci.client);
2734                p.println("    inputContext=" + ci.inputContext);
2735                p.println("    sessionRequested=" + ci.sessionRequested);
2736                p.println("    curSession=" + ci.curSession);
2737            }
2738            p.println("  mCurMethodId=" + mCurMethodId);
2739            client = mCurClient;
2740            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
2741            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
2742            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
2743                    + " mBoundToMethod=" + mBoundToMethod);
2744            p.println("  mCurToken=" + mCurToken);
2745            p.println("  mCurIntent=" + mCurIntent);
2746            method = mCurMethod;
2747            p.println("  mCurMethod=" + mCurMethod);
2748            p.println("  mEnabledSession=" + mEnabledSession);
2749            p.println("  mShowRequested=" + mShowRequested
2750                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
2751                    + " mShowForced=" + mShowForced
2752                    + " mInputShown=" + mInputShown);
2753            p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
2754        }
2755
2756        p.println(" ");
2757        if (client != null) {
2758            pw.flush();
2759            try {
2760                client.client.asBinder().dump(fd, args);
2761            } catch (RemoteException e) {
2762                p.println("Input method client dead: " + e);
2763            }
2764        } else {
2765            p.println("No input method client.");
2766        }
2767
2768        p.println(" ");
2769        if (method != null) {
2770            pw.flush();
2771            try {
2772                method.asBinder().dump(fd, args);
2773            } catch (RemoteException e) {
2774                p.println("Input method service dead: " + e);
2775            }
2776        } else {
2777            p.println("No input method service.");
2778        }
2779    }
2780}
2781