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