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