InputMethodManagerService.java revision d08a9238db0de62c956788ceebb227d099ad88c2
1/*
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 * use this file except in compliance with the License. You may obtain a copy of
5 * the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 * License for the specific language governing permissions and limitations under
13 * the License.
14 */
15
16package com.android.server;
17
18import com.android.internal.content.PackageMonitor;
19import com.android.internal.os.HandlerCaller;
20import com.android.internal.os.SomeArgs;
21import com.android.internal.util.FastXmlSerializer;
22import com.android.internal.view.IInputContext;
23import com.android.internal.view.IInputMethod;
24import com.android.internal.view.IInputMethodCallback;
25import com.android.internal.view.IInputMethodClient;
26import com.android.internal.view.IInputMethodManager;
27import com.android.internal.view.IInputMethodSession;
28import com.android.internal.view.InputBindResult;
29import com.android.server.EventLogTags;
30import com.android.server.wm.WindowManagerService;
31
32import org.xmlpull.v1.XmlPullParser;
33import org.xmlpull.v1.XmlPullParserException;
34import org.xmlpull.v1.XmlSerializer;
35
36import android.app.ActivityManagerNative;
37import android.app.AppGlobals;
38import android.app.AlertDialog;
39import android.app.IUserSwitchObserver;
40import android.app.KeyguardManager;
41import android.app.Notification;
42import android.app.NotificationManager;
43import android.app.PendingIntent;
44import android.content.BroadcastReceiver;
45import android.content.ComponentName;
46import android.content.ContentResolver;
47import android.content.Context;
48import android.content.DialogInterface;
49import android.content.DialogInterface.OnCancelListener;
50import android.content.Intent;
51import android.content.IntentFilter;
52import android.content.ServiceConnection;
53import android.content.pm.ApplicationInfo;
54import android.content.pm.IPackageManager;
55import android.content.pm.PackageManager;
56import android.content.pm.PackageManager.NameNotFoundException;
57import android.content.pm.ResolveInfo;
58import android.content.pm.ServiceInfo;
59import android.content.res.Configuration;
60import android.content.res.Resources;
61import android.content.res.TypedArray;
62import android.database.ContentObserver;
63import android.inputmethodservice.InputMethodService;
64import android.os.Binder;
65import android.os.Environment;
66import android.os.Handler;
67import android.os.IBinder;
68import android.os.IInterface;
69import android.os.IRemoteCallback;
70import android.os.Message;
71import android.os.Process;
72import android.os.Parcel;
73import android.os.RemoteException;
74import android.os.ResultReceiver;
75import android.os.ServiceManager;
76import android.os.SystemClock;
77import android.os.UserHandle;
78import android.provider.Settings;
79import android.provider.Settings.Secure;
80import android.provider.Settings.SettingNotFoundException;
81import android.text.TextUtils;
82import android.text.style.SuggestionSpan;
83import android.util.AtomicFile;
84import android.util.EventLog;
85import android.util.LruCache;
86import android.util.Pair;
87import android.util.PrintWriterPrinter;
88import android.util.Printer;
89import android.util.Slog;
90import android.util.Xml;
91import android.view.IWindowManager;
92import android.view.LayoutInflater;
93import android.view.View;
94import android.view.ViewGroup;
95import android.view.WindowManager;
96import android.view.inputmethod.EditorInfo;
97import android.view.inputmethod.InputBinding;
98import android.view.inputmethod.InputMethod;
99import android.view.inputmethod.InputMethodInfo;
100import android.view.inputmethod.InputMethodManager;
101import android.view.inputmethod.InputMethodSubtype;
102import android.widget.ArrayAdapter;
103import android.widget.CompoundButton;
104import android.widget.CompoundButton.OnCheckedChangeListener;
105import android.widget.RadioButton;
106import android.widget.Switch;
107import android.widget.TextView;
108
109import java.io.File;
110import java.io.FileDescriptor;
111import java.io.FileInputStream;
112import java.io.FileOutputStream;
113import java.io.IOException;
114import java.io.PrintWriter;
115import java.util.ArrayList;
116import java.util.Collections;
117import java.util.Comparator;
118import java.util.HashMap;
119import java.util.HashSet;
120import java.util.List;
121import java.util.Locale;
122import java.util.TreeMap;
123
124/**
125 * This class provides a system service that manages input methods.
126 */
127public class InputMethodManagerService extends IInputMethodManager.Stub
128        implements ServiceConnection, Handler.Callback {
129    static final boolean DEBUG = false;
130    static final String TAG = "InputMethodManagerService";
131
132    static final int MSG_SHOW_IM_PICKER = 1;
133    static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
134    static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
135    static final int MSG_SHOW_IM_CONFIG = 4;
136
137    static final int MSG_UNBIND_INPUT = 1000;
138    static final int MSG_BIND_INPUT = 1010;
139    static final int MSG_SHOW_SOFT_INPUT = 1020;
140    static final int MSG_HIDE_SOFT_INPUT = 1030;
141    static final int MSG_ATTACH_TOKEN = 1040;
142    static final int MSG_CREATE_SESSION = 1050;
143
144    static final int MSG_START_INPUT = 2000;
145    static final int MSG_RESTART_INPUT = 2010;
146
147    static final int MSG_UNBIND_METHOD = 3000;
148    static final int MSG_BIND_METHOD = 3010;
149    static final int MSG_SET_ACTIVE = 3020;
150
151    static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
152
153    static final long TIME_TO_RECONNECT = 10*1000;
154
155    static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
156
157    private static final int NOT_A_SUBTYPE_ID = -1;
158    private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
159    private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
160    private static final String SUBTYPE_MODE_VOICE = "voice";
161    private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
162    private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
163            "EnabledWhenDefaultIsNotAsciiCapable";
164    private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
165    private static final Locale ENGLISH_LOCALE = new Locale("en");
166
167    final Context mContext;
168    final Resources mRes;
169    final Handler mHandler;
170    final InputMethodSettings mSettings;
171    final SettingsObserver mSettingsObserver;
172    final IWindowManager mIWindowManager;
173    final HandlerCaller mCaller;
174    private InputMethodFileManager mFileManager;
175    private InputMethodAndSubtypeListManager mImListManager;
176    private final HardKeyboardListener mHardKeyboardListener;
177    private final WindowManagerService mWindowManagerService;
178
179    final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
180
181    // All known input methods.  mMethodMap also serves as the global
182    // lock for this class.
183    final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
184    final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
185    private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
186            new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
187
188    // Used to bring IME service up to visible adjustment while it is being shown.
189    final ServiceConnection mVisibleConnection = new ServiceConnection() {
190        @Override public void onServiceConnected(ComponentName name, IBinder service) {
191        }
192
193        @Override public void onServiceDisconnected(ComponentName name) {
194        }
195    };
196    boolean mVisibleBound = false;
197
198    // Ongoing notification
199    private NotificationManager mNotificationManager;
200    private KeyguardManager mKeyguardManager;
201    private StatusBarManagerService mStatusBar;
202    private Notification mImeSwitcherNotification;
203    private PendingIntent mImeSwitchPendingIntent;
204    private boolean mShowOngoingImeSwitcherForPhones;
205    private boolean mNotificationShown;
206    private final boolean mImeSelectedOnBoot;
207
208    class SessionState {
209        final ClientState client;
210        final IInputMethod method;
211        final IInputMethodSession session;
212
213        @Override
214        public String toString() {
215            return "SessionState{uid " + client.uid + " pid " + client.pid
216                    + " method " + Integer.toHexString(
217                            System.identityHashCode(method))
218                    + " session " + Integer.toHexString(
219                            System.identityHashCode(session))
220                    + "}";
221        }
222
223        SessionState(ClientState _client, IInputMethod _method,
224                IInputMethodSession _session) {
225            client = _client;
226            method = _method;
227            session = _session;
228        }
229    }
230
231    class ClientState {
232        final IInputMethodClient client;
233        final IInputContext inputContext;
234        final int uid;
235        final int pid;
236        final InputBinding binding;
237
238        boolean sessionRequested;
239        SessionState curSession;
240
241        @Override
242        public String toString() {
243            return "ClientState{" + Integer.toHexString(
244                    System.identityHashCode(this)) + " uid " + uid
245                    + " pid " + pid + "}";
246        }
247
248        ClientState(IInputMethodClient _client, IInputContext _inputContext,
249                int _uid, int _pid) {
250            client = _client;
251            inputContext = _inputContext;
252            uid = _uid;
253            pid = _pid;
254            binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
255        }
256    }
257
258    final HashMap<IBinder, ClientState> mClients
259            = new HashMap<IBinder, ClientState>();
260
261    /**
262     * Set once the system is ready to run third party code.
263     */
264    boolean mSystemReady;
265
266    /**
267     * Id of the currently selected input method.
268     */
269    String mCurMethodId;
270
271    /**
272     * The current binding sequence number, incremented every time there is
273     * a new bind performed.
274     */
275    int mCurSeq;
276
277    /**
278     * The client that is currently bound to an input method.
279     */
280    ClientState mCurClient;
281
282    /**
283     * The last window token that gained focus.
284     */
285    IBinder mCurFocusedWindow;
286
287    /**
288     * The input context last provided by the current client.
289     */
290    IInputContext mCurInputContext;
291
292    /**
293     * The attributes last provided by the current client.
294     */
295    EditorInfo mCurAttribute;
296
297    /**
298     * The input method ID of the input method service that we are currently
299     * connected to or in the process of connecting to.
300     */
301    String mCurId;
302
303    /**
304     * The current subtype of the current input method.
305     */
306    private InputMethodSubtype mCurrentSubtype;
307
308    // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
309    private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
310            mShortcutInputMethodsAndSubtypes =
311                new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
312
313    /**
314     * Set to true if our ServiceConnection is currently actively bound to
315     * a service (whether or not we have gotten its IBinder back yet).
316     */
317    boolean mHaveConnection;
318
319    /**
320     * Set if the client has asked for the input method to be shown.
321     */
322    boolean mShowRequested;
323
324    /**
325     * Set if we were explicitly told to show the input method.
326     */
327    boolean mShowExplicitlyRequested;
328
329    /**
330     * Set if we were forced to be shown.
331     */
332    boolean mShowForced;
333
334    /**
335     * Set if we last told the input method to show itself.
336     */
337    boolean mInputShown;
338
339    /**
340     * The Intent used to connect to the current input method.
341     */
342    Intent mCurIntent;
343
344    /**
345     * The token we have made for the currently active input method, to
346     * identify it in the future.
347     */
348    IBinder mCurToken;
349
350    /**
351     * If non-null, this is the input method service we are currently connected
352     * to.
353     */
354    IInputMethod mCurMethod;
355
356    /**
357     * Time that we last initiated a bind to the input method, to determine
358     * if we should try to disconnect and reconnect to it.
359     */
360    long mLastBindTime;
361
362    /**
363     * Have we called mCurMethod.bindInput()?
364     */
365    boolean mBoundToMethod;
366
367    /**
368     * Currently enabled session.  Only touched by service thread, not
369     * protected by a lock.
370     */
371    SessionState mEnabledSession;
372
373    /**
374     * True if the screen is on.  The value is true initially.
375     */
376    boolean mScreenOn = true;
377
378    int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
379    int mImeWindowVis;
380
381    private AlertDialog.Builder mDialogBuilder;
382    private AlertDialog mSwitchingDialog;
383    private View mSwitchingDialogTitleView;
384    private InputMethodInfo[] mIms;
385    private int[] mSubtypeIds;
386    private Locale mLastSystemLocale;
387    private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
388    private final IPackageManager mIPackageManager;
389
390    class SettingsObserver extends ContentObserver {
391        SettingsObserver(Handler handler) {
392            super(handler);
393            ContentResolver resolver = mContext.getContentResolver();
394            resolver.registerContentObserver(Settings.Secure.getUriFor(
395                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
396            resolver.registerContentObserver(Settings.Secure.getUriFor(
397                    Settings.Secure.ENABLED_INPUT_METHODS), false, this);
398            resolver.registerContentObserver(Settings.Secure.getUriFor(
399                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
400        }
401
402        @Override public void onChange(boolean selfChange) {
403            synchronized (mMethodMap) {
404                updateFromSettingsLocked();
405            }
406        }
407    }
408
409    class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
410        private void updateActive() {
411            // Inform the current client of the change in active status
412            if (mCurClient != null && mCurClient.client != null) {
413                executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
414                        MSG_SET_ACTIVE, mScreenOn ? 1 : 0, mCurClient));
415            }
416        }
417
418        @Override
419        public void onReceive(Context context, Intent intent) {
420            final String action = intent.getAction();
421            if (Intent.ACTION_SCREEN_ON.equals(action)) {
422                mScreenOn = true;
423                refreshImeWindowVisibilityLocked();
424                updateActive();
425                return;
426            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
427                mScreenOn = false;
428                setImeWindowVisibilityStatusHiddenLocked();
429                updateActive();
430                return;
431            } else if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
432                hideInputMethodMenu();
433                // No need to updateActive
434                return;
435            } else {
436                Slog.w(TAG, "Unexpected intent " + intent);
437            }
438        }
439    }
440
441    class MyPackageMonitor extends PackageMonitor {
442        private boolean isChangingPackagesOfCurrentUser() {
443            final int userId = getChangingUserId();
444            final boolean retval = userId == mSettings.getCurrentUserId();
445            if (DEBUG) {
446                Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
447            }
448            return retval;
449        }
450
451        @Override
452        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
453            if (!isChangingPackagesOfCurrentUser()) {
454                return false;
455            }
456            synchronized (mMethodMap) {
457                String curInputMethodId = mSettings.getSelectedInputMethod();
458                final int N = mMethodList.size();
459                if (curInputMethodId != null) {
460                    for (int i=0; i<N; i++) {
461                        InputMethodInfo imi = mMethodList.get(i);
462                        if (imi.getId().equals(curInputMethodId)) {
463                            for (String pkg : packages) {
464                                if (imi.getPackageName().equals(pkg)) {
465                                    if (!doit) {
466                                        return true;
467                                    }
468                                    resetSelectedInputMethodAndSubtypeLocked("");
469                                    chooseNewDefaultIMELocked();
470                                    return true;
471                                }
472                            }
473                        }
474                    }
475                }
476            }
477            return false;
478        }
479
480        @Override
481        public void onSomePackagesChanged() {
482            if (!isChangingPackagesOfCurrentUser()) {
483                return;
484            }
485            synchronized (mMethodMap) {
486                InputMethodInfo curIm = null;
487                String curInputMethodId = mSettings.getSelectedInputMethod();
488                final int N = mMethodList.size();
489                if (curInputMethodId != null) {
490                    for (int i=0; i<N; i++) {
491                        InputMethodInfo imi = mMethodList.get(i);
492                        final String imiId = imi.getId();
493                        if (imiId.equals(curInputMethodId)) {
494                            curIm = imi;
495                        }
496
497                        int change = isPackageDisappearing(imi.getPackageName());
498                        if (isPackageModified(imi.getPackageName())) {
499                            mFileManager.deleteAllInputMethodSubtypes(imiId);
500                        }
501                        if (change == PACKAGE_TEMPORARY_CHANGE
502                                || change == PACKAGE_PERMANENT_CHANGE) {
503                            Slog.i(TAG, "Input method uninstalled, disabling: "
504                                    + imi.getComponent());
505                            setInputMethodEnabledLocked(imi.getId(), false);
506                        }
507                    }
508                }
509
510                buildInputMethodListLocked(mMethodList, mMethodMap);
511
512                boolean changed = false;
513
514                if (curIm != null) {
515                    int change = isPackageDisappearing(curIm.getPackageName());
516                    if (change == PACKAGE_TEMPORARY_CHANGE
517                            || change == PACKAGE_PERMANENT_CHANGE) {
518                        ServiceInfo si = null;
519                        try {
520                            si = mIPackageManager.getServiceInfo(
521                                    curIm.getComponent(), 0, mSettings.getCurrentUserId());
522                        } catch (RemoteException ex) {
523                        }
524                        if (si == null) {
525                            // Uh oh, current input method is no longer around!
526                            // Pick another one...
527                            Slog.i(TAG, "Current input method removed: " + curInputMethodId);
528                            setImeWindowVisibilityStatusHiddenLocked();
529                            if (!chooseNewDefaultIMELocked()) {
530                                changed = true;
531                                curIm = null;
532                                Slog.i(TAG, "Unsetting current input method");
533                                resetSelectedInputMethodAndSubtypeLocked("");
534                            }
535                        }
536                    }
537                }
538
539                if (curIm == null) {
540                    // We currently don't have a default input method... is
541                    // one now available?
542                    changed = chooseNewDefaultIMELocked();
543                }
544
545                if (changed) {
546                    updateFromSettingsLocked();
547                }
548            }
549        }
550    }
551
552    private static class MethodCallback extends IInputMethodCallback.Stub {
553        private final IInputMethod mMethod;
554        private final InputMethodManagerService mParentIMMS;
555
556        MethodCallback(final IInputMethod method, final InputMethodManagerService imms) {
557            mMethod = method;
558            mParentIMMS = imms;
559        }
560
561        @Override
562        public void finishedEvent(int seq, boolean handled) throws RemoteException {
563        }
564
565        @Override
566        public void sessionCreated(IInputMethodSession session) throws RemoteException {
567            mParentIMMS.onSessionCreated(mMethod, session);
568        }
569    }
570
571    private class HardKeyboardListener
572            implements WindowManagerService.OnHardKeyboardStatusChangeListener {
573        @Override
574        public void onHardKeyboardStatusChange(boolean available, boolean enabled) {
575            mHandler.sendMessage(mHandler.obtainMessage(
576                    MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0, enabled ? 1 : 0));
577        }
578
579        public void handleHardKeyboardStatusChange(boolean available, boolean enabled) {
580            if (DEBUG) {
581                Slog.w(TAG, "HardKeyboardStatusChanged: available = " + available + ", enabled = "
582                        + enabled);
583            }
584            synchronized(mMethodMap) {
585                if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
586                        && mSwitchingDialog.isShowing()) {
587                    mSwitchingDialogTitleView.findViewById(
588                            com.android.internal.R.id.hard_keyboard_section).setVisibility(
589                                    available ? View.VISIBLE : View.GONE);
590                }
591            }
592        }
593    }
594
595    public InputMethodManagerService(Context context, WindowManagerService windowManager) {
596        mIPackageManager = AppGlobals.getPackageManager();
597        mContext = context;
598        mRes = context.getResources();
599        mHandler = new Handler(this);
600        mIWindowManager = IWindowManager.Stub.asInterface(
601                ServiceManager.getService(Context.WINDOW_SERVICE));
602        mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
603            @Override
604            public void executeMessage(Message msg) {
605                handleMessage(msg);
606            }
607        });
608        mWindowManagerService = windowManager;
609        mHardKeyboardListener = new HardKeyboardListener();
610
611        mImeSwitcherNotification = new Notification();
612        mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default;
613        mImeSwitcherNotification.when = 0;
614        mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT;
615        mImeSwitcherNotification.tickerText = null;
616        mImeSwitcherNotification.defaults = 0; // please be quiet
617        mImeSwitcherNotification.sound = null;
618        mImeSwitcherNotification.vibrate = null;
619
620        // Tag this notification specially so SystemUI knows it's important
621        mImeSwitcherNotification.kind = new String[] { "android.system.imeswitcher" };
622
623        Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
624        mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
625
626        mShowOngoingImeSwitcherForPhones = false;
627
628        final IntentFilter broadcastFilter = new IntentFilter();
629        broadcastFilter.addAction(Intent.ACTION_SCREEN_ON);
630        broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF);
631        broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
632        mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
633
634        mNotificationShown = false;
635        int userId = 0;
636        try {
637            ActivityManagerNative.getDefault().registerUserSwitchObserver(
638                    new IUserSwitchObserver.Stub() {
639                        @Override
640                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
641                            synchronized(mMethodMap) {
642                                switchUserLocked(newUserId);
643                            }
644                            if (reply != null) {
645                                try {
646                                    reply.sendResult(null);
647                                } catch (RemoteException e) {
648                                }
649                            }
650                        }
651
652                        @Override
653                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
654                        }
655                    });
656            userId = ActivityManagerNative.getDefault().getCurrentUser().id;
657        } catch (RemoteException e) {
658            Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
659        }
660        mMyPackageMonitor.register(mContext, null, true);
661
662        // mSettings should be created before buildInputMethodListLocked
663        mSettings = new InputMethodSettings(
664                mRes, context.getContentResolver(), mMethodMap, mMethodList, userId);
665        mFileManager = new InputMethodFileManager(mMethodMap, userId);
666        mImListManager = new InputMethodAndSubtypeListManager(context, this);
667
668        // Just checking if defaultImiId is empty or not
669        final String defaultImiId = mSettings.getSelectedInputMethod();
670        mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
671
672        buildInputMethodListLocked(mMethodList, mMethodMap);
673        mSettings.enableAllIMEsIfThereIsNoEnabledIME();
674
675        if (!mImeSelectedOnBoot) {
676            Slog.w(TAG, "No IME selected. Choose the most applicable IME.");
677            resetDefaultImeLocked(context);
678        }
679
680        mSettingsObserver = new SettingsObserver(mHandler);
681        updateFromSettingsLocked();
682
683        // IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME
684        // according to the new system locale.
685        final IntentFilter filter = new IntentFilter();
686        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
687        mContext.registerReceiver(
688                new BroadcastReceiver() {
689                    @Override
690                    public void onReceive(Context context, Intent intent) {
691                        synchronized(mMethodMap) {
692                            checkCurrentLocaleChangedLocked();
693                        }
694                    }
695                }, filter);
696    }
697
698    private void resetDefaultImeLocked(Context context) {
699        // Do not reset the default (current) IME when it is a 3rd-party IME
700        if (mCurMethodId != null && !isSystemIme(mMethodMap.get(mCurMethodId))) {
701            return;
702        }
703
704        InputMethodInfo defIm = null;
705        for (InputMethodInfo imi : mMethodList) {
706            if (defIm == null) {
707                if (isValidSystemDefaultIme(imi, context)) {
708                    defIm = imi;
709                    Slog.i(TAG, "Selected default: " + imi.getId());
710                }
711            }
712        }
713        if (defIm == null && mMethodList.size() > 0) {
714            defIm = getMostApplicableDefaultIMELocked();
715            Slog.i(TAG, "No default found, using " + defIm.getId());
716        }
717        if (defIm != null) {
718            setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
719        }
720    }
721
722    private void resetAllInternalStateLocked(boolean updateOnlyWhenLocaleChanged) {
723        if (!mSystemReady) {
724            // not system ready
725            return;
726        }
727        final Locale newLocale = mRes.getConfiguration().locale;
728        if (!updateOnlyWhenLocaleChanged
729                || (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
730            if (!updateOnlyWhenLocaleChanged) {
731                hideCurrentInputLocked(0, null);
732                mCurMethodId = null;
733                unbindCurrentMethodLocked(true);
734            }
735            if (DEBUG) {
736                Slog.i(TAG, "Locale has been changed to " + newLocale);
737            }
738            // InputMethodAndSubtypeListManager should be reset when the locale is changed.
739            mImListManager = new InputMethodAndSubtypeListManager(mContext, this);
740            buildInputMethodListLocked(mMethodList, mMethodMap);
741            if (!updateOnlyWhenLocaleChanged) {
742                final String selectedImiId = mSettings.getSelectedInputMethod();
743                if (TextUtils.isEmpty(selectedImiId)) {
744                    // This is the first time of the user switch and
745                    // set the current ime to the proper one.
746                    resetDefaultImeLocked(mContext);
747                }
748            } else {
749                // If the locale is changed, needs to reset the default ime
750                resetDefaultImeLocked(mContext);
751            }
752            updateFromSettingsLocked();
753            mLastSystemLocale = newLocale;
754            if (!updateOnlyWhenLocaleChanged) {
755                try {
756                    startInputInnerLocked();
757                } catch (RuntimeException e) {
758                    Slog.w(TAG, "Unexpected exception", e);
759                }
760            }
761        }
762    }
763
764    private void checkCurrentLocaleChangedLocked() {
765        resetAllInternalStateLocked(true);
766    }
767
768    private void switchUserLocked(int newUserId) {
769        mSettings.setCurrentUserId(newUserId);
770        // InputMethodFileManager should be reset when the user is changed
771        mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
772        resetAllInternalStateLocked(false);
773    }
774
775    private boolean isValidSystemDefaultIme(InputMethodInfo imi, Context context) {
776        if (!mSystemReady) {
777            return false;
778        }
779        if (!isSystemIme(imi)) {
780            return false;
781        }
782        if (imi.getIsDefaultResourceId() != 0) {
783            try {
784                Resources res = context.createPackageContext(
785                        imi.getPackageName(), 0).getResources();
786                if (res.getBoolean(imi.getIsDefaultResourceId())
787                        && containsSubtypeOf(imi, context.getResources().getConfiguration().
788                                locale.getLanguage())) {
789                    return true;
790                }
791            } catch (PackageManager.NameNotFoundException ex) {
792            } catch (Resources.NotFoundException ex) {
793            }
794        }
795        if (imi.getSubtypeCount() == 0) {
796            Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName());
797        }
798        return false;
799    }
800
801    private static boolean isSystemImeThatHasEnglishSubtype(InputMethodInfo imi) {
802        if (!isSystemIme(imi)) {
803            return false;
804        }
805        return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage());
806    }
807
808    private static boolean containsSubtypeOf(InputMethodInfo imi, String language) {
809        final int N = imi.getSubtypeCount();
810        for (int i = 0; i < N; ++i) {
811            if (imi.getSubtypeAt(i).getLocale().startsWith(language)) {
812                return true;
813            }
814        }
815        return false;
816    }
817
818    @Override
819    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
820            throws RemoteException {
821        try {
822            return super.onTransact(code, data, reply, flags);
823        } catch (RuntimeException e) {
824            // The input method manager only throws security exceptions, so let's
825            // log all others.
826            if (!(e instanceof SecurityException)) {
827                Slog.e(TAG, "Input Method Manager Crash", e);
828            }
829            throw e;
830        }
831    }
832
833    public void systemReady(StatusBarManagerService statusBar) {
834        synchronized (mMethodMap) {
835            if (DEBUG) {
836                Slog.d(TAG, "--- systemReady");
837            }
838            if (!mSystemReady) {
839                mSystemReady = true;
840                mKeyguardManager =
841                        (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
842                mNotificationManager = (NotificationManager)
843                        mContext.getSystemService(Context.NOTIFICATION_SERVICE);
844                mStatusBar = statusBar;
845                statusBar.setIconVisibility("ime", false);
846                updateImeWindowStatusLocked();
847                mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
848                        com.android.internal.R.bool.show_ongoing_ime_switcher);
849                if (mShowOngoingImeSwitcherForPhones) {
850                    mWindowManagerService.setOnHardKeyboardStatusChangeListener(
851                            mHardKeyboardListener);
852                }
853                buildInputMethodListLocked(mMethodList, mMethodMap);
854                if (!mImeSelectedOnBoot) {
855                    Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
856                    checkCurrentLocaleChangedLocked();
857                }
858                mLastSystemLocale = mRes.getConfiguration().locale;
859                try {
860                    startInputInnerLocked();
861                } catch (RuntimeException e) {
862                    Slog.w(TAG, "Unexpected exception", e);
863                }
864            }
865        }
866    }
867
868    private void setImeWindowVisibilityStatusHiddenLocked() {
869        mImeWindowVis = 0;
870        updateImeWindowStatusLocked();
871    }
872
873    private void refreshImeWindowVisibilityLocked() {
874        final Configuration conf = mRes.getConfiguration();
875        final boolean haveHardKeyboard = conf.keyboard
876                != Configuration.KEYBOARD_NOKEYS;
877        final boolean hardKeyShown = haveHardKeyboard
878                && conf.hardKeyboardHidden
879                        != Configuration.HARDKEYBOARDHIDDEN_YES;
880        final boolean isScreenLocked = mKeyguardManager != null
881                && mKeyguardManager.isKeyguardLocked()
882                && mKeyguardManager.isKeyguardSecure();
883        mImeWindowVis = (!isScreenLocked && (mInputShown || hardKeyShown)) ?
884                (InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE) : 0;
885        updateImeWindowStatusLocked();
886    }
887
888    private void updateImeWindowStatusLocked() {
889        setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition);
890    }
891
892    // ---------------------------------------------------------------------------------------
893    // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
894    // 1) it comes from the system process
895    // 2) the calling process' user id is identical to the current user id IMMS thinks.
896    private boolean calledFromValidUser() {
897        final int uid = Binder.getCallingUid();
898        final int userId = UserHandle.getUserId(uid);
899        if (DEBUG) {
900            Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
901                    + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
902                    + " calling userId = " + userId + ", foreground user id = "
903                    + mSettings.getCurrentUserId());
904        }
905        if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
906            return true;
907        } else {
908            Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace());
909            return false;
910        }
911    }
912
913    private boolean bindCurrentInputMethodService(
914            Intent service, ServiceConnection conn, int flags) {
915        if (service == null || conn == null) {
916            Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
917            return false;
918        }
919        return mContext.bindService(service, conn, flags, mSettings.getCurrentUserId());
920    }
921
922    @Override
923    public List<InputMethodInfo> getInputMethodList() {
924        // TODO: Make this work even for non-current users?
925        if (!calledFromValidUser()) {
926            return Collections.emptyList();
927        }
928        synchronized (mMethodMap) {
929            return new ArrayList<InputMethodInfo>(mMethodList);
930        }
931    }
932
933    @Override
934    public List<InputMethodInfo> getEnabledInputMethodList() {
935        // TODO: Make this work even for non-current users?
936        if (!calledFromValidUser()) {
937            return Collections.emptyList();
938        }
939        synchronized (mMethodMap) {
940            return mSettings.getEnabledInputMethodListLocked();
941        }
942    }
943
944    private HashMap<InputMethodInfo, List<InputMethodSubtype>>
945            getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
946        HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
947                new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
948        for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) {
949            enabledInputMethodAndSubtypes.put(
950                    imi, getEnabledInputMethodSubtypeListLocked(imi, true));
951        }
952        return enabledInputMethodAndSubtypes;
953    }
954
955    public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi,
956            boolean allowsImplicitlySelectedSubtypes) {
957        if (imi == null && mCurMethodId != null) {
958            imi = mMethodMap.get(mCurMethodId);
959        }
960        List<InputMethodSubtype> enabledSubtypes =
961                mSettings.getEnabledInputMethodSubtypeListLocked(imi);
962        if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
963            enabledSubtypes = getImplicitlyApplicableSubtypesLocked(mRes, imi);
964        }
965        return InputMethodSubtype.sort(mContext, 0, imi, enabledSubtypes);
966    }
967
968    @Override
969    public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
970            boolean allowsImplicitlySelectedSubtypes) {
971        // TODO: Make this work even for non-current users?
972        if (!calledFromValidUser()) {
973            return Collections.emptyList();
974        }
975        synchronized (mMethodMap) {
976            return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes);
977        }
978    }
979
980    @Override
981    public void addClient(IInputMethodClient client,
982            IInputContext inputContext, int uid, int pid) {
983        if (!calledFromValidUser()) {
984            return;
985        }
986        synchronized (mMethodMap) {
987            mClients.put(client.asBinder(), new ClientState(client,
988                    inputContext, uid, pid));
989        }
990    }
991
992    @Override
993    public void removeClient(IInputMethodClient client) {
994        if (!calledFromValidUser()) {
995            return;
996        }
997        synchronized (mMethodMap) {
998            mClients.remove(client.asBinder());
999        }
1000    }
1001
1002    void executeOrSendMessage(IInterface target, Message msg) {
1003         if (target.asBinder() instanceof Binder) {
1004             mCaller.sendMessage(msg);
1005         } else {
1006             handleMessage(msg);
1007             msg.recycle();
1008         }
1009    }
1010
1011    void unbindCurrentClientLocked() {
1012        if (mCurClient != null) {
1013            if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
1014                    + mCurClient.client.asBinder());
1015            if (mBoundToMethod) {
1016                mBoundToMethod = false;
1017                if (mCurMethod != null) {
1018                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1019                            MSG_UNBIND_INPUT, mCurMethod));
1020                }
1021            }
1022
1023            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
1024                    MSG_SET_ACTIVE, 0, mCurClient));
1025            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
1026                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
1027            mCurClient.sessionRequested = false;
1028            mCurClient = null;
1029
1030            hideInputMethodMenuLocked();
1031        }
1032    }
1033
1034    private int getImeShowFlags() {
1035        int flags = 0;
1036        if (mShowForced) {
1037            flags |= InputMethod.SHOW_FORCED
1038                    | InputMethod.SHOW_EXPLICIT;
1039        } else if (mShowExplicitlyRequested) {
1040            flags |= InputMethod.SHOW_EXPLICIT;
1041        }
1042        return flags;
1043    }
1044
1045    private int getAppShowFlags() {
1046        int flags = 0;
1047        if (mShowForced) {
1048            flags |= InputMethodManager.SHOW_FORCED;
1049        } else if (!mShowExplicitlyRequested) {
1050            flags |= InputMethodManager.SHOW_IMPLICIT;
1051        }
1052        return flags;
1053    }
1054
1055    InputBindResult attachNewInputLocked(boolean initial) {
1056        if (!mBoundToMethod) {
1057            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1058                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
1059            mBoundToMethod = true;
1060        }
1061        final SessionState session = mCurClient.curSession;
1062        if (initial) {
1063            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
1064                    MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
1065        } else {
1066            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
1067                    MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
1068        }
1069        if (mShowRequested) {
1070            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
1071            showCurrentInputLocked(getAppShowFlags(), null);
1072        }
1073        return new InputBindResult(session.session, mCurId, mCurSeq);
1074    }
1075
1076    InputBindResult startInputLocked(IInputMethodClient client,
1077            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
1078        // If no method is currently selected, do nothing.
1079        if (mCurMethodId == null) {
1080            return mNoBinding;
1081        }
1082
1083        ClientState cs = mClients.get(client.asBinder());
1084        if (cs == null) {
1085            throw new IllegalArgumentException("unknown client "
1086                    + client.asBinder());
1087        }
1088
1089        try {
1090            if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
1091                // Check with the window manager to make sure this client actually
1092                // has a window with focus.  If not, reject.  This is thread safe
1093                // because if the focus changes some time before or after, the
1094                // next client receiving focus that has any interest in input will
1095                // be calling through here after that change happens.
1096                Slog.w(TAG, "Starting input on non-focused client " + cs.client
1097                        + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
1098                return null;
1099            }
1100        } catch (RemoteException e) {
1101        }
1102
1103        return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
1104    }
1105
1106    InputBindResult startInputUncheckedLocked(ClientState cs,
1107            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
1108        // If no method is currently selected, do nothing.
1109        if (mCurMethodId == null) {
1110            return mNoBinding;
1111        }
1112
1113        if (mCurClient != cs) {
1114            // If the client is changing, we need to switch over to the new
1115            // one.
1116            unbindCurrentClientLocked();
1117            if (DEBUG) Slog.v(TAG, "switching to client: client = "
1118                    + cs.client.asBinder());
1119
1120            // If the screen is on, inform the new client it is active
1121            if (mScreenOn) {
1122                executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
1123                        MSG_SET_ACTIVE, mScreenOn ? 1 : 0, cs));
1124            }
1125        }
1126
1127        // Bump up the sequence for this client and attach it.
1128        mCurSeq++;
1129        if (mCurSeq <= 0) mCurSeq = 1;
1130        mCurClient = cs;
1131        mCurInputContext = inputContext;
1132        mCurAttribute = attribute;
1133
1134        // Check if the input method is changing.
1135        if (mCurId != null && mCurId.equals(mCurMethodId)) {
1136            if (cs.curSession != null) {
1137                // Fast case: if we are already connected to the input method,
1138                // then just return it.
1139                return attachNewInputLocked(
1140                        (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
1141            }
1142            if (mHaveConnection) {
1143                if (mCurMethod != null) {
1144                    if (!cs.sessionRequested) {
1145                        cs.sessionRequested = true;
1146                        if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
1147                        executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1148                                MSG_CREATE_SESSION, mCurMethod,
1149                                new MethodCallback(mCurMethod, this)));
1150                    }
1151                    // Return to client, and we will get back with it when
1152                    // we have had a session made for it.
1153                    return new InputBindResult(null, mCurId, mCurSeq);
1154                } else if (SystemClock.uptimeMillis()
1155                        < (mLastBindTime+TIME_TO_RECONNECT)) {
1156                    // In this case we have connected to the service, but
1157                    // don't yet have its interface.  If it hasn't been too
1158                    // long since we did the connection, we'll return to
1159                    // the client and wait to get the service interface so
1160                    // we can report back.  If it has been too long, we want
1161                    // to fall through so we can try a disconnect/reconnect
1162                    // to see if we can get back in touch with the service.
1163                    return new InputBindResult(null, mCurId, mCurSeq);
1164                } else {
1165                    EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
1166                            mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
1167                }
1168            }
1169        }
1170
1171        return startInputInnerLocked();
1172    }
1173
1174    InputBindResult startInputInnerLocked() {
1175        if (mCurMethodId == null) {
1176            return mNoBinding;
1177        }
1178
1179        if (!mSystemReady) {
1180            // If the system is not yet ready, we shouldn't be running third
1181            // party code.
1182            return new InputBindResult(null, mCurMethodId, mCurSeq);
1183        }
1184
1185        InputMethodInfo info = mMethodMap.get(mCurMethodId);
1186        if (info == null) {
1187            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1188        }
1189
1190        unbindCurrentMethodLocked(false);
1191
1192        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
1193        mCurIntent.setComponent(info.getComponent());
1194        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
1195                com.android.internal.R.string.input_method_binding_label);
1196        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
1197                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
1198        if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
1199                | Context.BIND_NOT_VISIBLE)) {
1200            mLastBindTime = SystemClock.uptimeMillis();
1201            mHaveConnection = true;
1202            mCurId = info.getId();
1203            mCurToken = new Binder();
1204            try {
1205                if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
1206                mIWindowManager.addWindowToken(mCurToken,
1207                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
1208            } catch (RemoteException e) {
1209            }
1210            return new InputBindResult(null, mCurId, mCurSeq);
1211        } else {
1212            mCurIntent = null;
1213            Slog.w(TAG, "Failure connecting to input method service: "
1214                    + mCurIntent);
1215        }
1216        return null;
1217    }
1218
1219    @Override
1220    public InputBindResult startInput(IInputMethodClient client,
1221            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
1222        if (!calledFromValidUser()) {
1223            return null;
1224        }
1225        synchronized (mMethodMap) {
1226            final long ident = Binder.clearCallingIdentity();
1227            try {
1228                return startInputLocked(client, inputContext, attribute, controlFlags);
1229            } finally {
1230                Binder.restoreCallingIdentity(ident);
1231            }
1232        }
1233    }
1234
1235    @Override
1236    public void finishInput(IInputMethodClient client) {
1237    }
1238
1239    @Override
1240    public void onServiceConnected(ComponentName name, IBinder service) {
1241        synchronized (mMethodMap) {
1242            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
1243                mCurMethod = IInputMethod.Stub.asInterface(service);
1244                if (mCurToken == null) {
1245                    Slog.w(TAG, "Service connected without a token!");
1246                    unbindCurrentMethodLocked(false);
1247                    return;
1248                }
1249                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
1250                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1251                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
1252                if (mCurClient != null) {
1253                    if (DEBUG) Slog.v(TAG, "Creating first session while with client "
1254                            + mCurClient);
1255                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1256                            MSG_CREATE_SESSION, mCurMethod,
1257                            new MethodCallback(mCurMethod, this)));
1258                }
1259            }
1260        }
1261    }
1262
1263    void onSessionCreated(IInputMethod method, IInputMethodSession session) {
1264        synchronized (mMethodMap) {
1265            if (mCurMethod != null && method != null
1266                    && mCurMethod.asBinder() == method.asBinder()) {
1267                if (mCurClient != null) {
1268                    mCurClient.curSession = new SessionState(mCurClient,
1269                            method, session);
1270                    mCurClient.sessionRequested = false;
1271                    InputBindResult res = attachNewInputLocked(true);
1272                    if (res.method != null) {
1273                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
1274                                MSG_BIND_METHOD, mCurClient.client, res));
1275                    }
1276                }
1277            }
1278        }
1279    }
1280
1281    void unbindCurrentMethodLocked(boolean reportToClient) {
1282        if (mVisibleBound) {
1283            mContext.unbindService(mVisibleConnection);
1284            mVisibleBound = false;
1285        }
1286
1287        if (mHaveConnection) {
1288            mContext.unbindService(this);
1289            mHaveConnection = false;
1290        }
1291
1292        if (mCurToken != null) {
1293            try {
1294                if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
1295                if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0) {
1296                    // The current IME is shown. Hence an IME switch (transition) is happening.
1297                    mWindowManagerService.saveLastInputMethodWindowForTransition();
1298                }
1299                mIWindowManager.removeWindowToken(mCurToken);
1300            } catch (RemoteException e) {
1301            }
1302            mCurToken = null;
1303        }
1304
1305        mCurId = null;
1306        clearCurMethodLocked();
1307
1308        if (reportToClient && mCurClient != null) {
1309            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
1310                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
1311        }
1312    }
1313
1314    private void finishSession(SessionState sessionState) {
1315        if (sessionState != null && sessionState.session != null) {
1316            try {
1317                sessionState.session.finishSession();
1318            } catch (RemoteException e) {
1319                Slog.w(TAG, "Session failed to close due to remote exception", e);
1320                setImeWindowVisibilityStatusHiddenLocked();
1321            }
1322        }
1323    }
1324
1325    void clearCurMethodLocked() {
1326        if (mCurMethod != null) {
1327            for (ClientState cs : mClients.values()) {
1328                cs.sessionRequested = false;
1329                finishSession(cs.curSession);
1330                cs.curSession = null;
1331            }
1332
1333            finishSession(mEnabledSession);
1334            mEnabledSession = null;
1335            mCurMethod = null;
1336        }
1337        if (mStatusBar != null) {
1338            mStatusBar.setIconVisibility("ime", false);
1339        }
1340    }
1341
1342    @Override
1343    public void onServiceDisconnected(ComponentName name) {
1344        synchronized (mMethodMap) {
1345            if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
1346                    + " mCurIntent=" + mCurIntent);
1347            if (mCurMethod != null && mCurIntent != null
1348                    && name.equals(mCurIntent.getComponent())) {
1349                clearCurMethodLocked();
1350                // We consider this to be a new bind attempt, since the system
1351                // should now try to restart the service for us.
1352                mLastBindTime = SystemClock.uptimeMillis();
1353                mShowRequested = mInputShown;
1354                mInputShown = false;
1355                if (mCurClient != null) {
1356                    executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
1357                            MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
1358                }
1359            }
1360        }
1361    }
1362
1363    @Override
1364    public void updateStatusIcon(IBinder token, String packageName, int iconId) {
1365        int uid = Binder.getCallingUid();
1366        long ident = Binder.clearCallingIdentity();
1367        try {
1368            if (token == null || mCurToken != token) {
1369                Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
1370                return;
1371            }
1372
1373            synchronized (mMethodMap) {
1374                if (iconId == 0) {
1375                    if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
1376                    if (mStatusBar != null) {
1377                        mStatusBar.setIconVisibility("ime", false);
1378                    }
1379                } else if (packageName != null) {
1380                    if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
1381                    CharSequence contentDescription = null;
1382                    try {
1383                        // Use PackageManager to load label
1384                        final PackageManager packageManager = mContext.getPackageManager();
1385                        contentDescription = packageManager.getApplicationLabel(
1386                                mIPackageManager.getApplicationInfo(packageName, 0,
1387                                        mSettings.getCurrentUserId()));
1388                    } catch (RemoteException e) {
1389                        /* ignore */
1390                    }
1391                    if (mStatusBar != null) {
1392                        mStatusBar.setIcon("ime", packageName, iconId, 0,
1393                                contentDescription  != null
1394                                        ? contentDescription.toString() : null);
1395                        mStatusBar.setIconVisibility("ime", true);
1396                    }
1397                }
1398            }
1399        } finally {
1400            Binder.restoreCallingIdentity(ident);
1401        }
1402    }
1403
1404    private boolean needsToShowImeSwitchOngoingNotification() {
1405        if (!mShowOngoingImeSwitcherForPhones) return false;
1406        if (isScreenLocked()) return false;
1407        synchronized (mMethodMap) {
1408            List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
1409            final int N = imis.size();
1410            if (N > 2) return true;
1411            if (N < 1) return false;
1412            int nonAuxCount = 0;
1413            int auxCount = 0;
1414            InputMethodSubtype nonAuxSubtype = null;
1415            InputMethodSubtype auxSubtype = null;
1416            for(int i = 0; i < N; ++i) {
1417                final InputMethodInfo imi = imis.get(i);
1418                final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeListLocked(
1419                        imi, true);
1420                final int subtypeCount = subtypes.size();
1421                if (subtypeCount == 0) {
1422                    ++nonAuxCount;
1423                } else {
1424                    for (int j = 0; j < subtypeCount; ++j) {
1425                        final InputMethodSubtype subtype = subtypes.get(j);
1426                        if (!subtype.isAuxiliary()) {
1427                            ++nonAuxCount;
1428                            nonAuxSubtype = subtype;
1429                        } else {
1430                            ++auxCount;
1431                            auxSubtype = subtype;
1432                        }
1433                    }
1434                }
1435            }
1436            if (nonAuxCount > 1 || auxCount > 1) {
1437                return true;
1438            } else if (nonAuxCount == 1 && auxCount == 1) {
1439                if (nonAuxSubtype != null && auxSubtype != null
1440                        && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
1441                                || auxSubtype.overridesImplicitlyEnabledSubtype()
1442                                || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
1443                        && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
1444                    return false;
1445                }
1446                return true;
1447            }
1448            return false;
1449        }
1450    }
1451
1452    // Caution! This method is called in this class. Handle multi-user carefully
1453    @SuppressWarnings("deprecation")
1454    @Override
1455    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
1456        final long ident = Binder.clearCallingIdentity();
1457        try {
1458            if (token == null || mCurToken != token) {
1459                int uid = Binder.getCallingUid();
1460                Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
1461                return;
1462            }
1463
1464            synchronized (mMethodMap) {
1465                mImeWindowVis = vis;
1466                mBackDisposition = backDisposition;
1467                if (mStatusBar != null) {
1468                    mStatusBar.setImeWindowStatus(token, vis, backDisposition);
1469                }
1470                final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0;
1471                final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
1472                if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
1473                    // Used to load label
1474                    final PackageManager pm = mContext.getPackageManager();
1475                    final CharSequence title = mRes.getText(
1476                            com.android.internal.R.string.select_input_method);
1477                    final CharSequence imiLabel = imi.loadLabel(pm);
1478                    if (DEBUG) {
1479                        Slog.d(TAG, "--- imiLabel = " + imiLabel);
1480                    }
1481                    final CharSequence summary = mCurrentSubtype != null
1482                            ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext,
1483                                        imi.getPackageName(), imi.getServiceInfo().applicationInfo),
1484                                                (TextUtils.isEmpty(imiLabel) ?
1485                                                        "" : " - " + imiLabel))
1486                            : imiLabel;
1487
1488                    mImeSwitcherNotification.setLatestEventInfo(
1489                            mContext, title, summary, mImeSwitchPendingIntent);
1490                    if (mNotificationManager != null) {
1491                        mNotificationManager.notify(
1492                                com.android.internal.R.string.select_input_method,
1493                                mImeSwitcherNotification);
1494                        mNotificationShown = true;
1495                    }
1496                } else {
1497                    if (mNotificationShown && mNotificationManager != null) {
1498                        mNotificationManager.cancel(
1499                                com.android.internal.R.string.select_input_method);
1500                        mNotificationShown = false;
1501                    }
1502                }
1503            }
1504        } finally {
1505            Binder.restoreCallingIdentity(ident);
1506        }
1507    }
1508
1509    @Override
1510    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
1511        if (!calledFromValidUser()) {
1512            return;
1513        }
1514        synchronized (mMethodMap) {
1515            final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
1516            for (int i = 0; i < spans.length; ++i) {
1517                SuggestionSpan ss = spans[i];
1518                if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
1519                    mSecureSuggestionSpans.put(ss, currentImi);
1520                    final InputMethodInfo targetImi = mSecureSuggestionSpans.get(ss);
1521                }
1522            }
1523        }
1524    }
1525
1526    @Override
1527    public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
1528        if (!calledFromValidUser()) {
1529            return false;
1530        }
1531        synchronized (mMethodMap) {
1532            final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
1533            // TODO: Do not send the intent if the process of the targetImi is already dead.
1534            if (targetImi != null) {
1535                final String[] suggestions = span.getSuggestions();
1536                if (index < 0 || index >= suggestions.length) return false;
1537                final String className = span.getNotificationTargetClassName();
1538                final Intent intent = new Intent();
1539                // Ensures that only a class in the original IME package will receive the
1540                // notification.
1541                intent.setClassName(targetImi.getPackageName(), className);
1542                intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
1543                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
1544                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
1545                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
1546                mContext.sendBroadcast(intent);
1547                return true;
1548            }
1549        }
1550        return false;
1551    }
1552
1553    void updateFromSettingsLocked() {
1554        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
1555        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
1556        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
1557        // enabled.
1558        String id = mSettings.getSelectedInputMethod();
1559        // There is no input method selected, try to choose new applicable input method.
1560        if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
1561            id = mSettings.getSelectedInputMethod();
1562        }
1563        if (!TextUtils.isEmpty(id)) {
1564            try {
1565                setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
1566            } catch (IllegalArgumentException e) {
1567                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
1568                mCurMethodId = null;
1569                unbindCurrentMethodLocked(true);
1570            }
1571            mShortcutInputMethodsAndSubtypes.clear();
1572        } else {
1573            // There is no longer an input method set, so stop any current one.
1574            mCurMethodId = null;
1575            unbindCurrentMethodLocked(true);
1576        }
1577    }
1578
1579    /* package */ void setInputMethodLocked(String id, int subtypeId) {
1580        InputMethodInfo info = mMethodMap.get(id);
1581        if (info == null) {
1582            throw new IllegalArgumentException("Unknown id: " + id);
1583        }
1584
1585        // See if we need to notify a subtype change within the same IME.
1586        if (id.equals(mCurMethodId)) {
1587            final int subtypeCount = info.getSubtypeCount();
1588            if (subtypeCount <= 0) {
1589                return;
1590            }
1591            final InputMethodSubtype oldSubtype = mCurrentSubtype;
1592            final InputMethodSubtype newSubtype;
1593            if (subtypeId >= 0 && subtypeId < subtypeCount) {
1594                newSubtype = info.getSubtypeAt(subtypeId);
1595            } else {
1596                // If subtype is null, try to find the most applicable one from
1597                // getCurrentInputMethodSubtype.
1598                newSubtype = getCurrentInputMethodSubtypeLocked();
1599            }
1600            if (newSubtype == null || oldSubtype == null) {
1601                Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
1602                        + ", new subtype = " + newSubtype);
1603                return;
1604            }
1605            if (newSubtype != oldSubtype) {
1606                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
1607                if (mCurMethod != null) {
1608                    try {
1609                        refreshImeWindowVisibilityLocked();
1610                        mCurMethod.changeInputMethodSubtype(newSubtype);
1611                    } catch (RemoteException e) {
1612                        Slog.w(TAG, "Failed to call changeInputMethodSubtype");
1613                    }
1614                }
1615            }
1616            return;
1617        }
1618
1619        // Changing to a different IME.
1620        final long ident = Binder.clearCallingIdentity();
1621        try {
1622            // Set a subtype to this input method.
1623            // subtypeId the name of a subtype which will be set.
1624            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
1625            // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
1626            // because mCurMethodId is stored as a history in
1627            // setSelectedInputMethodAndSubtypeLocked().
1628            mCurMethodId = id;
1629
1630            if (ActivityManagerNative.isSystemReady()) {
1631                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
1632                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1633                intent.putExtra("input_method_id", id);
1634                mContext.sendBroadcast(intent);
1635            }
1636            unbindCurrentClientLocked();
1637        } finally {
1638            Binder.restoreCallingIdentity(ident);
1639        }
1640    }
1641
1642    @Override
1643    public boolean showSoftInput(IInputMethodClient client, int flags,
1644            ResultReceiver resultReceiver) {
1645        if (!calledFromValidUser()) {
1646            return false;
1647        }
1648        int uid = Binder.getCallingUid();
1649        long ident = Binder.clearCallingIdentity();
1650        try {
1651            synchronized (mMethodMap) {
1652                if (mCurClient == null || client == null
1653                        || mCurClient.client.asBinder() != client.asBinder()) {
1654                    try {
1655                        // We need to check if this is the current client with
1656                        // focus in the window manager, to allow this call to
1657                        // be made before input is started in it.
1658                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1659                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
1660                            return false;
1661                        }
1662                    } catch (RemoteException e) {
1663                        return false;
1664                    }
1665                }
1666
1667                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
1668                return showCurrentInputLocked(flags, resultReceiver);
1669            }
1670        } finally {
1671            Binder.restoreCallingIdentity(ident);
1672        }
1673    }
1674
1675    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1676        mShowRequested = true;
1677        if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1678            mShowExplicitlyRequested = true;
1679        }
1680        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1681            mShowExplicitlyRequested = true;
1682            mShowForced = true;
1683        }
1684
1685        if (!mSystemReady) {
1686            return false;
1687        }
1688
1689        boolean res = false;
1690        if (mCurMethod != null) {
1691            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1692                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1693                    resultReceiver));
1694            mInputShown = true;
1695            if (mHaveConnection && !mVisibleBound) {
1696                bindCurrentInputMethodService(
1697                        mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
1698                mVisibleBound = true;
1699            }
1700            res = true;
1701        } else if (mHaveConnection && SystemClock.uptimeMillis()
1702                >= (mLastBindTime+TIME_TO_RECONNECT)) {
1703            // The client has asked to have the input method shown, but
1704            // we have been sitting here too long with a connection to the
1705            // service and no interface received, so let's disconnect/connect
1706            // to try to prod things along.
1707            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
1708                    SystemClock.uptimeMillis()-mLastBindTime,1);
1709            Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
1710            mContext.unbindService(this);
1711            bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
1712                    | Context.BIND_NOT_VISIBLE);
1713        } else {
1714            if (DEBUG) {
1715                Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
1716                        + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
1717            }
1718        }
1719
1720        return res;
1721    }
1722
1723    @Override
1724    public boolean hideSoftInput(IInputMethodClient client, int flags,
1725            ResultReceiver resultReceiver) {
1726        if (!calledFromValidUser()) {
1727            return false;
1728        }
1729        int uid = Binder.getCallingUid();
1730        long ident = Binder.clearCallingIdentity();
1731        try {
1732            synchronized (mMethodMap) {
1733                if (mCurClient == null || client == null
1734                        || mCurClient.client.asBinder() != client.asBinder()) {
1735                    try {
1736                        // We need to check if this is the current client with
1737                        // focus in the window manager, to allow this call to
1738                        // be made before input is started in it.
1739                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1740                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
1741                                    + uid + ": " + client);
1742                            setImeWindowVisibilityStatusHiddenLocked();
1743                            return false;
1744                        }
1745                    } catch (RemoteException e) {
1746                        setImeWindowVisibilityStatusHiddenLocked();
1747                        return false;
1748                    }
1749                }
1750
1751                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
1752                return hideCurrentInputLocked(flags, resultReceiver);
1753            }
1754        } finally {
1755            Binder.restoreCallingIdentity(ident);
1756        }
1757    }
1758
1759    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1760        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1761                && (mShowExplicitlyRequested || mShowForced)) {
1762            if (DEBUG) Slog.v(TAG,
1763                    "Not hiding: explicit show not cancelled by non-explicit hide");
1764            return false;
1765        }
1766        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1767            if (DEBUG) Slog.v(TAG,
1768                    "Not hiding: forced show not cancelled by not-always hide");
1769            return false;
1770        }
1771        boolean res;
1772        if (mInputShown && mCurMethod != null) {
1773            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1774                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1775            res = true;
1776        } else {
1777            res = false;
1778        }
1779        if (mHaveConnection && mVisibleBound) {
1780            mContext.unbindService(mVisibleConnection);
1781            mVisibleBound = false;
1782        }
1783        mInputShown = false;
1784        mShowRequested = false;
1785        mShowExplicitlyRequested = false;
1786        mShowForced = false;
1787        return res;
1788    }
1789
1790    @Override
1791    public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
1792            int controlFlags, int softInputMode, int windowFlags,
1793            EditorInfo attribute, IInputContext inputContext) {
1794        if (!calledFromValidUser()) {
1795            return null;
1796        }
1797        InputBindResult res = null;
1798        long ident = Binder.clearCallingIdentity();
1799        try {
1800            synchronized (mMethodMap) {
1801                if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
1802                        + " controlFlags=#" + Integer.toHexString(controlFlags)
1803                        + " softInputMode=#" + Integer.toHexString(softInputMode)
1804                        + " windowFlags=#" + Integer.toHexString(windowFlags));
1805
1806                ClientState cs = mClients.get(client.asBinder());
1807                if (cs == null) {
1808                    throw new IllegalArgumentException("unknown client "
1809                            + client.asBinder());
1810                }
1811
1812                try {
1813                    if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
1814                        // Check with the window manager to make sure this client actually
1815                        // has a window with focus.  If not, reject.  This is thread safe
1816                        // because if the focus changes some time before or after, the
1817                        // next client receiving focus that has any interest in input will
1818                        // be calling through here after that change happens.
1819                        Slog.w(TAG, "Focus gain on non-focused client " + cs.client
1820                                + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
1821                        return null;
1822                    }
1823                } catch (RemoteException e) {
1824                }
1825
1826                if (mCurFocusedWindow == windowToken) {
1827                    Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
1828                            + " attribute=" + attribute);
1829                    if (attribute != null) {
1830                        return startInputUncheckedLocked(cs, inputContext, attribute,
1831                                controlFlags);
1832                    }
1833                    return null;
1834                }
1835                mCurFocusedWindow = windowToken;
1836
1837                // Should we auto-show the IME even if the caller has not
1838                // specified what should be done with it?
1839                // We only do this automatically if the window can resize
1840                // to accommodate the IME (so what the user sees will give
1841                // them good context without input information being obscured
1842                // by the IME) or if running on a large screen where there
1843                // is more room for the target window + IME.
1844                final boolean doAutoShow =
1845                        (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1846                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1847                        || mRes.getConfiguration().isLayoutSizeAtLeast(
1848                                Configuration.SCREENLAYOUT_SIZE_LARGE);
1849                final boolean isTextEditor =
1850                        (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
1851
1852                // We want to start input before showing the IME, but after closing
1853                // it.  We want to do this after closing it to help the IME disappear
1854                // more quickly (not get stuck behind it initializing itself for the
1855                // new focused input, even if its window wants to hide the IME).
1856                boolean didStart = false;
1857
1858                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1859                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
1860                        if (!isTextEditor || !doAutoShow) {
1861                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1862                                // There is no focus view, and this window will
1863                                // be behind any soft input window, so hide the
1864                                // soft input window if it is shown.
1865                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
1866                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
1867                            }
1868                        } else if (isTextEditor && doAutoShow && (softInputMode &
1869                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1870                            // There is a focus view, and we are navigating forward
1871                            // into the window, so show the input window for the user.
1872                            // We only do this automatically if the window can resize
1873                            // to accommodate the IME (so what the user sees will give
1874                            // them good context without input information being obscured
1875                            // by the IME) or if running on a large screen where there
1876                            // is more room for the target window + IME.
1877                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
1878                            if (attribute != null) {
1879                                res = startInputUncheckedLocked(cs, inputContext, attribute,
1880                                        controlFlags);
1881                                didStart = true;
1882                            }
1883                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1884                        }
1885                        break;
1886                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1887                        // Do nothing.
1888                        break;
1889                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1890                        if ((softInputMode &
1891                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1892                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
1893                            hideCurrentInputLocked(0, null);
1894                        }
1895                        break;
1896                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
1897                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
1898                        hideCurrentInputLocked(0, null);
1899                        break;
1900                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1901                        if ((softInputMode &
1902                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1903                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
1904                            if (attribute != null) {
1905                                res = startInputUncheckedLocked(cs, inputContext, attribute,
1906                                        controlFlags);
1907                                didStart = true;
1908                            }
1909                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1910                        }
1911                        break;
1912                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
1913                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
1914                        if (attribute != null) {
1915                            res = startInputUncheckedLocked(cs, inputContext, attribute,
1916                                    controlFlags);
1917                            didStart = true;
1918                        }
1919                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1920                        break;
1921                }
1922
1923                if (!didStart && attribute != null) {
1924                    res = startInputUncheckedLocked(cs, inputContext, attribute,
1925                            controlFlags);
1926                }
1927            }
1928        } finally {
1929            Binder.restoreCallingIdentity(ident);
1930        }
1931
1932        return res;
1933    }
1934
1935    @Override
1936    public void showInputMethodPickerFromClient(IInputMethodClient client) {
1937        if (!calledFromValidUser()) {
1938            return;
1939        }
1940        synchronized (mMethodMap) {
1941            if (mCurClient == null || client == null
1942                    || mCurClient.client.asBinder() != client.asBinder()) {
1943                Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
1944                        + Binder.getCallingUid() + ": " + client);
1945            }
1946
1947            // Always call subtype picker, because subtype picker is a superset of input method
1948            // picker.
1949            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
1950        }
1951    }
1952
1953    @Override
1954    public void setInputMethod(IBinder token, String id) {
1955        if (!calledFromValidUser()) {
1956            return;
1957        }
1958        setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
1959    }
1960
1961    @Override
1962    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
1963        if (!calledFromValidUser()) {
1964            return;
1965        }
1966        synchronized (mMethodMap) {
1967            if (subtype != null) {
1968                setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
1969                        mMethodMap.get(id), subtype.hashCode()));
1970            } else {
1971                setInputMethod(token, id);
1972            }
1973        }
1974    }
1975
1976    @Override
1977    public void showInputMethodAndSubtypeEnablerFromClient(
1978            IInputMethodClient client, String inputMethodId) {
1979        if (!calledFromValidUser()) {
1980            return;
1981        }
1982        synchronized (mMethodMap) {
1983            if (mCurClient == null || client == null
1984                || mCurClient.client.asBinder() != client.asBinder()) {
1985                Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
1986            }
1987            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1988                    MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
1989        }
1990    }
1991
1992    @Override
1993    public boolean switchToLastInputMethod(IBinder token) {
1994        if (!calledFromValidUser()) {
1995            return false;
1996        }
1997        synchronized (mMethodMap) {
1998            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1999            final InputMethodInfo lastImi;
2000            if (lastIme != null) {
2001                lastImi = mMethodMap.get(lastIme.first);
2002            } else {
2003                lastImi = null;
2004            }
2005            String targetLastImiId = null;
2006            int subtypeId = NOT_A_SUBTYPE_ID;
2007            if (lastIme != null && lastImi != null) {
2008                final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
2009                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
2010                final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
2011                        : mCurrentSubtype.hashCode();
2012                // If the last IME is the same as the current IME and the last subtype is not
2013                // defined, there is no need to switch to the last IME.
2014                if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
2015                    targetLastImiId = lastIme.first;
2016                    subtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2017                }
2018            }
2019
2020            if (TextUtils.isEmpty(targetLastImiId) && !canAddToLastInputMethod(mCurrentSubtype)) {
2021                // This is a safety net. If the currentSubtype can't be added to the history
2022                // and the framework couldn't find the last ime, we will make the last ime be
2023                // the most applicable enabled keyboard subtype of the system imes.
2024                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2025                if (enabled != null) {
2026                    final int N = enabled.size();
2027                    final String locale = mCurrentSubtype == null
2028                            ? mRes.getConfiguration().locale.toString()
2029                            : mCurrentSubtype.getLocale();
2030                    for (int i = 0; i < N; ++i) {
2031                        final InputMethodInfo imi = enabled.get(i);
2032                        if (imi.getSubtypeCount() > 0 && isSystemIme(imi)) {
2033                            InputMethodSubtype keyboardSubtype =
2034                                    findLastResortApplicableSubtypeLocked(mRes, getSubtypes(imi),
2035                                            SUBTYPE_MODE_KEYBOARD, locale, true);
2036                            if (keyboardSubtype != null) {
2037                                targetLastImiId = imi.getId();
2038                                subtypeId = getSubtypeIdFromHashCode(
2039                                        imi, keyboardSubtype.hashCode());
2040                                if(keyboardSubtype.getLocale().equals(locale)) {
2041                                    break;
2042                                }
2043                            }
2044                        }
2045                    }
2046                }
2047            }
2048
2049            if (!TextUtils.isEmpty(targetLastImiId)) {
2050                if (DEBUG) {
2051                    Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
2052                            + ", from: " + mCurMethodId + ", " + subtypeId);
2053                }
2054                setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId);
2055                return true;
2056            } else {
2057                return false;
2058            }
2059        }
2060    }
2061
2062    @Override
2063    public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
2064        if (!calledFromValidUser()) {
2065            return false;
2066        }
2067        synchronized (mMethodMap) {
2068            final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
2069                    onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
2070            if (nextSubtype == null) {
2071                return false;
2072            }
2073            setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
2074            return true;
2075        }
2076    }
2077
2078    @Override
2079    public InputMethodSubtype getLastInputMethodSubtype() {
2080        if (!calledFromValidUser()) {
2081            return null;
2082        }
2083        synchronized (mMethodMap) {
2084            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2085            // TODO: Handle the case of the last IME with no subtypes
2086            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
2087                    || TextUtils.isEmpty(lastIme.second)) return null;
2088            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
2089            if (lastImi == null) return null;
2090            try {
2091                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
2092                final int lastSubtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2093                if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
2094                    return null;
2095                }
2096                return lastImi.getSubtypeAt(lastSubtypeId);
2097            } catch (NumberFormatException e) {
2098                return null;
2099            }
2100        }
2101    }
2102
2103    @Override
2104    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
2105        if (!calledFromValidUser()) {
2106            return;
2107        }
2108        // By this IPC call, only a process which shares the same uid with the IME can add
2109        // additional input method subtypes to the IME.
2110        if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
2111        synchronized (mMethodMap) {
2112            final InputMethodInfo imi = mMethodMap.get(imiId);
2113            if (imi == null) return;
2114            final String[] packageInfos;
2115            try {
2116                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
2117            } catch (RemoteException e) {
2118                Slog.e(TAG, "Failed to get package infos");
2119                return;
2120            }
2121            if (packageInfos != null) {
2122                final int packageNum = packageInfos.length;
2123                for (int i = 0; i < packageNum; ++i) {
2124                    if (packageInfos[i].equals(imi.getPackageName())) {
2125                        mFileManager.addInputMethodSubtypes(imi, subtypes);
2126                        final long ident = Binder.clearCallingIdentity();
2127                        try {
2128                            buildInputMethodListLocked(mMethodList, mMethodMap);
2129                        } finally {
2130                            Binder.restoreCallingIdentity(ident);
2131                        }
2132                        return;
2133                    }
2134                }
2135            }
2136        }
2137        return;
2138    }
2139
2140    private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
2141        synchronized (mMethodMap) {
2142            if (token == null) {
2143                if (mContext.checkCallingOrSelfPermission(
2144                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
2145                        != PackageManager.PERMISSION_GRANTED) {
2146                    throw new SecurityException(
2147                            "Using null token requires permission "
2148                            + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2149                }
2150            } else if (mCurToken != token) {
2151                Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
2152                        + " token: " + token);
2153                return;
2154            }
2155
2156            final long ident = Binder.clearCallingIdentity();
2157            try {
2158                setInputMethodLocked(id, subtypeId);
2159            } finally {
2160                Binder.restoreCallingIdentity(ident);
2161            }
2162        }
2163    }
2164
2165    @Override
2166    public void hideMySoftInput(IBinder token, int flags) {
2167        if (!calledFromValidUser()) {
2168            return;
2169        }
2170        synchronized (mMethodMap) {
2171            if (token == null || mCurToken != token) {
2172                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
2173                        + Binder.getCallingUid() + " token: " + token);
2174                return;
2175            }
2176            long ident = Binder.clearCallingIdentity();
2177            try {
2178                hideCurrentInputLocked(flags, null);
2179            } finally {
2180                Binder.restoreCallingIdentity(ident);
2181            }
2182        }
2183    }
2184
2185    @Override
2186    public void showMySoftInput(IBinder token, int flags) {
2187        if (!calledFromValidUser()) {
2188            return;
2189        }
2190        synchronized (mMethodMap) {
2191            if (token == null || mCurToken != token) {
2192                Slog.w(TAG, "Ignoring showMySoftInput of uid "
2193                        + Binder.getCallingUid() + " token: " + token);
2194                return;
2195            }
2196            long ident = Binder.clearCallingIdentity();
2197            try {
2198                showCurrentInputLocked(flags, null);
2199            } finally {
2200                Binder.restoreCallingIdentity(ident);
2201            }
2202        }
2203    }
2204
2205    void setEnabledSessionInMainThread(SessionState session) {
2206        if (mEnabledSession != session) {
2207            if (mEnabledSession != null) {
2208                try {
2209                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
2210                    mEnabledSession.method.setSessionEnabled(
2211                            mEnabledSession.session, false);
2212                } catch (RemoteException e) {
2213                }
2214            }
2215            mEnabledSession = session;
2216            try {
2217                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
2218                session.method.setSessionEnabled(
2219                        session.session, true);
2220            } catch (RemoteException e) {
2221            }
2222        }
2223    }
2224
2225    @Override
2226    public boolean handleMessage(Message msg) {
2227        SomeArgs args;
2228        switch (msg.what) {
2229            case MSG_SHOW_IM_PICKER:
2230                showInputMethodMenu();
2231                return true;
2232
2233            case MSG_SHOW_IM_SUBTYPE_PICKER:
2234                showInputMethodSubtypeMenu();
2235                return true;
2236
2237            case MSG_SHOW_IM_SUBTYPE_ENABLER:
2238                args = (SomeArgs)msg.obj;
2239                showInputMethodAndSubtypeEnabler((String)args.arg1);
2240                args.recycle();
2241                return true;
2242
2243            case MSG_SHOW_IM_CONFIG:
2244                showConfigureInputMethods();
2245                return true;
2246
2247            // ---------------------------------------------------------
2248
2249            case MSG_UNBIND_INPUT:
2250                try {
2251                    ((IInputMethod)msg.obj).unbindInput();
2252                } catch (RemoteException e) {
2253                    // There is nothing interesting about the method dying.
2254                }
2255                return true;
2256            case MSG_BIND_INPUT:
2257                args = (SomeArgs)msg.obj;
2258                try {
2259                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
2260                } catch (RemoteException e) {
2261                }
2262                args.recycle();
2263                return true;
2264            case MSG_SHOW_SOFT_INPUT:
2265                args = (SomeArgs)msg.obj;
2266                try {
2267                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
2268                            (ResultReceiver)args.arg2);
2269                } catch (RemoteException e) {
2270                }
2271                args.recycle();
2272                return true;
2273            case MSG_HIDE_SOFT_INPUT:
2274                args = (SomeArgs)msg.obj;
2275                try {
2276                    ((IInputMethod)args.arg1).hideSoftInput(0,
2277                            (ResultReceiver)args.arg2);
2278                } catch (RemoteException e) {
2279                }
2280                args.recycle();
2281                return true;
2282            case MSG_ATTACH_TOKEN:
2283                args = (SomeArgs)msg.obj;
2284                try {
2285                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
2286                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
2287                } catch (RemoteException e) {
2288                }
2289                args.recycle();
2290                return true;
2291            case MSG_CREATE_SESSION:
2292                args = (SomeArgs)msg.obj;
2293                try {
2294                    ((IInputMethod)args.arg1).createSession(
2295                            (IInputMethodCallback)args.arg2);
2296                } catch (RemoteException e) {
2297                }
2298                args.recycle();
2299                return true;
2300            // ---------------------------------------------------------
2301
2302            case MSG_START_INPUT:
2303                args = (SomeArgs)msg.obj;
2304                try {
2305                    SessionState session = (SessionState)args.arg1;
2306                    setEnabledSessionInMainThread(session);
2307                    session.method.startInput((IInputContext)args.arg2,
2308                            (EditorInfo)args.arg3);
2309                } catch (RemoteException e) {
2310                }
2311                args.recycle();
2312                return true;
2313            case MSG_RESTART_INPUT:
2314                args = (SomeArgs)msg.obj;
2315                try {
2316                    SessionState session = (SessionState)args.arg1;
2317                    setEnabledSessionInMainThread(session);
2318                    session.method.restartInput((IInputContext)args.arg2,
2319                            (EditorInfo)args.arg3);
2320                } catch (RemoteException e) {
2321                }
2322                args.recycle();
2323                return true;
2324
2325            // ---------------------------------------------------------
2326
2327            case MSG_UNBIND_METHOD:
2328                try {
2329                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
2330                } catch (RemoteException e) {
2331                    // There is nothing interesting about the last client dying.
2332                }
2333                return true;
2334            case MSG_BIND_METHOD:
2335                args = (SomeArgs)msg.obj;
2336                try {
2337                    ((IInputMethodClient)args.arg1).onBindMethod(
2338                            (InputBindResult)args.arg2);
2339                } catch (RemoteException e) {
2340                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
2341                }
2342                args.recycle();
2343                return true;
2344            case MSG_SET_ACTIVE:
2345                try {
2346                    ((ClientState)msg.obj).client.setActive(msg.arg1 != 0);
2347                } catch (RemoteException e) {
2348                    Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
2349                            + ((ClientState)msg.obj).pid + " uid "
2350                            + ((ClientState)msg.obj).uid);
2351                }
2352                return true;
2353
2354            // --------------------------------------------------------------
2355            case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
2356                mHardKeyboardListener.handleHardKeyboardStatusChange(
2357                        msg.arg1 == 1, msg.arg2 == 1);
2358                return true;
2359        }
2360        return false;
2361    }
2362
2363    private static boolean isSystemIme(InputMethodInfo inputMethod) {
2364        return (inputMethod.getServiceInfo().applicationInfo.flags
2365                & ApplicationInfo.FLAG_SYSTEM) != 0;
2366    }
2367
2368    private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
2369        ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2370        final int subtypeCount = imi.getSubtypeCount();
2371        for (int i = 0; i < subtypeCount; ++i) {
2372            subtypes.add(imi.getSubtypeAt(i));
2373        }
2374        return subtypes;
2375    }
2376
2377    private static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
2378            InputMethodInfo imi, String mode) {
2379        ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2380        final int subtypeCount = imi.getSubtypeCount();
2381        for (int i = 0; i < subtypeCount; ++i) {
2382            final InputMethodSubtype subtype = imi.getSubtypeAt(i);
2383            if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
2384                subtypes.add(subtype);
2385            }
2386        }
2387        return subtypes;
2388    }
2389
2390    private InputMethodInfo getMostApplicableDefaultIMELocked() {
2391        List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2392        if (enabled != null && enabled.size() > 0) {
2393            // We'd prefer to fall back on a system IME, since that is safer.
2394            int i = enabled.size();
2395            int firstFoundSystemIme = -1;
2396            while (i > 0) {
2397                i--;
2398                final InputMethodInfo imi = enabled.get(i);
2399                if (isSystemImeThatHasEnglishSubtype(imi) && !imi.isAuxiliaryIme()) {
2400                    return imi;
2401                }
2402                if (firstFoundSystemIme < 0 && isSystemIme(imi) && !imi.isAuxiliaryIme()) {
2403                    firstFoundSystemIme = i;
2404                }
2405            }
2406            return enabled.get(Math.max(firstFoundSystemIme, 0));
2407        }
2408        return null;
2409    }
2410
2411    private boolean chooseNewDefaultIMELocked() {
2412        final InputMethodInfo imi = getMostApplicableDefaultIMELocked();
2413        if (imi != null) {
2414            if (DEBUG) {
2415                Slog.d(TAG, "New default IME was selected: " + imi.getId());
2416            }
2417            resetSelectedInputMethodAndSubtypeLocked(imi.getId());
2418            return true;
2419        }
2420
2421        return false;
2422    }
2423
2424    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
2425            HashMap<String, InputMethodInfo> map) {
2426        if (DEBUG) {
2427            Slog.d(TAG, "--- re-buildInputMethodList " + ", \n ------ \n" + getStackTrace());
2428        }
2429        list.clear();
2430        map.clear();
2431
2432        // Use for queryIntentServicesAsUser
2433        final PackageManager pm = mContext.getPackageManager();
2434        final Configuration config = mRes.getConfiguration();
2435        final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
2436        String disabledSysImes = mSettings.getDisabledSystemInputMethods();
2437        if (disabledSysImes == null) disabledSysImes = "";
2438
2439        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
2440                new Intent(InputMethod.SERVICE_INTERFACE),
2441                PackageManager.GET_META_DATA, mSettings.getCurrentUserId());
2442
2443        final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
2444                mFileManager.getAllAdditionalInputMethodSubtypes();
2445        for (int i = 0; i < services.size(); ++i) {
2446            ResolveInfo ri = services.get(i);
2447            ServiceInfo si = ri.serviceInfo;
2448            ComponentName compName = new ComponentName(si.packageName, si.name);
2449            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
2450                    si.permission)) {
2451                Slog.w(TAG, "Skipping input method " + compName
2452                        + ": it does not require the permission "
2453                        + android.Manifest.permission.BIND_INPUT_METHOD);
2454                continue;
2455            }
2456
2457            if (DEBUG) Slog.d(TAG, "Checking " + compName);
2458
2459            try {
2460                InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
2461                list.add(p);
2462                final String id = p.getId();
2463                map.put(id, p);
2464
2465                // Valid system default IMEs and IMEs that have English subtypes are enabled
2466                // by default, unless there's a hard keyboard and the system IME was explicitly
2467                // disabled
2468                if ((isValidSystemDefaultIme(p, mContext) || isSystemImeThatHasEnglishSubtype(p))
2469                        && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
2470                    setInputMethodEnabledLocked(id, true);
2471                }
2472
2473                if (DEBUG) {
2474                    Slog.d(TAG, "Found a third-party input method " + p);
2475                }
2476
2477            } catch (XmlPullParserException e) {
2478                Slog.w(TAG, "Unable to load input method " + compName, e);
2479            } catch (IOException e) {
2480                Slog.w(TAG, "Unable to load input method " + compName, e);
2481            }
2482        }
2483
2484        final String defaultImiId = mSettings.getSelectedInputMethod();
2485        if (!TextUtils.isEmpty(defaultImiId)) {
2486            if (!map.containsKey(defaultImiId)) {
2487                Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
2488                if (chooseNewDefaultIMELocked()) {
2489                    updateFromSettingsLocked();
2490                }
2491            } else {
2492                // Double check that the default IME is certainly enabled.
2493                setInputMethodEnabledLocked(defaultImiId, true);
2494            }
2495        }
2496    }
2497
2498    // ----------------------------------------------------------------------
2499
2500    private void showInputMethodMenu() {
2501        showInputMethodMenuInternal(false);
2502    }
2503
2504    private void showInputMethodSubtypeMenu() {
2505        showInputMethodMenuInternal(true);
2506    }
2507
2508    private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
2509        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
2510        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2511                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2512                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2513        if (!TextUtils.isEmpty(inputMethodId)) {
2514            intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
2515        }
2516        mContext.startActivity(intent);
2517    }
2518
2519    private void showConfigureInputMethods() {
2520        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
2521        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2522                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2523                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2524        mContext.startActivity(intent);
2525    }
2526
2527    private boolean isScreenLocked() {
2528        return mKeyguardManager != null
2529                && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
2530    }
2531    private void showInputMethodMenuInternal(boolean showSubtypes) {
2532        if (DEBUG) Slog.v(TAG, "Show switching menu");
2533
2534        final Context context = mContext;
2535        final boolean isScreenLocked = isScreenLocked();
2536
2537        final String lastInputMethodId = mSettings.getSelectedInputMethod();
2538        int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2539        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
2540
2541        synchronized (mMethodMap) {
2542            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
2543                    getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
2544            if (immis == null || immis.size() == 0) {
2545                return;
2546            }
2547
2548            hideInputMethodMenuLocked();
2549
2550            final List<ImeSubtypeListItem> imList =
2551                    mImListManager.getSortedInputMethodAndSubtypeList(
2552                            showSubtypes, mInputShown, isScreenLocked);
2553
2554            if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
2555                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
2556                if (currentSubtype != null) {
2557                    final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
2558                    lastInputMethodSubtypeId =
2559                            getSubtypeIdFromHashCode(currentImi, currentSubtype.hashCode());
2560                }
2561            }
2562
2563            final int N = imList.size();
2564            mIms = new InputMethodInfo[N];
2565            mSubtypeIds = new int[N];
2566            int checkedItem = 0;
2567            for (int i = 0; i < N; ++i) {
2568                final ImeSubtypeListItem item = imList.get(i);
2569                mIms[i] = item.mImi;
2570                mSubtypeIds[i] = item.mSubtypeId;
2571                if (mIms[i].getId().equals(lastInputMethodId)) {
2572                    int subtypeId = mSubtypeIds[i];
2573                    if ((subtypeId == NOT_A_SUBTYPE_ID)
2574                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
2575                            || (subtypeId == lastInputMethodSubtypeId)) {
2576                        checkedItem = i;
2577                    }
2578                }
2579            }
2580            final TypedArray a = context.obtainStyledAttributes(null,
2581                    com.android.internal.R.styleable.DialogPreference,
2582                    com.android.internal.R.attr.alertDialogStyle, 0);
2583            mDialogBuilder = new AlertDialog.Builder(context)
2584                    .setOnCancelListener(new OnCancelListener() {
2585                        @Override
2586                        public void onCancel(DialogInterface dialog) {
2587                            hideInputMethodMenu();
2588                        }
2589                    })
2590                    .setIcon(a.getDrawable(
2591                            com.android.internal.R.styleable.DialogPreference_dialogTitle));
2592            a.recycle();
2593            final LayoutInflater inflater =
2594                    (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2595            final View tv = inflater.inflate(
2596                    com.android.internal.R.layout.input_method_switch_dialog_title, null);
2597            mDialogBuilder.setCustomTitle(tv);
2598
2599            // Setup layout for a toggle switch of the hardware keyboard
2600            mSwitchingDialogTitleView = tv;
2601            mSwitchingDialogTitleView.findViewById(
2602                    com.android.internal.R.id.hard_keyboard_section).setVisibility(
2603                            mWindowManagerService.isHardKeyboardAvailable() ?
2604                                    View.VISIBLE : View.GONE);
2605            final Switch hardKeySwitch =  ((Switch)mSwitchingDialogTitleView.findViewById(
2606                    com.android.internal.R.id.hard_keyboard_switch));
2607            hardKeySwitch.setChecked(mWindowManagerService.isHardKeyboardEnabled());
2608            hardKeySwitch.setOnCheckedChangeListener(
2609                    new OnCheckedChangeListener() {
2610                        @Override
2611                        public void onCheckedChanged(
2612                                CompoundButton buttonView, boolean isChecked) {
2613                            mWindowManagerService.setHardKeyboardEnabled(isChecked);
2614                        }
2615                    });
2616
2617            final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context,
2618                    com.android.internal.R.layout.simple_list_item_2_single_choice, imList,
2619                    checkedItem);
2620
2621            mDialogBuilder.setSingleChoiceItems(adapter, checkedItem,
2622                    new AlertDialog.OnClickListener() {
2623                        @Override
2624                        public void onClick(DialogInterface dialog, int which) {
2625                            synchronized (mMethodMap) {
2626                                if (mIms == null || mIms.length <= which
2627                                        || mSubtypeIds == null || mSubtypeIds.length <= which) {
2628                                    return;
2629                                }
2630                                InputMethodInfo im = mIms[which];
2631                                int subtypeId = mSubtypeIds[which];
2632                                hideInputMethodMenu();
2633                                if (im != null) {
2634                                    if ((subtypeId < 0)
2635                                            || (subtypeId >= im.getSubtypeCount())) {
2636                                        subtypeId = NOT_A_SUBTYPE_ID;
2637                                    }
2638                                    setInputMethodLocked(im.getId(), subtypeId);
2639                                }
2640                            }
2641                        }
2642                    });
2643
2644            if (showSubtypes && !isScreenLocked) {
2645                mDialogBuilder.setPositiveButton(
2646                        com.android.internal.R.string.configure_input_methods,
2647                        new DialogInterface.OnClickListener() {
2648                            @Override
2649                            public void onClick(DialogInterface dialog, int whichButton) {
2650                                showConfigureInputMethods();
2651                            }
2652                        });
2653            }
2654            mSwitchingDialog = mDialogBuilder.create();
2655            mSwitchingDialog.setCanceledOnTouchOutside(true);
2656            mSwitchingDialog.getWindow().setType(
2657                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
2658            mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
2659            mSwitchingDialog.show();
2660        }
2661    }
2662
2663    private static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> {
2664        public final CharSequence mImeName;
2665        public final CharSequence mSubtypeName;
2666        public final InputMethodInfo mImi;
2667        public final int mSubtypeId;
2668        private final boolean mIsSystemLocale;
2669        private final boolean mIsSystemLanguage;
2670
2671        public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
2672                InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) {
2673            mImeName = imeName;
2674            mSubtypeName = subtypeName;
2675            mImi = imi;
2676            mSubtypeId = subtypeId;
2677            if (TextUtils.isEmpty(subtypeLocale)) {
2678                mIsSystemLocale = false;
2679                mIsSystemLanguage = false;
2680            } else {
2681                mIsSystemLocale = subtypeLocale.equals(systemLocale);
2682                mIsSystemLanguage = mIsSystemLocale
2683                        || subtypeLocale.startsWith(systemLocale.substring(0, 2));
2684            }
2685        }
2686
2687        @Override
2688        public int compareTo(ImeSubtypeListItem other) {
2689            if (TextUtils.isEmpty(mImeName)) {
2690                return 1;
2691            }
2692            if (TextUtils.isEmpty(other.mImeName)) {
2693                return -1;
2694            }
2695            if (!TextUtils.equals(mImeName, other.mImeName)) {
2696                return mImeName.toString().compareTo(other.mImeName.toString());
2697            }
2698            if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
2699                return 0;
2700            }
2701            if (mIsSystemLocale) {
2702                return -1;
2703            }
2704            if (other.mIsSystemLocale) {
2705                return 1;
2706            }
2707            if (mIsSystemLanguage) {
2708                return -1;
2709            }
2710            if (other.mIsSystemLanguage) {
2711                return 1;
2712            }
2713            if (TextUtils.isEmpty(mSubtypeName)) {
2714                return 1;
2715            }
2716            if (TextUtils.isEmpty(other.mSubtypeName)) {
2717                return -1;
2718            }
2719            return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
2720        }
2721    }
2722
2723    private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
2724        private final LayoutInflater mInflater;
2725        private final int mTextViewResourceId;
2726        private final List<ImeSubtypeListItem> mItemsList;
2727        private final int mCheckedItem;
2728        public ImeSubtypeListAdapter(Context context, int textViewResourceId,
2729                List<ImeSubtypeListItem> itemsList, int checkedItem) {
2730            super(context, textViewResourceId, itemsList);
2731            mTextViewResourceId = textViewResourceId;
2732            mItemsList = itemsList;
2733            mCheckedItem = checkedItem;
2734            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2735        }
2736
2737        @Override
2738        public View getView(int position, View convertView, ViewGroup parent) {
2739            final View view = convertView != null ? convertView
2740                    : mInflater.inflate(mTextViewResourceId, null);
2741            if (position < 0 || position >= mItemsList.size()) return view;
2742            final ImeSubtypeListItem item = mItemsList.get(position);
2743            final CharSequence imeName = item.mImeName;
2744            final CharSequence subtypeName = item.mSubtypeName;
2745            final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
2746            final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
2747            if (TextUtils.isEmpty(subtypeName)) {
2748                firstTextView.setText(imeName);
2749                secondTextView.setVisibility(View.GONE);
2750            } else {
2751                firstTextView.setText(subtypeName);
2752                secondTextView.setText(imeName);
2753                secondTextView.setVisibility(View.VISIBLE);
2754            }
2755            final RadioButton radioButton =
2756                    (RadioButton)view.findViewById(com.android.internal.R.id.radio);
2757            radioButton.setChecked(position == mCheckedItem);
2758            return view;
2759        }
2760    }
2761
2762    void hideInputMethodMenu() {
2763        synchronized (mMethodMap) {
2764            hideInputMethodMenuLocked();
2765        }
2766    }
2767
2768    void hideInputMethodMenuLocked() {
2769        if (DEBUG) Slog.v(TAG, "Hide switching menu");
2770
2771        if (mSwitchingDialog != null) {
2772            mSwitchingDialog.dismiss();
2773            mSwitchingDialog = null;
2774        }
2775
2776        mDialogBuilder = null;
2777        mIms = null;
2778    }
2779
2780    // ----------------------------------------------------------------------
2781
2782    @Override
2783    public boolean setInputMethodEnabled(String id, boolean enabled) {
2784        // TODO: Make this work even for non-current users?
2785        if (!calledFromValidUser()) {
2786            return false;
2787        }
2788        synchronized (mMethodMap) {
2789            if (mContext.checkCallingOrSelfPermission(
2790                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
2791                    != PackageManager.PERMISSION_GRANTED) {
2792                throw new SecurityException(
2793                        "Requires permission "
2794                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2795            }
2796
2797            long ident = Binder.clearCallingIdentity();
2798            try {
2799                return setInputMethodEnabledLocked(id, enabled);
2800            } finally {
2801                Binder.restoreCallingIdentity(ident);
2802            }
2803        }
2804    }
2805
2806    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
2807        // Make sure this is a valid input method.
2808        InputMethodInfo imm = mMethodMap.get(id);
2809        if (imm == null) {
2810            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
2811        }
2812
2813        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
2814                .getEnabledInputMethodsAndSubtypeListLocked();
2815
2816        if (enabled) {
2817            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
2818                if (pair.first.equals(id)) {
2819                    // We are enabling this input method, but it is already enabled.
2820                    // Nothing to do. The previous state was enabled.
2821                    return true;
2822                }
2823            }
2824            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
2825            // Previous state was disabled.
2826            return false;
2827        } else {
2828            StringBuilder builder = new StringBuilder();
2829            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2830                    builder, enabledInputMethodsList, id)) {
2831                // Disabled input method is currently selected, switch to another one.
2832                final String selId = mSettings.getSelectedInputMethod();
2833                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
2834                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
2835                    resetSelectedInputMethodAndSubtypeLocked("");
2836                }
2837                // Previous state was enabled.
2838                return true;
2839            } else {
2840                // We are disabling the input method but it is already disabled.
2841                // Nothing to do.  The previous state was disabled.
2842                return false;
2843            }
2844        }
2845    }
2846
2847    private boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
2848        if (subtype == null) return true;
2849        return !subtype.isAuxiliary();
2850    }
2851
2852    private void saveCurrentInputMethodAndSubtypeToHistory() {
2853        String subtypeId = NOT_A_SUBTYPE_ID_STR;
2854        if (mCurrentSubtype != null) {
2855            subtypeId = String.valueOf(mCurrentSubtype.hashCode());
2856        }
2857        if (canAddToLastInputMethod(mCurrentSubtype)) {
2858            mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
2859        }
2860    }
2861
2862    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
2863            boolean setSubtypeOnly) {
2864        // Update the history of InputMethod and Subtype
2865        saveCurrentInputMethodAndSubtypeToHistory();
2866
2867        // Set Subtype here
2868        if (imi == null || subtypeId < 0) {
2869            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2870            mCurrentSubtype = null;
2871        } else {
2872            if (subtypeId < imi.getSubtypeCount()) {
2873                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
2874                mSettings.putSelectedSubtype(subtype.hashCode());
2875                mCurrentSubtype = subtype;
2876            } else {
2877                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2878                // If the subtype is not specified, choose the most applicable one
2879                mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
2880            }
2881        }
2882
2883        // Workaround.
2884        // ASEC is not ready in the IMMS constructor. Accordingly, forward-locked
2885        // IMEs are not recognized and considered uninstalled.
2886        // Actually, we can't move everything after SystemReady because
2887        // IMMS needs to run in the encryption lock screen. So, we just skip changing
2888        // the default IME here and try cheking the default IME again in systemReady().
2889        // TODO: Do nothing before system ready and implement a separated logic for
2890        // the encryption lock screen.
2891        // TODO: ASEC should be ready before IMMS is instantiated.
2892        if (mSystemReady && !setSubtypeOnly) {
2893            // Set InputMethod here
2894            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
2895        }
2896    }
2897
2898    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
2899        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
2900        int lastSubtypeId = NOT_A_SUBTYPE_ID;
2901        // newDefaultIme is empty when there is no candidate for the selected IME.
2902        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
2903            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
2904            if (subtypeHashCode != null) {
2905                try {
2906                    lastSubtypeId = getSubtypeIdFromHashCode(
2907                            imi, Integer.valueOf(subtypeHashCode));
2908                } catch (NumberFormatException e) {
2909                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
2910                }
2911            }
2912        }
2913        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
2914    }
2915
2916    private int getSelectedInputMethodSubtypeId(String id) {
2917        InputMethodInfo imi = mMethodMap.get(id);
2918        if (imi == null) {
2919            return NOT_A_SUBTYPE_ID;
2920        }
2921        final int subtypeHashCode = mSettings.getSelectedInputMethodSubtypeHashCode();
2922        return getSubtypeIdFromHashCode(imi, subtypeHashCode);
2923    }
2924
2925    private static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
2926        return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
2927    }
2928
2929    private static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
2930        if (imi != null) {
2931            final int subtypeCount = imi.getSubtypeCount();
2932            for (int i = 0; i < subtypeCount; ++i) {
2933                InputMethodSubtype ims = imi.getSubtypeAt(i);
2934                if (subtypeHashCode == ims.hashCode()) {
2935                    return i;
2936                }
2937            }
2938        }
2939        return NOT_A_SUBTYPE_ID;
2940    }
2941
2942    private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
2943            Resources res, InputMethodInfo imi) {
2944        final List<InputMethodSubtype> subtypes = getSubtypes(imi);
2945        final String systemLocale = res.getConfiguration().locale.toString();
2946        if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
2947        final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
2948                new HashMap<String, InputMethodSubtype>();
2949        final int N = subtypes.size();
2950        for (int i = 0; i < N; ++i) {
2951            // scan overriding implicitly enabled subtypes.
2952            InputMethodSubtype subtype = subtypes.get(i);
2953            if (subtype.overridesImplicitlyEnabledSubtype()) {
2954                final String mode = subtype.getMode();
2955                if (!applicableModeAndSubtypesMap.containsKey(mode)) {
2956                    applicableModeAndSubtypesMap.put(mode, subtype);
2957                }
2958            }
2959        }
2960        if (applicableModeAndSubtypesMap.size() > 0) {
2961            return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
2962        }
2963        for (int i = 0; i < N; ++i) {
2964            final InputMethodSubtype subtype = subtypes.get(i);
2965            final String locale = subtype.getLocale();
2966            final String mode = subtype.getMode();
2967            // When system locale starts with subtype's locale, that subtype will be applicable
2968            // for system locale
2969            // For instance, it's clearly applicable for cases like system locale = en_US and
2970            // subtype = en, but it is not necessarily considered applicable for cases like system
2971            // locale = en and subtype = en_US.
2972            // We just call systemLocale.startsWith(locale) in this function because there is no
2973            // need to find applicable subtypes aggressively unlike
2974            // findLastResortApplicableSubtypeLocked.
2975            if (systemLocale.startsWith(locale)) {
2976                final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
2977                // If more applicable subtypes are contained, skip.
2978                if (applicableSubtype != null) {
2979                    if (systemLocale.equals(applicableSubtype.getLocale())) continue;
2980                    if (!systemLocale.equals(locale)) continue;
2981                }
2982                applicableModeAndSubtypesMap.put(mode, subtype);
2983            }
2984        }
2985        final InputMethodSubtype keyboardSubtype
2986                = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
2987        final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
2988                applicableModeAndSubtypesMap.values());
2989        if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
2990            for (int i = 0; i < N; ++i) {
2991                final InputMethodSubtype subtype = subtypes.get(i);
2992                final String mode = subtype.getMode();
2993                if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
2994                        TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
2995                    applicableSubtypes.add(subtype);
2996                }
2997            }
2998        }
2999        if (keyboardSubtype == null) {
3000            InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
3001                    res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
3002            if (lastResortKeyboardSubtype != null) {
3003                applicableSubtypes.add(lastResortKeyboardSubtype);
3004            }
3005        }
3006        return applicableSubtypes;
3007    }
3008
3009    /**
3010     * If there are no selected subtypes, tries finding the most applicable one according to the
3011     * given locale.
3012     * @param subtypes this function will search the most applicable subtype in subtypes
3013     * @param mode subtypes will be filtered by mode
3014     * @param locale subtypes will be filtered by locale
3015     * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
3016     * it will return the first subtype matched with mode
3017     * @return the most applicable subtypeId
3018     */
3019    private static InputMethodSubtype findLastResortApplicableSubtypeLocked(
3020            Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
3021            boolean canIgnoreLocaleAsLastResort) {
3022        if (subtypes == null || subtypes.size() == 0) {
3023            return null;
3024        }
3025        if (TextUtils.isEmpty(locale)) {
3026            locale = res.getConfiguration().locale.toString();
3027        }
3028        final String language = locale.substring(0, 2);
3029        boolean partialMatchFound = false;
3030        InputMethodSubtype applicableSubtype = null;
3031        InputMethodSubtype firstMatchedModeSubtype = null;
3032        final int N = subtypes.size();
3033        for (int i = 0; i < N; ++i) {
3034            InputMethodSubtype subtype = subtypes.get(i);
3035            final String subtypeLocale = subtype.getLocale();
3036            // An applicable subtype should match "mode". If mode is null, mode will be ignored,
3037            // and all subtypes with all modes can be candidates.
3038            if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
3039                if (firstMatchedModeSubtype == null) {
3040                    firstMatchedModeSubtype = subtype;
3041                }
3042                if (locale.equals(subtypeLocale)) {
3043                    // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
3044                    applicableSubtype = subtype;
3045                    break;
3046                } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
3047                    // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
3048                    applicableSubtype = subtype;
3049                    partialMatchFound = true;
3050                }
3051            }
3052        }
3053
3054        if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
3055            return firstMatchedModeSubtype;
3056        }
3057
3058        // The first subtype applicable to the system locale will be defined as the most applicable
3059        // subtype.
3060        if (DEBUG) {
3061            if (applicableSubtype != null) {
3062                Slog.d(TAG, "Applicable InputMethodSubtype was found: "
3063                        + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
3064            }
3065        }
3066        return applicableSubtype;
3067    }
3068
3069    // If there are no selected shortcuts, tries finding the most applicable ones.
3070    private Pair<InputMethodInfo, InputMethodSubtype>
3071            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
3072        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
3073        InputMethodInfo mostApplicableIMI = null;
3074        InputMethodSubtype mostApplicableSubtype = null;
3075        boolean foundInSystemIME = false;
3076
3077        // Search applicable subtype for each InputMethodInfo
3078        for (InputMethodInfo imi: imis) {
3079            final String imiId = imi.getId();
3080            if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
3081                continue;
3082            }
3083            InputMethodSubtype subtype = null;
3084            final List<InputMethodSubtype> enabledSubtypes =
3085                    getEnabledInputMethodSubtypeListLocked(imi, true);
3086            // 1. Search by the current subtype's locale from enabledSubtypes.
3087            if (mCurrentSubtype != null) {
3088                subtype = findLastResortApplicableSubtypeLocked(
3089                        mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
3090            }
3091            // 2. Search by the system locale from enabledSubtypes.
3092            // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
3093            if (subtype == null) {
3094                subtype = findLastResortApplicableSubtypeLocked(
3095                        mRes, enabledSubtypes, mode, null, true);
3096            }
3097            final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
3098                    getOverridingImplicitlyEnabledSubtypes(imi, mode);
3099            final ArrayList<InputMethodSubtype> subtypesForSearch =
3100                    overridingImplicitlyEnabledSubtypes.isEmpty()
3101                            ? getSubtypes(imi) : overridingImplicitlyEnabledSubtypes;
3102            // 4. Search by the current subtype's locale from all subtypes.
3103            if (subtype == null && mCurrentSubtype != null) {
3104                subtype = findLastResortApplicableSubtypeLocked(
3105                        mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
3106            }
3107            // 5. Search by the system locale from all subtypes.
3108            // 6. Search the first enabled subtype matched with mode from all subtypes.
3109            if (subtype == null) {
3110                subtype = findLastResortApplicableSubtypeLocked(
3111                        mRes, subtypesForSearch, mode, null, true);
3112            }
3113            if (subtype != null) {
3114                if (imiId.equals(mCurMethodId)) {
3115                    // The current input method is the most applicable IME.
3116                    mostApplicableIMI = imi;
3117                    mostApplicableSubtype = subtype;
3118                    break;
3119                } else if (!foundInSystemIME) {
3120                    // The system input method is 2nd applicable IME.
3121                    mostApplicableIMI = imi;
3122                    mostApplicableSubtype = subtype;
3123                    if ((imi.getServiceInfo().applicationInfo.flags
3124                            & ApplicationInfo.FLAG_SYSTEM) != 0) {
3125                        foundInSystemIME = true;
3126                    }
3127                }
3128            }
3129        }
3130        if (DEBUG) {
3131            if (mostApplicableIMI != null) {
3132                Slog.w(TAG, "Most applicable shortcut input method was:"
3133                        + mostApplicableIMI.getId());
3134                if (mostApplicableSubtype != null) {
3135                    Slog.w(TAG, "Most applicable shortcut input method subtype was:"
3136                            + "," + mostApplicableSubtype.getMode() + ","
3137                            + mostApplicableSubtype.getLocale());
3138                }
3139            }
3140        }
3141        if (mostApplicableIMI != null) {
3142            return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
3143                    mostApplicableSubtype);
3144        } else {
3145            return null;
3146        }
3147    }
3148
3149    /**
3150     * @return Return the current subtype of this input method.
3151     */
3152    @Override
3153    public InputMethodSubtype getCurrentInputMethodSubtype() {
3154        // TODO: Make this work even for non-current users?
3155        if (!calledFromValidUser()) {
3156            return null;
3157        }
3158        synchronized (mMethodMap) {
3159            return getCurrentInputMethodSubtypeLocked();
3160        }
3161    }
3162
3163    private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
3164        if (mCurMethodId == null) {
3165            return null;
3166        }
3167        final boolean subtypeIsSelected =
3168                mSettings.getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
3169        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3170        if (imi == null || imi.getSubtypeCount() == 0) {
3171            return null;
3172        }
3173        if (!subtypeIsSelected || mCurrentSubtype == null
3174                || !isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
3175            int subtypeId = getSelectedInputMethodSubtypeId(mCurMethodId);
3176            if (subtypeId == NOT_A_SUBTYPE_ID) {
3177                // If there are no selected subtypes, the framework will try to find
3178                // the most applicable subtype from explicitly or implicitly enabled
3179                // subtypes.
3180                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
3181                        getEnabledInputMethodSubtypeListLocked(imi, true);
3182                // If there is only one explicitly or implicitly enabled subtype,
3183                // just returns it.
3184                if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
3185                    mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
3186                } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
3187                    mCurrentSubtype = findLastResortApplicableSubtypeLocked(
3188                            mRes, explicitlyOrImplicitlyEnabledSubtypes,
3189                            SUBTYPE_MODE_KEYBOARD, null, true);
3190                    if (mCurrentSubtype == null) {
3191                        mCurrentSubtype = findLastResortApplicableSubtypeLocked(
3192                                mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
3193                                true);
3194                    }
3195                }
3196            } else {
3197                mCurrentSubtype = getSubtypes(imi).get(subtypeId);
3198            }
3199        }
3200        return mCurrentSubtype;
3201    }
3202
3203    private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
3204            InputMethodSubtype subtype) {
3205        if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
3206            mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
3207        } else {
3208            ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
3209            subtypes.add(subtype);
3210            mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
3211        }
3212    }
3213
3214    // TODO: We should change the return type from List to List<Parcelable>
3215    @SuppressWarnings("rawtypes")
3216    @Override
3217    public List getShortcutInputMethodsAndSubtypes() {
3218        synchronized (mMethodMap) {
3219            ArrayList<Object> ret = new ArrayList<Object>();
3220            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
3221                // If there are no selected shortcut subtypes, the framework will try to find
3222                // the most applicable subtype from all subtypes whose mode is
3223                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
3224                Pair<InputMethodInfo, InputMethodSubtype> info =
3225                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
3226                            SUBTYPE_MODE_VOICE);
3227                if (info != null) {
3228                    ret.add(info.first);
3229                    ret.add(info.second);
3230                }
3231                return ret;
3232            }
3233            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
3234                ret.add(imi);
3235                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
3236                    ret.add(subtype);
3237                }
3238            }
3239            return ret;
3240        }
3241    }
3242
3243    @Override
3244    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
3245        // TODO: Make this work even for non-current users?
3246        if (!calledFromValidUser()) {
3247            return false;
3248        }
3249        synchronized (mMethodMap) {
3250            if (subtype != null && mCurMethodId != null) {
3251                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3252                int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
3253                if (subtypeId != NOT_A_SUBTYPE_ID) {
3254                    setInputMethodLocked(mCurMethodId, subtypeId);
3255                    return true;
3256                }
3257            }
3258            return false;
3259        }
3260    }
3261
3262    private static class InputMethodAndSubtypeListManager {
3263        private final Context mContext;
3264        // Used to load label
3265        private final PackageManager mPm;
3266        private final InputMethodManagerService mImms;
3267        private final String mSystemLocaleStr;
3268        public InputMethodAndSubtypeListManager(Context context, InputMethodManagerService imms) {
3269            mContext = context;
3270            mPm = context.getPackageManager();
3271            mImms = imms;
3272            final Locale locale = context.getResources().getConfiguration().locale;
3273            mSystemLocaleStr = locale != null ? locale.toString() : "";
3274        }
3275
3276        private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
3277                new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
3278                        new Comparator<InputMethodInfo>() {
3279                            @Override
3280                            public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
3281                                if (imi2 == null) return 0;
3282                                if (imi1 == null) return 1;
3283                                if (mPm == null) {
3284                                    return imi1.getId().compareTo(imi2.getId());
3285                                }
3286                                CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId();
3287                                CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId();
3288                                return imiId1.toString().compareTo(imiId2.toString());
3289                            }
3290                        });
3291
3292        public ImeSubtypeListItem getNextInputMethod(
3293                boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
3294            if (imi == null) {
3295                return null;
3296            }
3297            final List<ImeSubtypeListItem> imList = getSortedInputMethodAndSubtypeList();
3298            if (imList.size() <= 1) {
3299                return null;
3300            }
3301            final int N = imList.size();
3302            final int currentSubtypeId = subtype != null
3303                    ? getSubtypeIdFromHashCode(imi, subtype.hashCode())
3304                    : NOT_A_SUBTYPE_ID;
3305            for (int i = 0; i < N; ++i) {
3306                final ImeSubtypeListItem isli = imList.get(i);
3307                if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) {
3308                    if (!onlyCurrentIme) {
3309                        return imList.get((i + 1) % N);
3310                    }
3311                    for (int j = 0; j < N - 1; ++j) {
3312                        final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N);
3313                        if (candidate.mImi.equals(imi)) {
3314                            return candidate;
3315                        }
3316                    }
3317                    return null;
3318                }
3319            }
3320            return null;
3321        }
3322
3323        public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() {
3324            return getSortedInputMethodAndSubtypeList(true, false, false);
3325        }
3326
3327        public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes,
3328                boolean inputShown, boolean isScreenLocked) {
3329            final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>();
3330            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
3331                    mImms.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
3332            if (immis == null || immis.size() == 0) {
3333                return Collections.emptyList();
3334            }
3335            mSortedImmis.clear();
3336            mSortedImmis.putAll(immis);
3337            for (InputMethodInfo imi : mSortedImmis.keySet()) {
3338                if (imi == null) continue;
3339                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
3340                HashSet<String> enabledSubtypeSet = new HashSet<String>();
3341                for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
3342                    enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
3343                }
3344                ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi);
3345                final CharSequence imeLabel = imi.loadLabel(mPm);
3346                if (showSubtypes && enabledSubtypeSet.size() > 0) {
3347                    final int subtypeCount = imi.getSubtypeCount();
3348                    if (DEBUG) {
3349                        Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
3350                    }
3351                    for (int j = 0; j < subtypeCount; ++j) {
3352                        final InputMethodSubtype subtype = imi.getSubtypeAt(j);
3353                        final String subtypeHashCode = String.valueOf(subtype.hashCode());
3354                        // We show all enabled IMEs and subtypes when an IME is shown.
3355                        if (enabledSubtypeSet.contains(subtypeHashCode)
3356                                && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) {
3357                            final CharSequence subtypeLabel =
3358                                    subtype.overridesImplicitlyEnabledSubtype() ? null
3359                                            : subtype.getDisplayName(mContext, imi.getPackageName(),
3360                                                    imi.getServiceInfo().applicationInfo);
3361                            imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j,
3362                                    subtype.getLocale(), mSystemLocaleStr));
3363
3364                            // Removing this subtype from enabledSubtypeSet because we no longer
3365                            // need to add an entry of this subtype to imList to avoid duplicated
3366                            // entries.
3367                            enabledSubtypeSet.remove(subtypeHashCode);
3368                        }
3369                    }
3370                } else {
3371                    imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID,
3372                            null, mSystemLocaleStr));
3373                }
3374            }
3375            Collections.sort(imList);
3376            return imList;
3377        }
3378    }
3379
3380    /**
3381     * Utility class for putting and getting settings for InputMethod
3382     * TODO: Move all putters and getters of settings to this class.
3383     */
3384    private static class InputMethodSettings {
3385        // The string for enabled input method is saved as follows:
3386        // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
3387        private static final char INPUT_METHOD_SEPARATER = ':';
3388        private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
3389        private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
3390                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
3391
3392        private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
3393                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
3394
3395        private final Resources mRes;
3396        private final ContentResolver mResolver;
3397        private final HashMap<String, InputMethodInfo> mMethodMap;
3398        private final ArrayList<InputMethodInfo> mMethodList;
3399
3400        private String mEnabledInputMethodsStrCache;
3401        private int mCurrentUserId;
3402
3403        private static void buildEnabledInputMethodsSettingString(
3404                StringBuilder builder, Pair<String, ArrayList<String>> pair) {
3405            String id = pair.first;
3406            ArrayList<String> subtypes = pair.second;
3407            builder.append(id);
3408            // Inputmethod and subtypes are saved in the settings as follows:
3409            // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
3410            for (String subtypeId: subtypes) {
3411                builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
3412            }
3413        }
3414
3415        public InputMethodSettings(
3416                Resources res, ContentResolver resolver,
3417                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
3418                int userId) {
3419            setCurrentUserId(userId);
3420            mRes = res;
3421            mResolver = resolver;
3422            mMethodMap = methodMap;
3423            mMethodList = methodList;
3424        }
3425
3426        public void setCurrentUserId(int userId) {
3427            if (DEBUG) {
3428                Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
3429                        + userId + ", new ime = " + getSelectedInputMethod());
3430            }
3431            // IMMS settings are kept per user, so keep track of current user
3432            mCurrentUserId = userId;
3433        }
3434
3435        public List<InputMethodInfo> getEnabledInputMethodListLocked() {
3436            return createEnabledInputMethodListLocked(
3437                    getEnabledInputMethodsAndSubtypeListLocked());
3438        }
3439
3440        public List<Pair<InputMethodInfo, ArrayList<String>>>
3441                getEnabledInputMethodAndSubtypeHashCodeListLocked() {
3442            return createEnabledInputMethodAndSubtypeHashCodeListLocked(
3443                    getEnabledInputMethodsAndSubtypeListLocked());
3444        }
3445
3446        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
3447                InputMethodInfo imi) {
3448            List<Pair<String, ArrayList<String>>> imsList =
3449                    getEnabledInputMethodsAndSubtypeListLocked();
3450            ArrayList<InputMethodSubtype> enabledSubtypes =
3451                    new ArrayList<InputMethodSubtype>();
3452            if (imi != null) {
3453                for (Pair<String, ArrayList<String>> imsPair : imsList) {
3454                    InputMethodInfo info = mMethodMap.get(imsPair.first);
3455                    if (info != null && info.getId().equals(imi.getId())) {
3456                        final int subtypeCount = info.getSubtypeCount();
3457                        for (int i = 0; i < subtypeCount; ++i) {
3458                            InputMethodSubtype ims = info.getSubtypeAt(i);
3459                            for (String s: imsPair.second) {
3460                                if (String.valueOf(ims.hashCode()).equals(s)) {
3461                                    enabledSubtypes.add(ims);
3462                                }
3463                            }
3464                        }
3465                        break;
3466                    }
3467                }
3468            }
3469            return enabledSubtypes;
3470        }
3471
3472        // At the initial boot, the settings for input methods are not set,
3473        // so we need to enable IME in that case.
3474        public void enableAllIMEsIfThereIsNoEnabledIME() {
3475            if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
3476                StringBuilder sb = new StringBuilder();
3477                final int N = mMethodList.size();
3478                for (int i = 0; i < N; i++) {
3479                    InputMethodInfo imi = mMethodList.get(i);
3480                    Slog.i(TAG, "Adding: " + imi.getId());
3481                    if (i > 0) sb.append(':');
3482                    sb.append(imi.getId());
3483                }
3484                putEnabledInputMethodsStr(sb.toString());
3485            }
3486        }
3487
3488        private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
3489            ArrayList<Pair<String, ArrayList<String>>> imsList
3490                    = new ArrayList<Pair<String, ArrayList<String>>>();
3491            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
3492            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
3493                return imsList;
3494            }
3495            mInputMethodSplitter.setString(enabledInputMethodsStr);
3496            while (mInputMethodSplitter.hasNext()) {
3497                String nextImsStr = mInputMethodSplitter.next();
3498                mSubtypeSplitter.setString(nextImsStr);
3499                if (mSubtypeSplitter.hasNext()) {
3500                    ArrayList<String> subtypeHashes = new ArrayList<String>();
3501                    // The first element is ime id.
3502                    String imeId = mSubtypeSplitter.next();
3503                    while (mSubtypeSplitter.hasNext()) {
3504                        subtypeHashes.add(mSubtypeSplitter.next());
3505                    }
3506                    imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
3507                }
3508            }
3509            return imsList;
3510        }
3511
3512        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
3513            if (reloadInputMethodStr) {
3514                getEnabledInputMethodsStr();
3515            }
3516            if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
3517                // Add in the newly enabled input method.
3518                putEnabledInputMethodsStr(id);
3519            } else {
3520                putEnabledInputMethodsStr(
3521                        mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
3522            }
3523        }
3524
3525        /**
3526         * Build and put a string of EnabledInputMethods with removing specified Id.
3527         * @return the specified id was removed or not.
3528         */
3529        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
3530                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
3531            boolean isRemoved = false;
3532            boolean needsAppendSeparator = false;
3533            for (Pair<String, ArrayList<String>> ims: imsList) {
3534                String curId = ims.first;
3535                if (curId.equals(id)) {
3536                    // We are disabling this input method, and it is
3537                    // currently enabled.  Skip it to remove from the
3538                    // new list.
3539                    isRemoved = true;
3540                } else {
3541                    if (needsAppendSeparator) {
3542                        builder.append(INPUT_METHOD_SEPARATER);
3543                    } else {
3544                        needsAppendSeparator = true;
3545                    }
3546                    buildEnabledInputMethodsSettingString(builder, ims);
3547                }
3548            }
3549            if (isRemoved) {
3550                // Update the setting with the new list of input methods.
3551                putEnabledInputMethodsStr(builder.toString());
3552            }
3553            return isRemoved;
3554        }
3555
3556        private List<InputMethodInfo> createEnabledInputMethodListLocked(
3557                List<Pair<String, ArrayList<String>>> imsList) {
3558            final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
3559            for (Pair<String, ArrayList<String>> ims: imsList) {
3560                InputMethodInfo info = mMethodMap.get(ims.first);
3561                if (info != null) {
3562                    res.add(info);
3563                }
3564            }
3565            return res;
3566        }
3567
3568        private List<Pair<InputMethodInfo, ArrayList<String>>>
3569                createEnabledInputMethodAndSubtypeHashCodeListLocked(
3570                        List<Pair<String, ArrayList<String>>> imsList) {
3571            final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
3572                    = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
3573            for (Pair<String, ArrayList<String>> ims : imsList) {
3574                InputMethodInfo info = mMethodMap.get(ims.first);
3575                if (info != null) {
3576                    res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
3577                }
3578            }
3579            return res;
3580        }
3581
3582        private void putEnabledInputMethodsStr(String str) {
3583            Settings.Secure.putStringForUser(
3584                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
3585            mEnabledInputMethodsStrCache = str;
3586            if (DEBUG) {
3587                Slog.d(TAG, "putEnabledInputMethodStr: " + str);
3588            }
3589        }
3590
3591        private String getEnabledInputMethodsStr() {
3592            mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
3593                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
3594            if (DEBUG) {
3595                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
3596                        + ", " + mCurrentUserId);
3597            }
3598            return mEnabledInputMethodsStrCache;
3599        }
3600
3601        private void saveSubtypeHistory(
3602                List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
3603            StringBuilder builder = new StringBuilder();
3604            boolean isImeAdded = false;
3605            if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
3606                builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
3607                        newSubtypeId);
3608                isImeAdded = true;
3609            }
3610            for (Pair<String, String> ime: savedImes) {
3611                String imeId = ime.first;
3612                String subtypeId = ime.second;
3613                if (TextUtils.isEmpty(subtypeId)) {
3614                    subtypeId = NOT_A_SUBTYPE_ID_STR;
3615                }
3616                if (isImeAdded) {
3617                    builder.append(INPUT_METHOD_SEPARATER);
3618                } else {
3619                    isImeAdded = true;
3620                }
3621                builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
3622                        subtypeId);
3623            }
3624            // Remove the last INPUT_METHOD_SEPARATER
3625            putSubtypeHistoryStr(builder.toString());
3626        }
3627
3628        public void addSubtypeToHistory(String imeId, String subtypeId) {
3629            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
3630            for (Pair<String, String> ime: subtypeHistory) {
3631                if (ime.first.equals(imeId)) {
3632                    if (DEBUG) {
3633                        Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
3634                                + ime.second);
3635                    }
3636                    // We should break here
3637                    subtypeHistory.remove(ime);
3638                    break;
3639                }
3640            }
3641            if (DEBUG) {
3642                Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
3643            }
3644            saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
3645        }
3646
3647        private void putSubtypeHistoryStr(String str) {
3648            if (DEBUG) {
3649                Slog.d(TAG, "putSubtypeHistoryStr: " + str);
3650            }
3651            Settings.Secure.putStringForUser(
3652                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
3653        }
3654
3655        public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
3656            // Gets the first one from the history
3657            return getLastSubtypeForInputMethodLockedInternal(null);
3658        }
3659
3660        public String getLastSubtypeForInputMethodLocked(String imeId) {
3661            Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
3662            if (ime != null) {
3663                return ime.second;
3664            } else {
3665                return null;
3666            }
3667        }
3668
3669        private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
3670            List<Pair<String, ArrayList<String>>> enabledImes =
3671                    getEnabledInputMethodsAndSubtypeListLocked();
3672            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
3673            for (Pair<String, String> imeAndSubtype : subtypeHistory) {
3674                final String imeInTheHistory = imeAndSubtype.first;
3675                // If imeId is empty, returns the first IME and subtype in the history
3676                if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
3677                    final String subtypeInTheHistory = imeAndSubtype.second;
3678                    final String subtypeHashCode =
3679                            getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
3680                                    enabledImes, imeInTheHistory, subtypeInTheHistory);
3681                    if (!TextUtils.isEmpty(subtypeHashCode)) {
3682                        if (DEBUG) {
3683                            Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
3684                        }
3685                        return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
3686                    }
3687                }
3688            }
3689            if (DEBUG) {
3690                Slog.d(TAG, "No enabled IME found in the history");
3691            }
3692            return null;
3693        }
3694
3695        private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
3696                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
3697            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
3698                if (enabledIme.first.equals(imeId)) {
3699                    final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
3700                    final InputMethodInfo imi = mMethodMap.get(imeId);
3701                    if (explicitlyEnabledSubtypes.size() == 0) {
3702                        // If there are no explicitly enabled subtypes, applicable subtypes are
3703                        // enabled implicitly.
3704                        // If IME is enabled and no subtypes are enabled, applicable subtypes
3705                        // are enabled implicitly, so needs to treat them to be enabled.
3706                        if (imi != null && imi.getSubtypeCount() > 0) {
3707                            List<InputMethodSubtype> implicitlySelectedSubtypes =
3708                                    getImplicitlyApplicableSubtypesLocked(mRes, imi);
3709                            if (implicitlySelectedSubtypes != null) {
3710                                final int N = implicitlySelectedSubtypes.size();
3711                                for (int i = 0; i < N; ++i) {
3712                                    final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
3713                                    if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
3714                                        return subtypeHashCode;
3715                                    }
3716                                }
3717                            }
3718                        }
3719                    } else {
3720                        for (String s: explicitlyEnabledSubtypes) {
3721                            if (s.equals(subtypeHashCode)) {
3722                                // If both imeId and subtypeId are enabled, return subtypeId.
3723                                try {
3724                                    final int hashCode = Integer.valueOf(subtypeHashCode);
3725                                    // Check whether the subtype id is valid or not
3726                                    if (isValidSubtypeId(imi, hashCode)) {
3727                                        return s;
3728                                    } else {
3729                                        return NOT_A_SUBTYPE_ID_STR;
3730                                    }
3731                                } catch (NumberFormatException e) {
3732                                    return NOT_A_SUBTYPE_ID_STR;
3733                                }
3734                            }
3735                        }
3736                    }
3737                    // If imeId was enabled but subtypeId was disabled.
3738                    return NOT_A_SUBTYPE_ID_STR;
3739                }
3740            }
3741            // If both imeId and subtypeId are disabled, return null
3742            return null;
3743        }
3744
3745        private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
3746            ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
3747            final String subtypeHistoryStr = getSubtypeHistoryStr();
3748            if (TextUtils.isEmpty(subtypeHistoryStr)) {
3749                return imsList;
3750            }
3751            mInputMethodSplitter.setString(subtypeHistoryStr);
3752            while (mInputMethodSplitter.hasNext()) {
3753                String nextImsStr = mInputMethodSplitter.next();
3754                mSubtypeSplitter.setString(nextImsStr);
3755                if (mSubtypeSplitter.hasNext()) {
3756                    String subtypeId = NOT_A_SUBTYPE_ID_STR;
3757                    // The first element is ime id.
3758                    String imeId = mSubtypeSplitter.next();
3759                    while (mSubtypeSplitter.hasNext()) {
3760                        subtypeId = mSubtypeSplitter.next();
3761                        break;
3762                    }
3763                    imsList.add(new Pair<String, String>(imeId, subtypeId));
3764                }
3765            }
3766            return imsList;
3767        }
3768
3769        private String getSubtypeHistoryStr() {
3770            if (DEBUG) {
3771                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
3772                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
3773            }
3774            return Settings.Secure.getStringForUser(
3775                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
3776        }
3777
3778        public void putSelectedInputMethod(String imeId) {
3779            if (DEBUG) {
3780                Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
3781                        + mCurrentUserId);
3782            }
3783            Settings.Secure.putStringForUser(
3784                    mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
3785        }
3786
3787        public void putSelectedSubtype(int subtypeId) {
3788            if (DEBUG) {
3789                Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
3790                        + mCurrentUserId);
3791            }
3792            Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
3793                    subtypeId, mCurrentUserId);
3794        }
3795
3796        public String getDisabledSystemInputMethods() {
3797            return Settings.Secure.getStringForUser(
3798                    mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
3799        }
3800
3801        public String getSelectedInputMethod() {
3802            if (DEBUG) {
3803                Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
3804                        mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
3805                        + ", " + mCurrentUserId);
3806            }
3807            return Settings.Secure.getStringForUser(
3808                    mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
3809        }
3810
3811        public int getSelectedInputMethodSubtypeHashCode() {
3812            try {
3813                return Settings.Secure.getIntForUser(
3814                        mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
3815            } catch (SettingNotFoundException e) {
3816                return NOT_A_SUBTYPE_ID;
3817            }
3818        }
3819
3820        public int getCurrentUserId() {
3821            return mCurrentUserId;
3822        }
3823    }
3824
3825    // TODO: Cache the state for each user and reset when the cached user is removed.
3826    private static class InputMethodFileManager {
3827        private static final String SYSTEM_PATH = "system";
3828        private static final String INPUT_METHOD_PATH = "inputmethod";
3829        private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
3830        private static final String NODE_SUBTYPES = "subtypes";
3831        private static final String NODE_SUBTYPE = "subtype";
3832        private static final String NODE_IMI = "imi";
3833        private static final String ATTR_ID = "id";
3834        private static final String ATTR_LABEL = "label";
3835        private static final String ATTR_ICON = "icon";
3836        private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
3837        private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
3838        private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
3839        private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
3840        private final AtomicFile mAdditionalInputMethodSubtypeFile;
3841        private final HashMap<String, InputMethodInfo> mMethodMap;
3842        private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
3843                new HashMap<String, List<InputMethodSubtype>>();
3844        public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
3845            if (methodMap == null) {
3846                throw new NullPointerException("methodMap is null");
3847            }
3848            mMethodMap = methodMap;
3849            final File systemDir = userId == UserHandle.USER_OWNER
3850                    ? new File(Environment.getDataDirectory(), SYSTEM_PATH)
3851                    : Environment.getUserSystemDirectory(userId);
3852            final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
3853            if (!inputMethodDir.mkdirs()) {
3854                Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
3855            }
3856            final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
3857            mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
3858            if (!subtypeFile.exists()) {
3859                // If "subtypes.xml" doesn't exist, create a blank file.
3860                writeAdditionalInputMethodSubtypes(
3861                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
3862            } else {
3863                readAdditionalInputMethodSubtypes(
3864                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
3865            }
3866        }
3867
3868        private void deleteAllInputMethodSubtypes(String imiId) {
3869            synchronized (mMethodMap) {
3870                mAdditionalSubtypesMap.remove(imiId);
3871                writeAdditionalInputMethodSubtypes(
3872                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3873            }
3874        }
3875
3876        public void addInputMethodSubtypes(
3877                InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
3878            synchronized (mMethodMap) {
3879                final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
3880                final int N = additionalSubtypes.length;
3881                for (int i = 0; i < N; ++i) {
3882                    final InputMethodSubtype subtype = additionalSubtypes[i];
3883                    if (!subtypes.contains(subtype)) {
3884                        subtypes.add(subtype);
3885                    } else {
3886                        Slog.w(TAG, "Duplicated subtype definition found: "
3887                                + subtype.getLocale() + ", " + subtype.getMode());
3888                    }
3889                }
3890                mAdditionalSubtypesMap.put(imi.getId(), subtypes);
3891                writeAdditionalInputMethodSubtypes(
3892                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3893            }
3894        }
3895
3896        public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
3897            synchronized (mMethodMap) {
3898                return mAdditionalSubtypesMap;
3899            }
3900        }
3901
3902        private static void writeAdditionalInputMethodSubtypes(
3903                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
3904                HashMap<String, InputMethodInfo> methodMap) {
3905            // Safety net for the case that this function is called before methodMap is set.
3906            final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
3907            FileOutputStream fos = null;
3908            try {
3909                fos = subtypesFile.startWrite();
3910                final XmlSerializer out = new FastXmlSerializer();
3911                out.setOutput(fos, "utf-8");
3912                out.startDocument(null, true);
3913                out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
3914                out.startTag(null, NODE_SUBTYPES);
3915                for (String imiId : allSubtypes.keySet()) {
3916                    if (isSetMethodMap && !methodMap.containsKey(imiId)) {
3917                        Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
3918                        continue;
3919                    }
3920                    out.startTag(null, NODE_IMI);
3921                    out.attribute(null, ATTR_ID, imiId);
3922                    final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
3923                    final int N = subtypesList.size();
3924                    for (int i = 0; i < N; ++i) {
3925                        final InputMethodSubtype subtype = subtypesList.get(i);
3926                        out.startTag(null, NODE_SUBTYPE);
3927                        out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
3928                        out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
3929                        out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
3930                        out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
3931                        out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
3932                        out.attribute(null, ATTR_IS_AUXILIARY,
3933                                String.valueOf(subtype.isAuxiliary() ? 1 : 0));
3934                        out.endTag(null, NODE_SUBTYPE);
3935                    }
3936                    out.endTag(null, NODE_IMI);
3937                }
3938                out.endTag(null, NODE_SUBTYPES);
3939                out.endDocument();
3940                subtypesFile.finishWrite(fos);
3941            } catch (java.io.IOException e) {
3942                Slog.w(TAG, "Error writing subtypes", e);
3943                if (fos != null) {
3944                    subtypesFile.failWrite(fos);
3945                }
3946            }
3947        }
3948
3949        private static void readAdditionalInputMethodSubtypes(
3950                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
3951            if (allSubtypes == null || subtypesFile == null) return;
3952            allSubtypes.clear();
3953            FileInputStream fis = null;
3954            try {
3955                fis = subtypesFile.openRead();
3956                final XmlPullParser parser = Xml.newPullParser();
3957                parser.setInput(fis, null);
3958                int type = parser.getEventType();
3959                // Skip parsing until START_TAG
3960                while ((type = parser.next()) != XmlPullParser.START_TAG
3961                        && type != XmlPullParser.END_DOCUMENT) {}
3962                String firstNodeName = parser.getName();
3963                if (!NODE_SUBTYPES.equals(firstNodeName)) {
3964                    throw new XmlPullParserException("Xml doesn't start with subtypes");
3965                }
3966                final int depth =parser.getDepth();
3967                String currentImiId = null;
3968                ArrayList<InputMethodSubtype> tempSubtypesArray = null;
3969                while (((type = parser.next()) != XmlPullParser.END_TAG
3970                        || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
3971                    if (type != XmlPullParser.START_TAG)
3972                        continue;
3973                    final String nodeName = parser.getName();
3974                    if (NODE_IMI.equals(nodeName)) {
3975                        currentImiId = parser.getAttributeValue(null, ATTR_ID);
3976                        if (TextUtils.isEmpty(currentImiId)) {
3977                            Slog.w(TAG, "Invalid imi id found in subtypes.xml");
3978                            continue;
3979                        }
3980                        tempSubtypesArray = new ArrayList<InputMethodSubtype>();
3981                        allSubtypes.put(currentImiId, tempSubtypesArray);
3982                    } else if (NODE_SUBTYPE.equals(nodeName)) {
3983                        if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
3984                            Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
3985                            continue;
3986                        }
3987                        final int icon = Integer.valueOf(
3988                                parser.getAttributeValue(null, ATTR_ICON));
3989                        final int label = Integer.valueOf(
3990                                parser.getAttributeValue(null, ATTR_LABEL));
3991                        final String imeSubtypeLocale =
3992                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
3993                        final String imeSubtypeMode =
3994                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
3995                        final String imeSubtypeExtraValue =
3996                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
3997                        final boolean isAuxiliary = "1".equals(String.valueOf(
3998                                parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
3999                        final InputMethodSubtype subtype =
4000                                new InputMethodSubtype(label, icon, imeSubtypeLocale,
4001                                        imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
4002                        tempSubtypesArray.add(subtype);
4003                    }
4004                }
4005            } catch (XmlPullParserException e) {
4006                Slog.w(TAG, "Error reading subtypes: " + e);
4007                return;
4008            } catch (java.io.IOException e) {
4009                Slog.w(TAG, "Error reading subtypes: " + e);
4010                return;
4011            } catch (NumberFormatException e) {
4012                Slog.w(TAG, "Error reading subtypes: " + e);
4013                return;
4014            } finally {
4015                if (fis != null) {
4016                    try {
4017                        fis.close();
4018                    } catch (java.io.IOException e1) {
4019                        Slog.w(TAG, "Failed to close.");
4020                    }
4021                }
4022            }
4023        }
4024    }
4025
4026    // ----------------------------------------------------------------------
4027    // Utilities for debug
4028    private static String getStackTrace() {
4029        final StringBuilder sb = new StringBuilder();
4030        try {
4031            throw new RuntimeException();
4032        } catch (RuntimeException e) {
4033            final StackTraceElement[] frames = e.getStackTrace();
4034            // Start at 1 because the first frame is here and we don't care about it
4035            for (int j = 1; j < frames.length; ++j) {
4036                sb.append(frames[j].toString() + "\n");
4037            }
4038        }
4039        return sb.toString();
4040    }
4041
4042    @Override
4043    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
4044        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
4045                != PackageManager.PERMISSION_GRANTED) {
4046
4047            pw.println("Permission Denial: can't dump InputMethodManager from from pid="
4048                    + Binder.getCallingPid()
4049                    + ", uid=" + Binder.getCallingUid());
4050            return;
4051        }
4052
4053        IInputMethod method;
4054        ClientState client;
4055
4056        final Printer p = new PrintWriterPrinter(pw);
4057
4058        synchronized (mMethodMap) {
4059            p.println("Current Input Method Manager state:");
4060            int N = mMethodList.size();
4061            p.println("  Input Methods:");
4062            for (int i=0; i<N; i++) {
4063                InputMethodInfo info = mMethodList.get(i);
4064                p.println("  InputMethod #" + i + ":");
4065                info.dump(p, "    ");
4066            }
4067            p.println("  Clients:");
4068            for (ClientState ci : mClients.values()) {
4069                p.println("  Client " + ci + ":");
4070                p.println("    client=" + ci.client);
4071                p.println("    inputContext=" + ci.inputContext);
4072                p.println("    sessionRequested=" + ci.sessionRequested);
4073                p.println("    curSession=" + ci.curSession);
4074            }
4075            p.println("  mCurMethodId=" + mCurMethodId);
4076            client = mCurClient;
4077            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
4078            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
4079            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
4080                    + " mBoundToMethod=" + mBoundToMethod);
4081            p.println("  mCurToken=" + mCurToken);
4082            p.println("  mCurIntent=" + mCurIntent);
4083            method = mCurMethod;
4084            p.println("  mCurMethod=" + mCurMethod);
4085            p.println("  mEnabledSession=" + mEnabledSession);
4086            p.println("  mShowRequested=" + mShowRequested
4087                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
4088                    + " mShowForced=" + mShowForced
4089                    + " mInputShown=" + mInputShown);
4090            p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
4091        }
4092
4093        p.println(" ");
4094        if (client != null) {
4095            pw.flush();
4096            try {
4097                client.client.asBinder().dump(fd, args);
4098            } catch (RemoteException e) {
4099                p.println("Input method client dead: " + e);
4100            }
4101        } else {
4102            p.println("No input method client.");
4103        }
4104
4105        p.println(" ");
4106        if (method != null) {
4107            pw.flush();
4108            try {
4109                method.asBinder().dump(fd, args);
4110            } catch (RemoteException e) {
4111                p.println("Input method service dead: " + e);
4112            }
4113        } else {
4114            p.println("No input method service.");
4115        }
4116    }
4117}
4118