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