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