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