InputMethodManagerService.java revision 2c93efc9eb188532472edc9e0c3e1ab8121aa20d
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        if (isScreenLocked()) return false;
1136        synchronized (mMethodMap) {
1137            List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
1138            final int N = imis.size();
1139            if (N > 2) return true;
1140            if (N < 1) return false;
1141            int nonAuxCount = 0;
1142            int auxCount = 0;
1143            InputMethodSubtype nonAuxSubtype = null;
1144            InputMethodSubtype auxSubtype = null;
1145            for(int i = 0; i < N; ++i) {
1146                final InputMethodInfo imi = imis.get(i);
1147                final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeListLocked(
1148                        imi, true);
1149                final int subtypeCount = subtypes.size();
1150                if (subtypeCount == 0) {
1151                    ++nonAuxCount;
1152                } else {
1153                    for (int j = 0; j < subtypeCount; ++j) {
1154                        final InputMethodSubtype subtype = subtypes.get(j);
1155                        if (!subtype.isAuxiliary()) {
1156                            ++nonAuxCount;
1157                            nonAuxSubtype = subtype;
1158                        } else {
1159                            ++auxCount;
1160                            auxSubtype = subtype;
1161                        }
1162                    }
1163                }
1164            }
1165            if (nonAuxCount > 1 || auxCount > 1) {
1166                return true;
1167            } else if (nonAuxCount == 1 && auxCount == 1) {
1168                if (nonAuxSubtype != null && auxSubtype != null
1169                        && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
1170                                || auxSubtype.overridesImplicitlyEnabledSubtype()
1171                                || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
1172                        && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
1173                    return false;
1174                }
1175                return true;
1176            }
1177            return false;
1178        }
1179    }
1180
1181    @SuppressWarnings("deprecation")
1182    @Override
1183    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
1184        int uid = Binder.getCallingUid();
1185        long ident = Binder.clearCallingIdentity();
1186        try {
1187            if (token == null || mCurToken != token) {
1188                Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
1189                return;
1190            }
1191
1192            synchronized (mMethodMap) {
1193                mImeWindowVis = vis;
1194                mBackDisposition = backDisposition;
1195                if (mStatusBar != null) {
1196                    mStatusBar.setImeWindowStatus(token, vis, backDisposition);
1197                }
1198                final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0;
1199                final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
1200                if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
1201                    final PackageManager pm = mContext.getPackageManager();
1202                    final CharSequence title = mRes.getText(
1203                            com.android.internal.R.string.select_input_method);
1204                    final CharSequence imiLabel = imi.loadLabel(pm);
1205                    final CharSequence summary = mCurrentSubtype != null
1206                            ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext,
1207                                        imi.getPackageName(), imi.getServiceInfo().applicationInfo),
1208                                                (TextUtils.isEmpty(imiLabel) ?
1209                                                        "" : " - " + imiLabel))
1210                            : imiLabel;
1211
1212                    mImeSwitcherNotification.setLatestEventInfo(
1213                            mContext, title, summary, mImeSwitchPendingIntent);
1214                    if (mNotificationManager != null) {
1215                        mNotificationManager.notify(
1216                                com.android.internal.R.string.select_input_method,
1217                                mImeSwitcherNotification);
1218                        mNotificationShown = true;
1219                    }
1220                } else {
1221                    if (mNotificationShown && mNotificationManager != null) {
1222                        mNotificationManager.cancel(
1223                                com.android.internal.R.string.select_input_method);
1224                        mNotificationShown = false;
1225                    }
1226                }
1227            }
1228        } finally {
1229            Binder.restoreCallingIdentity(ident);
1230        }
1231    }
1232
1233    @Override
1234    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
1235        synchronized (mMethodMap) {
1236            final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
1237            for (int i = 0; i < spans.length; ++i) {
1238                SuggestionSpan ss = spans[i];
1239                if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
1240                    mSecureSuggestionSpans.put(ss, currentImi);
1241                    final InputMethodInfo targetImi = mSecureSuggestionSpans.get(ss);
1242                }
1243            }
1244        }
1245    }
1246
1247    @Override
1248    public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
1249        synchronized (mMethodMap) {
1250            final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
1251            // TODO: Do not send the intent if the process of the targetImi is already dead.
1252            if (targetImi != null) {
1253                final String[] suggestions = span.getSuggestions();
1254                if (index < 0 || index >= suggestions.length) return false;
1255                final String className = span.getNotificationTargetClassName();
1256                final Intent intent = new Intent();
1257                // Ensures that only a class in the original IME package will receive the
1258                // notification.
1259                intent.setClassName(targetImi.getPackageName(), className);
1260                intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
1261                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
1262                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
1263                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
1264                mContext.sendBroadcast(intent);
1265                return true;
1266            }
1267        }
1268        return false;
1269    }
1270
1271    void updateFromSettingsLocked() {
1272        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
1273        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
1274        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
1275        // enabled.
1276        String id = Settings.Secure.getString(mContext.getContentResolver(),
1277                Settings.Secure.DEFAULT_INPUT_METHOD);
1278        // There is no input method selected, try to choose new applicable input method.
1279        if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
1280            id = Settings.Secure.getString(mContext.getContentResolver(),
1281                    Settings.Secure.DEFAULT_INPUT_METHOD);
1282        }
1283        if (!TextUtils.isEmpty(id)) {
1284            try {
1285                setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
1286            } catch (IllegalArgumentException e) {
1287                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
1288                mCurMethodId = null;
1289                unbindCurrentMethodLocked(true);
1290            }
1291            mShortcutInputMethodsAndSubtypes.clear();
1292        } else {
1293            // There is no longer an input method set, so stop any current one.
1294            mCurMethodId = null;
1295            unbindCurrentMethodLocked(true);
1296        }
1297    }
1298
1299    /* package */ void setInputMethodLocked(String id, int subtypeId) {
1300        InputMethodInfo info = mMethodMap.get(id);
1301        if (info == null) {
1302            throw new IllegalArgumentException("Unknown id: " + id);
1303        }
1304
1305        if (id.equals(mCurMethodId)) {
1306            InputMethodSubtype subtype = null;
1307            if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) {
1308                subtype = info.getSubtypeAt(subtypeId);
1309            }
1310            if (subtype != mCurrentSubtype) {
1311                synchronized (mMethodMap) {
1312                    if (subtype != null) {
1313                        setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
1314                    }
1315                    if (mCurMethod != null) {
1316                        try {
1317                            refreshImeWindowVisibilityLocked();
1318                            // If subtype is null, try to find the most applicable one from
1319                            // getCurrentInputMethodSubtype.
1320                            if (subtype == null) {
1321                                subtype = getCurrentInputMethodSubtype();
1322                            }
1323                            mCurMethod.changeInputMethodSubtype(subtype);
1324                        } catch (RemoteException e) {
1325                            return;
1326                        }
1327                    }
1328                }
1329            }
1330            return;
1331        }
1332
1333        final long ident = Binder.clearCallingIdentity();
1334        try {
1335            // Set a subtype to this input method.
1336            // subtypeId the name of a subtype which will be set.
1337            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
1338            // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
1339            // because mCurMethodId is stored as a history in
1340            // setSelectedInputMethodAndSubtypeLocked().
1341            mCurMethodId = id;
1342
1343            if (ActivityManagerNative.isSystemReady()) {
1344                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
1345                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1346                intent.putExtra("input_method_id", id);
1347                mContext.sendBroadcast(intent);
1348            }
1349            unbindCurrentClientLocked();
1350        } finally {
1351            Binder.restoreCallingIdentity(ident);
1352        }
1353    }
1354
1355    @Override
1356    public boolean showSoftInput(IInputMethodClient client, int flags,
1357            ResultReceiver resultReceiver) {
1358        int uid = Binder.getCallingUid();
1359        long ident = Binder.clearCallingIdentity();
1360        try {
1361            synchronized (mMethodMap) {
1362                if (mCurClient == null || client == null
1363                        || mCurClient.client.asBinder() != client.asBinder()) {
1364                    try {
1365                        // We need to check if this is the current client with
1366                        // focus in the window manager, to allow this call to
1367                        // be made before input is started in it.
1368                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1369                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
1370                            return false;
1371                        }
1372                    } catch (RemoteException e) {
1373                        return false;
1374                    }
1375                }
1376
1377                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
1378                return showCurrentInputLocked(flags, resultReceiver);
1379            }
1380        } finally {
1381            Binder.restoreCallingIdentity(ident);
1382        }
1383    }
1384
1385    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1386        mShowRequested = true;
1387        if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1388            mShowExplicitlyRequested = true;
1389        }
1390        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1391            mShowExplicitlyRequested = true;
1392            mShowForced = true;
1393        }
1394
1395        if (!mSystemReady) {
1396            return false;
1397        }
1398
1399        boolean res = false;
1400        if (mCurMethod != null) {
1401            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1402                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1403                    resultReceiver));
1404            mInputShown = true;
1405            if (mHaveConnection && !mVisibleBound) {
1406                mContext.bindService(mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
1407                mVisibleBound = true;
1408            }
1409            res = true;
1410        } else if (mHaveConnection && SystemClock.uptimeMillis()
1411                >= (mLastBindTime+TIME_TO_RECONNECT)) {
1412            // The client has asked to have the input method shown, but
1413            // we have been sitting here too long with a connection to the
1414            // service and no interface received, so let's disconnect/connect
1415            // to try to prod things along.
1416            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
1417                    SystemClock.uptimeMillis()-mLastBindTime,1);
1418            Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
1419            mContext.unbindService(this);
1420            mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE
1421                    | Context.BIND_NOT_VISIBLE);
1422        }
1423
1424        return res;
1425    }
1426
1427    @Override
1428    public boolean hideSoftInput(IInputMethodClient client, int flags,
1429            ResultReceiver resultReceiver) {
1430        int uid = Binder.getCallingUid();
1431        long ident = Binder.clearCallingIdentity();
1432        try {
1433            synchronized (mMethodMap) {
1434                if (mCurClient == null || client == null
1435                        || mCurClient.client.asBinder() != client.asBinder()) {
1436                    try {
1437                        // We need to check if this is the current client with
1438                        // focus in the window manager, to allow this call to
1439                        // be made before input is started in it.
1440                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1441                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
1442                                    + uid + ": " + client);
1443                            setImeWindowVisibilityStatusHiddenLocked();
1444                            return false;
1445                        }
1446                    } catch (RemoteException e) {
1447                        setImeWindowVisibilityStatusHiddenLocked();
1448                        return false;
1449                    }
1450                }
1451
1452                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
1453                return hideCurrentInputLocked(flags, resultReceiver);
1454            }
1455        } finally {
1456            Binder.restoreCallingIdentity(ident);
1457        }
1458    }
1459
1460    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1461        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1462                && (mShowExplicitlyRequested || mShowForced)) {
1463            if (DEBUG) Slog.v(TAG,
1464                    "Not hiding: explicit show not cancelled by non-explicit hide");
1465            return false;
1466        }
1467        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1468            if (DEBUG) Slog.v(TAG,
1469                    "Not hiding: forced show not cancelled by not-always hide");
1470            return false;
1471        }
1472        boolean res;
1473        if (mInputShown && mCurMethod != null) {
1474            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1475                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1476            res = true;
1477        } else {
1478            res = false;
1479        }
1480        if (mHaveConnection && mVisibleBound) {
1481            mContext.unbindService(mVisibleConnection);
1482            mVisibleBound = false;
1483        }
1484        mInputShown = false;
1485        mShowRequested = false;
1486        mShowExplicitlyRequested = false;
1487        mShowForced = false;
1488        return res;
1489    }
1490
1491    @Override
1492    public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
1493            int controlFlags, int softInputMode, int windowFlags,
1494            EditorInfo attribute, IInputContext inputContext) {
1495        InputBindResult res = null;
1496        long ident = Binder.clearCallingIdentity();
1497        try {
1498            synchronized (mMethodMap) {
1499                if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
1500                        + " controlFlags=#" + Integer.toHexString(controlFlags)
1501                        + " softInputMode=#" + Integer.toHexString(softInputMode)
1502                        + " windowFlags=#" + Integer.toHexString(windowFlags));
1503
1504                ClientState cs = mClients.get(client.asBinder());
1505                if (cs == null) {
1506                    throw new IllegalArgumentException("unknown client "
1507                            + client.asBinder());
1508                }
1509
1510                try {
1511                    if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
1512                        // Check with the window manager to make sure this client actually
1513                        // has a window with focus.  If not, reject.  This is thread safe
1514                        // because if the focus changes some time before or after, the
1515                        // next client receiving focus that has any interest in input will
1516                        // be calling through here after that change happens.
1517                        Slog.w(TAG, "Focus gain on non-focused client " + cs.client
1518                                + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
1519                        return null;
1520                    }
1521                } catch (RemoteException e) {
1522                }
1523
1524                if (mCurFocusedWindow == windowToken) {
1525                    Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
1526                    if (attribute != null) {
1527                        return startInputUncheckedLocked(cs, inputContext, attribute,
1528                                controlFlags);
1529                    }
1530                    return null;
1531                }
1532                mCurFocusedWindow = windowToken;
1533
1534                // Should we auto-show the IME even if the caller has not
1535                // specified what should be done with it?
1536                // We only do this automatically if the window can resize
1537                // to accommodate the IME (so what the user sees will give
1538                // them good context without input information being obscured
1539                // by the IME) or if running on a large screen where there
1540                // is more room for the target window + IME.
1541                final boolean doAutoShow =
1542                        (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1543                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1544                        || mRes.getConfiguration().isLayoutSizeAtLeast(
1545                                Configuration.SCREENLAYOUT_SIZE_LARGE);
1546                final boolean isTextEditor =
1547                        (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
1548
1549                // We want to start input before showing the IME, but after closing
1550                // it.  We want to do this after closing it to help the IME disappear
1551                // more quickly (not get stuck behind it initializing itself for the
1552                // new focused input, even if its window wants to hide the IME).
1553                boolean didStart = false;
1554
1555                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1556                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
1557                        if (!isTextEditor || !doAutoShow) {
1558                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1559                                // There is no focus view, and this window will
1560                                // be behind any soft input window, so hide the
1561                                // soft input window if it is shown.
1562                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
1563                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
1564                            }
1565                        } else if (isTextEditor && doAutoShow && (softInputMode &
1566                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1567                            // There is a focus view, and we are navigating forward
1568                            // into the window, so show the input window for the user.
1569                            // We only do this automatically if the window can resize
1570                            // to accommodate the IME (so what the user sees will give
1571                            // them good context without input information being obscured
1572                            // by the IME) or if running on a large screen where there
1573                            // is more room for the target window + IME.
1574                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
1575                            if (attribute != null) {
1576                                res = startInputUncheckedLocked(cs, inputContext, attribute,
1577                                        controlFlags);
1578                                didStart = true;
1579                            }
1580                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1581                        }
1582                        break;
1583                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1584                        // Do nothing.
1585                        break;
1586                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1587                        if ((softInputMode &
1588                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1589                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
1590                            hideCurrentInputLocked(0, null);
1591                        }
1592                        break;
1593                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
1594                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
1595                        hideCurrentInputLocked(0, null);
1596                        break;
1597                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1598                        if ((softInputMode &
1599                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1600                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
1601                            if (attribute != null) {
1602                                res = startInputUncheckedLocked(cs, inputContext, attribute,
1603                                        controlFlags);
1604                                didStart = true;
1605                            }
1606                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1607                        }
1608                        break;
1609                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
1610                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
1611                        if (attribute != null) {
1612                            res = startInputUncheckedLocked(cs, inputContext, attribute,
1613                                    controlFlags);
1614                            didStart = true;
1615                        }
1616                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1617                        break;
1618                }
1619
1620                if (!didStart && attribute != null) {
1621                    res = startInputUncheckedLocked(cs, inputContext, attribute,
1622                            controlFlags);
1623                }
1624            }
1625        } finally {
1626            Binder.restoreCallingIdentity(ident);
1627        }
1628
1629        return res;
1630    }
1631
1632    @Override
1633    public void showInputMethodPickerFromClient(IInputMethodClient client) {
1634        synchronized (mMethodMap) {
1635            if (mCurClient == null || client == null
1636                    || mCurClient.client.asBinder() != client.asBinder()) {
1637                Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
1638                        + Binder.getCallingUid() + ": " + client);
1639            }
1640
1641            // Always call subtype picker, because subtype picker is a superset of input method
1642            // picker.
1643            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
1644        }
1645    }
1646
1647    @Override
1648    public void setInputMethod(IBinder token, String id) {
1649        setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
1650    }
1651
1652    @Override
1653    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
1654        synchronized (mMethodMap) {
1655            if (subtype != null) {
1656                setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
1657                        mMethodMap.get(id), subtype.hashCode()));
1658            } else {
1659                setInputMethod(token, id);
1660            }
1661        }
1662    }
1663
1664    @Override
1665    public void showInputMethodAndSubtypeEnablerFromClient(
1666            IInputMethodClient client, String inputMethodId) {
1667        synchronized (mMethodMap) {
1668            if (mCurClient == null || client == null
1669                || mCurClient.client.asBinder() != client.asBinder()) {
1670                Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
1671            }
1672            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1673                    MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
1674        }
1675    }
1676
1677    @Override
1678    public boolean switchToLastInputMethod(IBinder token) {
1679        synchronized (mMethodMap) {
1680            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1681            final InputMethodInfo lastImi;
1682            if (lastIme != null) {
1683                lastImi = mMethodMap.get(lastIme.first);
1684            } else {
1685                lastImi = null;
1686            }
1687            String targetLastImiId = null;
1688            int subtypeId = NOT_A_SUBTYPE_ID;
1689            if (lastIme != null && lastImi != null) {
1690                final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
1691                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
1692                final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
1693                        : mCurrentSubtype.hashCode();
1694                // If the last IME is the same as the current IME and the last subtype is not
1695                // defined, there is no need to switch to the last IME.
1696                if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
1697                    targetLastImiId = lastIme.first;
1698                    subtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
1699                }
1700            }
1701
1702            if (TextUtils.isEmpty(targetLastImiId) && !canAddToLastInputMethod(mCurrentSubtype)) {
1703                // This is a safety net. If the currentSubtype can't be added to the history
1704                // and the framework couldn't find the last ime, we will make the last ime be
1705                // the most applicable enabled keyboard subtype of the system imes.
1706                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1707                if (enabled != null) {
1708                    final int N = enabled.size();
1709                    final String locale = mCurrentSubtype == null
1710                            ? mRes.getConfiguration().locale.toString()
1711                            : mCurrentSubtype.getLocale();
1712                    for (int i = 0; i < N; ++i) {
1713                        final InputMethodInfo imi = enabled.get(i);
1714                        if (imi.getSubtypeCount() > 0 && isSystemIme(imi)) {
1715                            InputMethodSubtype keyboardSubtype =
1716                                    findLastResortApplicableSubtypeLocked(mRes, getSubtypes(imi),
1717                                            SUBTYPE_MODE_KEYBOARD, locale, true);
1718                            if (keyboardSubtype != null) {
1719                                targetLastImiId = imi.getId();
1720                                subtypeId = getSubtypeIdFromHashCode(
1721                                        imi, keyboardSubtype.hashCode());
1722                                if(keyboardSubtype.getLocale().equals(locale)) {
1723                                    break;
1724                                }
1725                            }
1726                        }
1727                    }
1728                }
1729            }
1730
1731            if (!TextUtils.isEmpty(targetLastImiId)) {
1732                if (DEBUG) {
1733                    Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
1734                            + ", from: " + mCurMethodId + ", " + subtypeId);
1735                }
1736                setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId);
1737                return true;
1738            } else {
1739                return false;
1740            }
1741        }
1742    }
1743
1744    @Override
1745    public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
1746        synchronized (mMethodMap) {
1747            final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
1748                    onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
1749            if (nextSubtype == null) {
1750                return false;
1751            }
1752            setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
1753            return true;
1754        }
1755    }
1756
1757    @Override
1758    public InputMethodSubtype getLastInputMethodSubtype() {
1759        synchronized (mMethodMap) {
1760            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1761            // TODO: Handle the case of the last IME with no subtypes
1762            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
1763                    || TextUtils.isEmpty(lastIme.second)) return null;
1764            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
1765            if (lastImi == null) return null;
1766            try {
1767                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
1768                final int lastSubtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
1769                if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
1770                    return null;
1771                }
1772                return lastImi.getSubtypeAt(lastSubtypeId);
1773            } catch (NumberFormatException e) {
1774                return null;
1775            }
1776        }
1777    }
1778
1779    @Override
1780    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
1781        // By this IPC call, only a process which shares the same uid with the IME can add
1782        // additional input method subtypes to the IME.
1783        if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
1784        synchronized (mMethodMap) {
1785            final InputMethodInfo imi = mMethodMap.get(imiId);
1786            if (imi == null) return;
1787            final PackageManager pm = mContext.getPackageManager();
1788            final String[] packageInfos = pm.getPackagesForUid(Binder.getCallingUid());
1789            if (packageInfos != null) {
1790                final int packageNum = packageInfos.length;
1791                for (int i = 0; i < packageNum; ++i) {
1792                    if (packageInfos[i].equals(imi.getPackageName())) {
1793                        mFileManager.addInputMethodSubtypes(imi, subtypes);
1794                        final long ident = Binder.clearCallingIdentity();
1795                        try {
1796                            buildInputMethodListLocked(mMethodList, mMethodMap);
1797                        } finally {
1798                            Binder.restoreCallingIdentity(ident);
1799                        }
1800                        return;
1801                    }
1802                }
1803            }
1804        }
1805        return;
1806    }
1807
1808    private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
1809        synchronized (mMethodMap) {
1810            if (token == null) {
1811                if (mContext.checkCallingOrSelfPermission(
1812                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
1813                        != PackageManager.PERMISSION_GRANTED) {
1814                    throw new SecurityException(
1815                            "Using null token requires permission "
1816                            + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1817                }
1818            } else if (mCurToken != token) {
1819                Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
1820                        + " token: " + token);
1821                return;
1822            }
1823
1824            final long ident = Binder.clearCallingIdentity();
1825            try {
1826                setInputMethodLocked(id, subtypeId);
1827            } finally {
1828                Binder.restoreCallingIdentity(ident);
1829            }
1830        }
1831    }
1832
1833    @Override
1834    public void hideMySoftInput(IBinder token, int flags) {
1835        synchronized (mMethodMap) {
1836            if (token == null || mCurToken != token) {
1837                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
1838                        + Binder.getCallingUid() + " token: " + token);
1839                return;
1840            }
1841            long ident = Binder.clearCallingIdentity();
1842            try {
1843                hideCurrentInputLocked(flags, null);
1844            } finally {
1845                Binder.restoreCallingIdentity(ident);
1846            }
1847        }
1848    }
1849
1850    @Override
1851    public void showMySoftInput(IBinder token, int flags) {
1852        synchronized (mMethodMap) {
1853            if (token == null || mCurToken != token) {
1854                Slog.w(TAG, "Ignoring showMySoftInput of uid "
1855                        + Binder.getCallingUid() + " token: " + token);
1856                return;
1857            }
1858            long ident = Binder.clearCallingIdentity();
1859            try {
1860                showCurrentInputLocked(flags, null);
1861            } finally {
1862                Binder.restoreCallingIdentity(ident);
1863            }
1864        }
1865    }
1866
1867    void setEnabledSessionInMainThread(SessionState session) {
1868        if (mEnabledSession != session) {
1869            if (mEnabledSession != null) {
1870                try {
1871                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
1872                    mEnabledSession.method.setSessionEnabled(
1873                            mEnabledSession.session, false);
1874                } catch (RemoteException e) {
1875                }
1876            }
1877            mEnabledSession = session;
1878            try {
1879                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
1880                session.method.setSessionEnabled(
1881                        session.session, true);
1882            } catch (RemoteException e) {
1883            }
1884        }
1885    }
1886
1887    @Override
1888    public boolean handleMessage(Message msg) {
1889        HandlerCaller.SomeArgs args;
1890        switch (msg.what) {
1891            case MSG_SHOW_IM_PICKER:
1892                showInputMethodMenu();
1893                return true;
1894
1895            case MSG_SHOW_IM_SUBTYPE_PICKER:
1896                showInputMethodSubtypeMenu();
1897                return true;
1898
1899            case MSG_SHOW_IM_SUBTYPE_ENABLER:
1900                args = (HandlerCaller.SomeArgs)msg.obj;
1901                showInputMethodAndSubtypeEnabler((String)args.arg1);
1902                return true;
1903
1904            case MSG_SHOW_IM_CONFIG:
1905                showConfigureInputMethods();
1906                return true;
1907
1908            // ---------------------------------------------------------
1909
1910            case MSG_UNBIND_INPUT:
1911                try {
1912                    ((IInputMethod)msg.obj).unbindInput();
1913                } catch (RemoteException e) {
1914                    // There is nothing interesting about the method dying.
1915                }
1916                return true;
1917            case MSG_BIND_INPUT:
1918                args = (HandlerCaller.SomeArgs)msg.obj;
1919                try {
1920                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1921                } catch (RemoteException e) {
1922                }
1923                return true;
1924            case MSG_SHOW_SOFT_INPUT:
1925                args = (HandlerCaller.SomeArgs)msg.obj;
1926                try {
1927                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
1928                            (ResultReceiver)args.arg2);
1929                } catch (RemoteException e) {
1930                }
1931                return true;
1932            case MSG_HIDE_SOFT_INPUT:
1933                args = (HandlerCaller.SomeArgs)msg.obj;
1934                try {
1935                    ((IInputMethod)args.arg1).hideSoftInput(0,
1936                            (ResultReceiver)args.arg2);
1937                } catch (RemoteException e) {
1938                }
1939                return true;
1940            case MSG_ATTACH_TOKEN:
1941                args = (HandlerCaller.SomeArgs)msg.obj;
1942                try {
1943                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
1944                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1945                } catch (RemoteException e) {
1946                }
1947                return true;
1948            case MSG_CREATE_SESSION:
1949                args = (HandlerCaller.SomeArgs)msg.obj;
1950                try {
1951                    ((IInputMethod)args.arg1).createSession(
1952                            (IInputMethodCallback)args.arg2);
1953                } catch (RemoteException e) {
1954                }
1955                return true;
1956            // ---------------------------------------------------------
1957
1958            case MSG_START_INPUT:
1959                args = (HandlerCaller.SomeArgs)msg.obj;
1960                try {
1961                    SessionState session = (SessionState)args.arg1;
1962                    setEnabledSessionInMainThread(session);
1963                    session.method.startInput((IInputContext)args.arg2,
1964                            (EditorInfo)args.arg3);
1965                } catch (RemoteException e) {
1966                }
1967                return true;
1968            case MSG_RESTART_INPUT:
1969                args = (HandlerCaller.SomeArgs)msg.obj;
1970                try {
1971                    SessionState session = (SessionState)args.arg1;
1972                    setEnabledSessionInMainThread(session);
1973                    session.method.restartInput((IInputContext)args.arg2,
1974                            (EditorInfo)args.arg3);
1975                } catch (RemoteException e) {
1976                }
1977                return true;
1978
1979            // ---------------------------------------------------------
1980
1981            case MSG_UNBIND_METHOD:
1982                try {
1983                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1984                } catch (RemoteException e) {
1985                    // There is nothing interesting about the last client dying.
1986                }
1987                return true;
1988            case MSG_BIND_METHOD:
1989                args = (HandlerCaller.SomeArgs)msg.obj;
1990                try {
1991                    ((IInputMethodClient)args.arg1).onBindMethod(
1992                            (InputBindResult)args.arg2);
1993                } catch (RemoteException e) {
1994                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
1995                }
1996                return true;
1997        }
1998        return false;
1999    }
2000
2001    private boolean isSystemIme(InputMethodInfo inputMethod) {
2002        return (inputMethod.getServiceInfo().applicationInfo.flags
2003                & ApplicationInfo.FLAG_SYSTEM) != 0;
2004    }
2005
2006    private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
2007        ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2008        final int subtypeCount = imi.getSubtypeCount();
2009        for (int i = 0; i < subtypeCount; ++i) {
2010            subtypes.add(imi.getSubtypeAt(i));
2011        }
2012        return subtypes;
2013    }
2014
2015
2016    private static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
2017            InputMethodInfo imi, String mode) {
2018        ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2019        final int subtypeCount = imi.getSubtypeCount();
2020        for (int i = 0; i < subtypeCount; ++i) {
2021            final InputMethodSubtype subtype = imi.getSubtypeAt(i);
2022            if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
2023                subtypes.add(subtype);
2024            }
2025        }
2026        return subtypes;
2027    }
2028
2029    private InputMethodInfo getMostApplicableDefaultIMELocked() {
2030        List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2031        if (enabled != null && enabled.size() > 0) {
2032            // We'd prefer to fall back on a system IME, since that is safer.
2033            int i=enabled.size();
2034            while (i > 0) {
2035                i--;
2036                final InputMethodInfo imi = enabled.get(i);
2037                if (isSystemIme(imi) && !imi.isAuxiliaryIme()) {
2038                    break;
2039                }
2040            }
2041            return enabled.get(i);
2042        }
2043        return null;
2044    }
2045
2046    private boolean chooseNewDefaultIMELocked() {
2047        final InputMethodInfo imi = getMostApplicableDefaultIMELocked();
2048        if (imi != null) {
2049            if (DEBUG) {
2050                Slog.d(TAG, "New default IME was selected: " + imi.getId());
2051            }
2052            resetSelectedInputMethodAndSubtypeLocked(imi.getId());
2053            return true;
2054        }
2055
2056        return false;
2057    }
2058
2059    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
2060            HashMap<String, InputMethodInfo> map) {
2061        list.clear();
2062        map.clear();
2063
2064        PackageManager pm = mContext.getPackageManager();
2065        final Configuration config = mRes.getConfiguration();
2066        final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
2067        String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
2068                Secure.DISABLED_SYSTEM_INPUT_METHODS);
2069        if (disabledSysImes == null) disabledSysImes = "";
2070
2071        List<ResolveInfo> services = pm.queryIntentServices(
2072                new Intent(InputMethod.SERVICE_INTERFACE),
2073                PackageManager.GET_META_DATA);
2074
2075        final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
2076                mFileManager.getAllAdditionalInputMethodSubtypes();
2077        for (int i = 0; i < services.size(); ++i) {
2078            ResolveInfo ri = services.get(i);
2079            ServiceInfo si = ri.serviceInfo;
2080            ComponentName compName = new ComponentName(si.packageName, si.name);
2081            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
2082                    si.permission)) {
2083                Slog.w(TAG, "Skipping input method " + compName
2084                        + ": it does not require the permission "
2085                        + android.Manifest.permission.BIND_INPUT_METHOD);
2086                continue;
2087            }
2088
2089            if (DEBUG) Slog.d(TAG, "Checking " + compName);
2090
2091            try {
2092                InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
2093                list.add(p);
2094                final String id = p.getId();
2095                map.put(id, p);
2096
2097                // System IMEs are enabled by default, unless there's a hard keyboard
2098                // and the system IME was explicitly disabled
2099                if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
2100                    setInputMethodEnabledLocked(id, true);
2101                }
2102
2103                if (DEBUG) {
2104                    Slog.d(TAG, "Found a third-party input method " + p);
2105                }
2106
2107            } catch (XmlPullParserException e) {
2108                Slog.w(TAG, "Unable to load input method " + compName, e);
2109            } catch (IOException e) {
2110                Slog.w(TAG, "Unable to load input method " + compName, e);
2111            }
2112        }
2113
2114        String defaultIme = Settings.Secure.getString(mContext
2115                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2116        if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
2117            if (chooseNewDefaultIMELocked()) {
2118                updateFromSettingsLocked();
2119            }
2120        }
2121    }
2122
2123    // ----------------------------------------------------------------------
2124
2125    private void showInputMethodMenu() {
2126        showInputMethodMenuInternal(false);
2127    }
2128
2129    private void showInputMethodSubtypeMenu() {
2130        showInputMethodMenuInternal(true);
2131    }
2132
2133    private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
2134        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
2135        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2136                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2137                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2138        if (!TextUtils.isEmpty(inputMethodId)) {
2139            intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
2140        }
2141        mContext.startActivity(intent);
2142    }
2143
2144    private void showConfigureInputMethods() {
2145        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
2146        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2147                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2148                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2149        mContext.startActivity(intent);
2150    }
2151
2152    private boolean isScreenLocked() {
2153        return mKeyguardManager != null
2154                && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
2155    }
2156    private void showInputMethodMenuInternal(boolean showSubtypes) {
2157        if (DEBUG) Slog.v(TAG, "Show switching menu");
2158
2159        final Context context = mContext;
2160        final PackageManager pm = context.getPackageManager();
2161        final boolean isScreenLocked = isScreenLocked();
2162
2163        final String lastInputMethodId = Settings.Secure.getString(context
2164                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2165        int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2166        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
2167
2168        synchronized (mMethodMap) {
2169            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
2170                    getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
2171            if (immis == null || immis.size() == 0) {
2172                return;
2173            }
2174
2175            hideInputMethodMenuLocked();
2176
2177            final List<ImeSubtypeListItem> imList =
2178                    mImListManager.getSortedInputMethodAndSubtypeList(
2179                            showSubtypes, mInputShown, isScreenLocked);
2180
2181            if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
2182                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtype();
2183                if (currentSubtype != null) {
2184                    final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
2185                    lastInputMethodSubtypeId =
2186                            getSubtypeIdFromHashCode(currentImi, currentSubtype.hashCode());
2187                }
2188            }
2189
2190            final int N = imList.size();
2191            mIms = new InputMethodInfo[N];
2192            mSubtypeIds = new int[N];
2193            int checkedItem = 0;
2194            for (int i = 0; i < N; ++i) {
2195                final ImeSubtypeListItem item = imList.get(i);
2196                mIms[i] = item.mImi;
2197                mSubtypeIds[i] = item.mSubtypeId;
2198                if (mIms[i].getId().equals(lastInputMethodId)) {
2199                    int subtypeId = mSubtypeIds[i];
2200                    if ((subtypeId == NOT_A_SUBTYPE_ID)
2201                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
2202                            || (subtypeId == lastInputMethodSubtypeId)) {
2203                        checkedItem = i;
2204                    }
2205                }
2206            }
2207
2208            final TypedArray a = context.obtainStyledAttributes(null,
2209                    com.android.internal.R.styleable.DialogPreference,
2210                    com.android.internal.R.attr.alertDialogStyle, 0);
2211            mDialogBuilder = new AlertDialog.Builder(context)
2212                    .setTitle(com.android.internal.R.string.select_input_method)
2213                    .setOnCancelListener(new OnCancelListener() {
2214                        @Override
2215                        public void onCancel(DialogInterface dialog) {
2216                            hideInputMethodMenu();
2217                        }
2218                    })
2219                    .setIcon(a.getDrawable(
2220                            com.android.internal.R.styleable.DialogPreference_dialogTitle));
2221            a.recycle();
2222
2223            final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context,
2224                    com.android.internal.R.layout.simple_list_item_2_single_choice, imList,
2225                    checkedItem);
2226
2227            mDialogBuilder.setSingleChoiceItems(adapter, checkedItem,
2228                    new AlertDialog.OnClickListener() {
2229                        @Override
2230                        public void onClick(DialogInterface dialog, int which) {
2231                            synchronized (mMethodMap) {
2232                                if (mIms == null || mIms.length <= which
2233                                        || mSubtypeIds == null || mSubtypeIds.length <= which) {
2234                                    return;
2235                                }
2236                                InputMethodInfo im = mIms[which];
2237                                int subtypeId = mSubtypeIds[which];
2238                                hideInputMethodMenu();
2239                                if (im != null) {
2240                                    if ((subtypeId < 0)
2241                                            || (subtypeId >= im.getSubtypeCount())) {
2242                                        subtypeId = NOT_A_SUBTYPE_ID;
2243                                    }
2244                                    setInputMethodLocked(im.getId(), subtypeId);
2245                                }
2246                            }
2247                        }
2248                    });
2249
2250            if (showSubtypes && !isScreenLocked) {
2251                mDialogBuilder.setPositiveButton(
2252                        com.android.internal.R.string.configure_input_methods,
2253                        new DialogInterface.OnClickListener() {
2254                            @Override
2255                            public void onClick(DialogInterface dialog, int whichButton) {
2256                                showConfigureInputMethods();
2257                            }
2258                        });
2259            }
2260            mSwitchingDialog = mDialogBuilder.create();
2261            mSwitchingDialog.setCanceledOnTouchOutside(true);
2262            mSwitchingDialog.getWindow().setType(
2263                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
2264            mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
2265            mSwitchingDialog.show();
2266        }
2267    }
2268
2269    private static class ImeSubtypeListItem {
2270        public final CharSequence mImeName;
2271        public final CharSequence mSubtypeName;
2272        public final InputMethodInfo mImi;
2273        public final int mSubtypeId;
2274        public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
2275                InputMethodInfo imi, int subtypeId) {
2276            mImeName = imeName;
2277            mSubtypeName = subtypeName;
2278            mImi = imi;
2279            mSubtypeId = subtypeId;
2280        }
2281    }
2282
2283    private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
2284        private final LayoutInflater mInflater;
2285        private final int mTextViewResourceId;
2286        private final List<ImeSubtypeListItem> mItemsList;
2287        private final int mCheckedItem;
2288        public ImeSubtypeListAdapter(Context context, int textViewResourceId,
2289                List<ImeSubtypeListItem> itemsList, int checkedItem) {
2290            super(context, textViewResourceId, itemsList);
2291            mTextViewResourceId = textViewResourceId;
2292            mItemsList = itemsList;
2293            mCheckedItem = checkedItem;
2294            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2295        }
2296
2297        @Override
2298        public View getView(int position, View convertView, ViewGroup parent) {
2299            final View view = convertView != null ? convertView
2300                    : mInflater.inflate(mTextViewResourceId, null);
2301            if (position < 0 || position >= mItemsList.size()) return view;
2302            final ImeSubtypeListItem item = mItemsList.get(position);
2303            final CharSequence imeName = item.mImeName;
2304            final CharSequence subtypeName = item.mSubtypeName;
2305            final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
2306            final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
2307            if (TextUtils.isEmpty(subtypeName)) {
2308                firstTextView.setText(imeName);
2309                secondTextView.setVisibility(View.GONE);
2310            } else {
2311                firstTextView.setText(subtypeName);
2312                secondTextView.setText(imeName);
2313                secondTextView.setVisibility(View.VISIBLE);
2314            }
2315            final RadioButton radioButton =
2316                    (RadioButton)view.findViewById(com.android.internal.R.id.radio);
2317            radioButton.setChecked(position == mCheckedItem);
2318            return view;
2319        }
2320    }
2321
2322    void hideInputMethodMenu() {
2323        synchronized (mMethodMap) {
2324            hideInputMethodMenuLocked();
2325        }
2326    }
2327
2328    void hideInputMethodMenuLocked() {
2329        if (DEBUG) Slog.v(TAG, "Hide switching menu");
2330
2331        if (mSwitchingDialog != null) {
2332            mSwitchingDialog.dismiss();
2333            mSwitchingDialog = null;
2334        }
2335
2336        mDialogBuilder = null;
2337        mIms = null;
2338    }
2339
2340    // ----------------------------------------------------------------------
2341
2342    @Override
2343    public boolean setInputMethodEnabled(String id, boolean enabled) {
2344        synchronized (mMethodMap) {
2345            if (mContext.checkCallingOrSelfPermission(
2346                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
2347                    != PackageManager.PERMISSION_GRANTED) {
2348                throw new SecurityException(
2349                        "Requires permission "
2350                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2351            }
2352
2353            long ident = Binder.clearCallingIdentity();
2354            try {
2355                return setInputMethodEnabledLocked(id, enabled);
2356            } finally {
2357                Binder.restoreCallingIdentity(ident);
2358            }
2359        }
2360    }
2361
2362    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
2363        // Make sure this is a valid input method.
2364        InputMethodInfo imm = mMethodMap.get(id);
2365        if (imm == null) {
2366            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
2367        }
2368
2369        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
2370                .getEnabledInputMethodsAndSubtypeListLocked();
2371
2372        if (enabled) {
2373            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
2374                if (pair.first.equals(id)) {
2375                    // We are enabling this input method, but it is already enabled.
2376                    // Nothing to do. The previous state was enabled.
2377                    return true;
2378                }
2379            }
2380            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
2381            // Previous state was disabled.
2382            return false;
2383        } else {
2384            StringBuilder builder = new StringBuilder();
2385            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2386                    builder, enabledInputMethodsList, id)) {
2387                // Disabled input method is currently selected, switch to another one.
2388                String selId = Settings.Secure.getString(mContext.getContentResolver(),
2389                        Settings.Secure.DEFAULT_INPUT_METHOD);
2390                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
2391                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
2392                    resetSelectedInputMethodAndSubtypeLocked("");
2393                }
2394                // Previous state was enabled.
2395                return true;
2396            } else {
2397                // We are disabling the input method but it is already disabled.
2398                // Nothing to do.  The previous state was disabled.
2399                return false;
2400            }
2401        }
2402    }
2403
2404    private boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
2405        if (subtype == null) return true;
2406        return !subtype.isAuxiliary();
2407    }
2408
2409    private void saveCurrentInputMethodAndSubtypeToHistory() {
2410        String subtypeId = NOT_A_SUBTYPE_ID_STR;
2411        if (mCurrentSubtype != null) {
2412            subtypeId = String.valueOf(mCurrentSubtype.hashCode());
2413        }
2414        if (canAddToLastInputMethod(mCurrentSubtype)) {
2415            mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
2416        }
2417    }
2418
2419    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
2420            boolean setSubtypeOnly) {
2421        // Update the history of InputMethod and Subtype
2422        saveCurrentInputMethodAndSubtypeToHistory();
2423
2424        // Set Subtype here
2425        if (imi == null || subtypeId < 0) {
2426            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2427            mCurrentSubtype = null;
2428        } else {
2429            if (subtypeId < imi.getSubtypeCount()) {
2430                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
2431                mSettings.putSelectedSubtype(subtype.hashCode());
2432                mCurrentSubtype = subtype;
2433            } else {
2434                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2435                mCurrentSubtype = null;
2436            }
2437        }
2438
2439        if (!setSubtypeOnly) {
2440            // Set InputMethod here
2441            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
2442        }
2443    }
2444
2445    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
2446        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
2447        int lastSubtypeId = NOT_A_SUBTYPE_ID;
2448        // newDefaultIme is empty when there is no candidate for the selected IME.
2449        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
2450            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
2451            if (subtypeHashCode != null) {
2452                try {
2453                    lastSubtypeId = getSubtypeIdFromHashCode(
2454                            imi, Integer.valueOf(subtypeHashCode));
2455                } catch (NumberFormatException e) {
2456                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
2457                }
2458            }
2459        }
2460        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
2461    }
2462
2463    private int getSelectedInputMethodSubtypeId(String id) {
2464        InputMethodInfo imi = mMethodMap.get(id);
2465        if (imi == null) {
2466            return NOT_A_SUBTYPE_ID;
2467        }
2468        int subtypeId;
2469        try {
2470            subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
2471                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
2472        } catch (SettingNotFoundException e) {
2473            return NOT_A_SUBTYPE_ID;
2474        }
2475        return getSubtypeIdFromHashCode(imi, subtypeId);
2476    }
2477
2478    private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
2479        if (imi != null) {
2480            final int subtypeCount = imi.getSubtypeCount();
2481            for (int i = 0; i < subtypeCount; ++i) {
2482                InputMethodSubtype ims = imi.getSubtypeAt(i);
2483                if (subtypeHashCode == ims.hashCode()) {
2484                    return i;
2485                }
2486            }
2487        }
2488        return NOT_A_SUBTYPE_ID;
2489    }
2490
2491    private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
2492            Resources res, InputMethodInfo imi) {
2493        final List<InputMethodSubtype> subtypes = getSubtypes(imi);
2494        final String systemLocale = res.getConfiguration().locale.toString();
2495        if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
2496        final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
2497                new HashMap<String, InputMethodSubtype>();
2498        final int N = subtypes.size();
2499        for (int i = 0; i < N; ++i) {
2500            // scan overriding implicitly enabled subtypes.
2501            InputMethodSubtype subtype = subtypes.get(i);
2502            if (subtype.overridesImplicitlyEnabledSubtype()) {
2503                final String mode = subtype.getMode();
2504                if (!applicableModeAndSubtypesMap.containsKey(mode)) {
2505                    applicableModeAndSubtypesMap.put(mode, subtype);
2506                }
2507            }
2508        }
2509        if (applicableModeAndSubtypesMap.size() > 0) {
2510            return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
2511        }
2512        for (int i = 0; i < N; ++i) {
2513            final InputMethodSubtype subtype = subtypes.get(i);
2514            final String locale = subtype.getLocale();
2515            final String mode = subtype.getMode();
2516            // When system locale starts with subtype's locale, that subtype will be applicable
2517            // for system locale
2518            // For instance, it's clearly applicable for cases like system locale = en_US and
2519            // subtype = en, but it is not necessarily considered applicable for cases like system
2520            // locale = en and subtype = en_US.
2521            // We just call systemLocale.startsWith(locale) in this function because there is no
2522            // need to find applicable subtypes aggressively unlike
2523            // findLastResortApplicableSubtypeLocked.
2524            if (systemLocale.startsWith(locale)) {
2525                final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
2526                // If more applicable subtypes are contained, skip.
2527                if (applicableSubtype != null) {
2528                    if (systemLocale.equals(applicableSubtype.getLocale())) continue;
2529                    if (!systemLocale.equals(locale)) continue;
2530                }
2531                applicableModeAndSubtypesMap.put(mode, subtype);
2532            }
2533        }
2534        final InputMethodSubtype keyboardSubtype
2535                = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
2536        final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
2537                applicableModeAndSubtypesMap.values());
2538        if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
2539            for (int i = 0; i < N; ++i) {
2540                final InputMethodSubtype subtype = subtypes.get(i);
2541                final String mode = subtype.getMode();
2542                if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
2543                        TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
2544                    applicableSubtypes.add(subtype);
2545                }
2546            }
2547        }
2548        if (keyboardSubtype == null) {
2549            InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
2550                    res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
2551            if (lastResortKeyboardSubtype != null) {
2552                applicableSubtypes.add(lastResortKeyboardSubtype);
2553            }
2554        }
2555        return applicableSubtypes;
2556    }
2557
2558    /**
2559     * If there are no selected subtypes, tries finding the most applicable one according to the
2560     * given locale.
2561     * @param subtypes this function will search the most applicable subtype in subtypes
2562     * @param mode subtypes will be filtered by mode
2563     * @param locale subtypes will be filtered by locale
2564     * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
2565     * it will return the first subtype matched with mode
2566     * @return the most applicable subtypeId
2567     */
2568    private static InputMethodSubtype findLastResortApplicableSubtypeLocked(
2569            Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
2570            boolean canIgnoreLocaleAsLastResort) {
2571        if (subtypes == null || subtypes.size() == 0) {
2572            return null;
2573        }
2574        if (TextUtils.isEmpty(locale)) {
2575            locale = res.getConfiguration().locale.toString();
2576        }
2577        final String language = locale.substring(0, 2);
2578        boolean partialMatchFound = false;
2579        InputMethodSubtype applicableSubtype = null;
2580        InputMethodSubtype firstMatchedModeSubtype = null;
2581        final int N = subtypes.size();
2582        for (int i = 0; i < N; ++i) {
2583            InputMethodSubtype subtype = subtypes.get(i);
2584            final String subtypeLocale = subtype.getLocale();
2585            // An applicable subtype should match "mode". If mode is null, mode will be ignored,
2586            // and all subtypes with all modes can be candidates.
2587            if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
2588                if (firstMatchedModeSubtype == null) {
2589                    firstMatchedModeSubtype = subtype;
2590                }
2591                if (locale.equals(subtypeLocale)) {
2592                    // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
2593                    applicableSubtype = subtype;
2594                    break;
2595                } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
2596                    // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
2597                    applicableSubtype = subtype;
2598                    partialMatchFound = true;
2599                }
2600            }
2601        }
2602
2603        if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
2604            return firstMatchedModeSubtype;
2605        }
2606
2607        // The first subtype applicable to the system locale will be defined as the most applicable
2608        // subtype.
2609        if (DEBUG) {
2610            if (applicableSubtype != null) {
2611                Slog.d(TAG, "Applicable InputMethodSubtype was found: "
2612                        + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
2613            }
2614        }
2615        return applicableSubtype;
2616    }
2617
2618    // If there are no selected shortcuts, tries finding the most applicable ones.
2619    private Pair<InputMethodInfo, InputMethodSubtype>
2620            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
2621        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
2622        InputMethodInfo mostApplicableIMI = null;
2623        InputMethodSubtype mostApplicableSubtype = null;
2624        boolean foundInSystemIME = false;
2625
2626        // Search applicable subtype for each InputMethodInfo
2627        for (InputMethodInfo imi: imis) {
2628            final String imiId = imi.getId();
2629            if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
2630                continue;
2631            }
2632            InputMethodSubtype subtype = null;
2633            final List<InputMethodSubtype> enabledSubtypes =
2634                    getEnabledInputMethodSubtypeList(imi, true);
2635            // 1. Search by the current subtype's locale from enabledSubtypes.
2636            if (mCurrentSubtype != null) {
2637                subtype = findLastResortApplicableSubtypeLocked(
2638                        mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
2639            }
2640            // 2. Search by the system locale from enabledSubtypes.
2641            // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
2642            if (subtype == null) {
2643                subtype = findLastResortApplicableSubtypeLocked(
2644                        mRes, enabledSubtypes, mode, null, true);
2645            }
2646            final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
2647                    getOverridingImplicitlyEnabledSubtypes(imi, mode);
2648            final ArrayList<InputMethodSubtype> subtypesForSearch =
2649                    overridingImplicitlyEnabledSubtypes.isEmpty()
2650                            ? getSubtypes(imi) : overridingImplicitlyEnabledSubtypes;
2651            // 4. Search by the current subtype's locale from all subtypes.
2652            if (subtype == null && mCurrentSubtype != null) {
2653                subtype = findLastResortApplicableSubtypeLocked(
2654                        mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
2655            }
2656            // 5. Search by the system locale from all subtypes.
2657            // 6. Search the first enabled subtype matched with mode from all subtypes.
2658            if (subtype == null) {
2659                subtype = findLastResortApplicableSubtypeLocked(
2660                        mRes, subtypesForSearch, mode, null, true);
2661            }
2662            if (subtype != null) {
2663                if (imiId.equals(mCurMethodId)) {
2664                    // The current input method is the most applicable IME.
2665                    mostApplicableIMI = imi;
2666                    mostApplicableSubtype = subtype;
2667                    break;
2668                } else if (!foundInSystemIME) {
2669                    // The system input method is 2nd applicable IME.
2670                    mostApplicableIMI = imi;
2671                    mostApplicableSubtype = subtype;
2672                    if ((imi.getServiceInfo().applicationInfo.flags
2673                            & ApplicationInfo.FLAG_SYSTEM) != 0) {
2674                        foundInSystemIME = true;
2675                    }
2676                }
2677            }
2678        }
2679        if (DEBUG) {
2680            if (mostApplicableIMI != null) {
2681                Slog.w(TAG, "Most applicable shortcut input method was:"
2682                        + mostApplicableIMI.getId());
2683                if (mostApplicableSubtype != null) {
2684                    Slog.w(TAG, "Most applicable shortcut input method subtype was:"
2685                            + "," + mostApplicableSubtype.getMode() + ","
2686                            + mostApplicableSubtype.getLocale());
2687                }
2688            }
2689        }
2690        if (mostApplicableIMI != null) {
2691            return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
2692                    mostApplicableSubtype);
2693        } else {
2694            return null;
2695        }
2696    }
2697
2698    /**
2699     * @return Return the current subtype of this input method.
2700     */
2701    @Override
2702    public InputMethodSubtype getCurrentInputMethodSubtype() {
2703        boolean subtypeIsSelected = false;
2704        try {
2705            subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
2706                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
2707        } catch (SettingNotFoundException e) {
2708        }
2709        synchronized (mMethodMap) {
2710            if (!subtypeIsSelected || mCurrentSubtype == null) {
2711                String lastInputMethodId = Settings.Secure.getString(
2712                        mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2713                int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2714                if (subtypeId == NOT_A_SUBTYPE_ID) {
2715                    InputMethodInfo imi = mMethodMap.get(lastInputMethodId);
2716                    if (imi != null) {
2717                        // If there are no selected subtypes, the framework will try to find
2718                        // the most applicable subtype from explicitly or implicitly enabled
2719                        // subtypes.
2720                        List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
2721                                getEnabledInputMethodSubtypeList(imi, true);
2722                        // If there is only one explicitly or implicitly enabled subtype,
2723                        // just returns it.
2724                        if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
2725                            mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
2726                        } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
2727                            mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2728                                    mRes, explicitlyOrImplicitlyEnabledSubtypes,
2729                                    SUBTYPE_MODE_KEYBOARD, null, true);
2730                            if (mCurrentSubtype == null) {
2731                                mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2732                                        mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
2733                                        true);
2734                            }
2735                        }
2736                    }
2737                } else {
2738                    mCurrentSubtype =
2739                            getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId);
2740                }
2741            }
2742            return mCurrentSubtype;
2743        }
2744    }
2745
2746    private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
2747            InputMethodSubtype subtype) {
2748        if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
2749            mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
2750        } else {
2751            ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2752            subtypes.add(subtype);
2753            mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
2754        }
2755    }
2756
2757    // TODO: We should change the return type from List to List<Parcelable>
2758    @SuppressWarnings("rawtypes")
2759    @Override
2760    public List getShortcutInputMethodsAndSubtypes() {
2761        synchronized (mMethodMap) {
2762            ArrayList<Object> ret = new ArrayList<Object>();
2763            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
2764                // If there are no selected shortcut subtypes, the framework will try to find
2765                // the most applicable subtype from all subtypes whose mode is
2766                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
2767                Pair<InputMethodInfo, InputMethodSubtype> info =
2768                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
2769                            SUBTYPE_MODE_VOICE);
2770                if (info != null) {
2771                    ret.add(info.first);
2772                    ret.add(info.second);
2773                }
2774                return ret;
2775            }
2776            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
2777                ret.add(imi);
2778                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
2779                    ret.add(subtype);
2780                }
2781            }
2782            return ret;
2783        }
2784    }
2785
2786    @Override
2787    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
2788        synchronized (mMethodMap) {
2789            if (subtype != null && mCurMethodId != null) {
2790                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2791                int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
2792                if (subtypeId != NOT_A_SUBTYPE_ID) {
2793                    setInputMethodLocked(mCurMethodId, subtypeId);
2794                    return true;
2795                }
2796            }
2797            return false;
2798        }
2799    }
2800
2801    private static class InputMethodAndSubtypeListManager {
2802        private final Context mContext;
2803        private final PackageManager mPm;
2804        private final InputMethodManagerService mImms;
2805        public InputMethodAndSubtypeListManager(Context context, InputMethodManagerService imms) {
2806            mContext = context;
2807            mPm = context.getPackageManager();
2808            mImms = imms;
2809        }
2810
2811        private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
2812                new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
2813                        new Comparator<InputMethodInfo>() {
2814                            @Override
2815                            public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
2816                                if (imi2 == null) return 0;
2817                                if (imi1 == null) return 1;
2818                                if (mPm == null) {
2819                                    return imi1.getId().compareTo(imi2.getId());
2820                                }
2821                                CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId();
2822                                CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId();
2823                                return imiId1.toString().compareTo(imiId2.toString());
2824                            }
2825                        });
2826
2827        public ImeSubtypeListItem getNextInputMethod(
2828                boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
2829            if (imi == null) {
2830                return null;
2831            }
2832            final List<ImeSubtypeListItem> imList = getSortedInputMethodAndSubtypeList();
2833            if (imList.size() <= 1) {
2834                return null;
2835            }
2836            final int N = imList.size();
2837            final int currentSubtypeId = subtype != null
2838                    ? mImms.getSubtypeIdFromHashCode(imi, subtype.hashCode())
2839                    : NOT_A_SUBTYPE_ID;
2840            for (int i = 0; i < N; ++i) {
2841                final ImeSubtypeListItem isli = imList.get(i);
2842                if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) {
2843                    if (!onlyCurrentIme) {
2844                        return imList.get((i + 1) % N);
2845                    }
2846                    for (int j = 0; j < N - 1; ++j) {
2847                        final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N);
2848                        if (candidate.mImi.equals(imi)) {
2849                            return candidate;
2850                        }
2851                    }
2852                    return null;
2853                }
2854            }
2855            return null;
2856        }
2857
2858        public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() {
2859            return getSortedInputMethodAndSubtypeList(true, false, false);
2860        }
2861
2862        public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes,
2863                boolean inputShown, boolean isScreenLocked) {
2864            final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>();
2865            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
2866                    mImms.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
2867            if (immis == null || immis.size() == 0) {
2868                return Collections.emptyList();
2869            }
2870            mSortedImmis.clear();
2871            mSortedImmis.putAll(immis);
2872            for (InputMethodInfo imi : mSortedImmis.keySet()) {
2873                if (imi == null) continue;
2874                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
2875                HashSet<String> enabledSubtypeSet = new HashSet<String>();
2876                for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
2877                    enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
2878                }
2879                ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi);
2880                final CharSequence imeLabel = imi.loadLabel(mPm);
2881                if (showSubtypes && enabledSubtypeSet.size() > 0) {
2882                    final int subtypeCount = imi.getSubtypeCount();
2883                    if (DEBUG) {
2884                        Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
2885                    }
2886                    for (int j = 0; j < subtypeCount; ++j) {
2887                        final InputMethodSubtype subtype = imi.getSubtypeAt(j);
2888                        final String subtypeHashCode = String.valueOf(subtype.hashCode());
2889                        // We show all enabled IMEs and subtypes when an IME is shown.
2890                        if (enabledSubtypeSet.contains(subtypeHashCode)
2891                                && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) {
2892                            final CharSequence subtypeLabel =
2893                                    subtype.overridesImplicitlyEnabledSubtype() ? null
2894                                            : subtype.getDisplayName(mContext, imi.getPackageName(),
2895                                                    imi.getServiceInfo().applicationInfo);
2896                            imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j));
2897
2898                            // Removing this subtype from enabledSubtypeSet because we no longer
2899                            // need to add an entry of this subtype to imList to avoid duplicated
2900                            // entries.
2901                            enabledSubtypeSet.remove(subtypeHashCode);
2902                        }
2903                    }
2904                } else {
2905                    imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID));
2906                }
2907            }
2908            return imList;
2909        }
2910    }
2911
2912    /**
2913     * Utility class for putting and getting settings for InputMethod
2914     * TODO: Move all putters and getters of settings to this class.
2915     */
2916    private static class InputMethodSettings {
2917        // The string for enabled input method is saved as follows:
2918        // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
2919        private static final char INPUT_METHOD_SEPARATER = ':';
2920        private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
2921        private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
2922                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
2923
2924        private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
2925                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
2926
2927        private final Resources mRes;
2928        private final ContentResolver mResolver;
2929        private final HashMap<String, InputMethodInfo> mMethodMap;
2930        private final ArrayList<InputMethodInfo> mMethodList;
2931
2932        private String mEnabledInputMethodsStrCache;
2933
2934        private static void buildEnabledInputMethodsSettingString(
2935                StringBuilder builder, Pair<String, ArrayList<String>> pair) {
2936            String id = pair.first;
2937            ArrayList<String> subtypes = pair.second;
2938            builder.append(id);
2939            // Inputmethod and subtypes are saved in the settings as follows:
2940            // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
2941            for (String subtypeId: subtypes) {
2942                builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
2943            }
2944        }
2945
2946        public InputMethodSettings(
2947                Resources res, ContentResolver resolver,
2948                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
2949            mRes = res;
2950            mResolver = resolver;
2951            mMethodMap = methodMap;
2952            mMethodList = methodList;
2953        }
2954
2955        public List<InputMethodInfo> getEnabledInputMethodListLocked() {
2956            return createEnabledInputMethodListLocked(
2957                    getEnabledInputMethodsAndSubtypeListLocked());
2958        }
2959
2960        public List<Pair<InputMethodInfo, ArrayList<String>>>
2961                getEnabledInputMethodAndSubtypeHashCodeListLocked() {
2962            return createEnabledInputMethodAndSubtypeHashCodeListLocked(
2963                    getEnabledInputMethodsAndSubtypeListLocked());
2964        }
2965
2966        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
2967                InputMethodInfo imi) {
2968            List<Pair<String, ArrayList<String>>> imsList =
2969                    getEnabledInputMethodsAndSubtypeListLocked();
2970            ArrayList<InputMethodSubtype> enabledSubtypes =
2971                    new ArrayList<InputMethodSubtype>();
2972            if (imi != null) {
2973                for (Pair<String, ArrayList<String>> imsPair : imsList) {
2974                    InputMethodInfo info = mMethodMap.get(imsPair.first);
2975                    if (info != null && info.getId().equals(imi.getId())) {
2976                        final int subtypeCount = info.getSubtypeCount();
2977                        for (int i = 0; i < subtypeCount; ++i) {
2978                            InputMethodSubtype ims = info.getSubtypeAt(i);
2979                            for (String s: imsPair.second) {
2980                                if (String.valueOf(ims.hashCode()).equals(s)) {
2981                                    enabledSubtypes.add(ims);
2982                                }
2983                            }
2984                        }
2985                        break;
2986                    }
2987                }
2988            }
2989            return enabledSubtypes;
2990        }
2991
2992        // At the initial boot, the settings for input methods are not set,
2993        // so we need to enable IME in that case.
2994        public void enableAllIMEsIfThereIsNoEnabledIME() {
2995            if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
2996                StringBuilder sb = new StringBuilder();
2997                final int N = mMethodList.size();
2998                for (int i = 0; i < N; i++) {
2999                    InputMethodInfo imi = mMethodList.get(i);
3000                    Slog.i(TAG, "Adding: " + imi.getId());
3001                    if (i > 0) sb.append(':');
3002                    sb.append(imi.getId());
3003                }
3004                putEnabledInputMethodsStr(sb.toString());
3005            }
3006        }
3007
3008        private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
3009            ArrayList<Pair<String, ArrayList<String>>> imsList
3010                    = new ArrayList<Pair<String, ArrayList<String>>>();
3011            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
3012            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
3013                return imsList;
3014            }
3015            mInputMethodSplitter.setString(enabledInputMethodsStr);
3016            while (mInputMethodSplitter.hasNext()) {
3017                String nextImsStr = mInputMethodSplitter.next();
3018                mSubtypeSplitter.setString(nextImsStr);
3019                if (mSubtypeSplitter.hasNext()) {
3020                    ArrayList<String> subtypeHashes = new ArrayList<String>();
3021                    // The first element is ime id.
3022                    String imeId = mSubtypeSplitter.next();
3023                    while (mSubtypeSplitter.hasNext()) {
3024                        subtypeHashes.add(mSubtypeSplitter.next());
3025                    }
3026                    imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
3027                }
3028            }
3029            return imsList;
3030        }
3031
3032        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
3033            if (reloadInputMethodStr) {
3034                getEnabledInputMethodsStr();
3035            }
3036            if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
3037                // Add in the newly enabled input method.
3038                putEnabledInputMethodsStr(id);
3039            } else {
3040                putEnabledInputMethodsStr(
3041                        mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
3042            }
3043        }
3044
3045        /**
3046         * Build and put a string of EnabledInputMethods with removing specified Id.
3047         * @return the specified id was removed or not.
3048         */
3049        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
3050                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
3051            boolean isRemoved = false;
3052            boolean needsAppendSeparator = false;
3053            for (Pair<String, ArrayList<String>> ims: imsList) {
3054                String curId = ims.first;
3055                if (curId.equals(id)) {
3056                    // We are disabling this input method, and it is
3057                    // currently enabled.  Skip it to remove from the
3058                    // new list.
3059                    isRemoved = true;
3060                } else {
3061                    if (needsAppendSeparator) {
3062                        builder.append(INPUT_METHOD_SEPARATER);
3063                    } else {
3064                        needsAppendSeparator = true;
3065                    }
3066                    buildEnabledInputMethodsSettingString(builder, ims);
3067                }
3068            }
3069            if (isRemoved) {
3070                // Update the setting with the new list of input methods.
3071                putEnabledInputMethodsStr(builder.toString());
3072            }
3073            return isRemoved;
3074        }
3075
3076        private List<InputMethodInfo> createEnabledInputMethodListLocked(
3077                List<Pair<String, ArrayList<String>>> imsList) {
3078            final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
3079            for (Pair<String, ArrayList<String>> ims: imsList) {
3080                InputMethodInfo info = mMethodMap.get(ims.first);
3081                if (info != null) {
3082                    res.add(info);
3083                }
3084            }
3085            return res;
3086        }
3087
3088        private List<Pair<InputMethodInfo, ArrayList<String>>>
3089                createEnabledInputMethodAndSubtypeHashCodeListLocked(
3090                        List<Pair<String, ArrayList<String>>> imsList) {
3091            final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
3092                    = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
3093            for (Pair<String, ArrayList<String>> ims : imsList) {
3094                InputMethodInfo info = mMethodMap.get(ims.first);
3095                if (info != null) {
3096                    res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
3097                }
3098            }
3099            return res;
3100        }
3101
3102        private void putEnabledInputMethodsStr(String str) {
3103            Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
3104            mEnabledInputMethodsStrCache = str;
3105        }
3106
3107        private String getEnabledInputMethodsStr() {
3108            mEnabledInputMethodsStrCache = Settings.Secure.getString(
3109                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
3110            if (DEBUG) {
3111                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
3112            }
3113            return mEnabledInputMethodsStrCache;
3114        }
3115
3116        private void saveSubtypeHistory(
3117                List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
3118            StringBuilder builder = new StringBuilder();
3119            boolean isImeAdded = false;
3120            if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
3121                builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
3122                        newSubtypeId);
3123                isImeAdded = true;
3124            }
3125            for (Pair<String, String> ime: savedImes) {
3126                String imeId = ime.first;
3127                String subtypeId = ime.second;
3128                if (TextUtils.isEmpty(subtypeId)) {
3129                    subtypeId = NOT_A_SUBTYPE_ID_STR;
3130                }
3131                if (isImeAdded) {
3132                    builder.append(INPUT_METHOD_SEPARATER);
3133                } else {
3134                    isImeAdded = true;
3135                }
3136                builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
3137                        subtypeId);
3138            }
3139            // Remove the last INPUT_METHOD_SEPARATER
3140            putSubtypeHistoryStr(builder.toString());
3141        }
3142
3143        public void addSubtypeToHistory(String imeId, String subtypeId) {
3144            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
3145            for (Pair<String, String> ime: subtypeHistory) {
3146                if (ime.first.equals(imeId)) {
3147                    if (DEBUG) {
3148                        Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
3149                                + ime.second);
3150                    }
3151                    // We should break here
3152                    subtypeHistory.remove(ime);
3153                    break;
3154                }
3155            }
3156            if (DEBUG) {
3157                Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
3158            }
3159            saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
3160        }
3161
3162        private void putSubtypeHistoryStr(String str) {
3163            if (DEBUG) {
3164                Slog.d(TAG, "putSubtypeHistoryStr: " + str);
3165            }
3166            Settings.Secure.putString(
3167                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
3168        }
3169
3170        public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
3171            // Gets the first one from the history
3172            return getLastSubtypeForInputMethodLockedInternal(null);
3173        }
3174
3175        public String getLastSubtypeForInputMethodLocked(String imeId) {
3176            Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
3177            if (ime != null) {
3178                return ime.second;
3179            } else {
3180                return null;
3181            }
3182        }
3183
3184        private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
3185            List<Pair<String, ArrayList<String>>> enabledImes =
3186                    getEnabledInputMethodsAndSubtypeListLocked();
3187            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
3188            for (Pair<String, String> imeAndSubtype : subtypeHistory) {
3189                final String imeInTheHistory = imeAndSubtype.first;
3190                // If imeId is empty, returns the first IME and subtype in the history
3191                if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
3192                    final String subtypeInTheHistory = imeAndSubtype.second;
3193                    final String subtypeHashCode =
3194                            getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
3195                                    enabledImes, imeInTheHistory, subtypeInTheHistory);
3196                    if (!TextUtils.isEmpty(subtypeHashCode)) {
3197                        if (DEBUG) {
3198                            Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
3199                        }
3200                        return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
3201                    }
3202                }
3203            }
3204            if (DEBUG) {
3205                Slog.d(TAG, "No enabled IME found in the history");
3206            }
3207            return null;
3208        }
3209
3210        private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
3211                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
3212            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
3213                if (enabledIme.first.equals(imeId)) {
3214                    final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
3215                    if (explicitlyEnabledSubtypes.size() == 0) {
3216                        // If there are no explicitly enabled subtypes, applicable subtypes are
3217                        // enabled implicitly.
3218                        InputMethodInfo imi = mMethodMap.get(imeId);
3219                        // If IME is enabled and no subtypes are enabled, applicable subtypes
3220                        // are enabled implicitly, so needs to treat them to be enabled.
3221                        if (imi != null && imi.getSubtypeCount() > 0) {
3222                            List<InputMethodSubtype> implicitlySelectedSubtypes =
3223                                    getImplicitlyApplicableSubtypesLocked(mRes, imi);
3224                            if (implicitlySelectedSubtypes != null) {
3225                                final int N = implicitlySelectedSubtypes.size();
3226                                for (int i = 0; i < N; ++i) {
3227                                    final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
3228                                    if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
3229                                        return subtypeHashCode;
3230                                    }
3231                                }
3232                            }
3233                        }
3234                    } else {
3235                        for (String s: explicitlyEnabledSubtypes) {
3236                            if (s.equals(subtypeHashCode)) {
3237                                // If both imeId and subtypeId are enabled, return subtypeId.
3238                                return s;
3239                            }
3240                        }
3241                    }
3242                    // If imeId was enabled but subtypeId was disabled.
3243                    return NOT_A_SUBTYPE_ID_STR;
3244                }
3245            }
3246            // If both imeId and subtypeId are disabled, return null
3247            return null;
3248        }
3249
3250        private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
3251            ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
3252            final String subtypeHistoryStr = getSubtypeHistoryStr();
3253            if (TextUtils.isEmpty(subtypeHistoryStr)) {
3254                return imsList;
3255            }
3256            mInputMethodSplitter.setString(subtypeHistoryStr);
3257            while (mInputMethodSplitter.hasNext()) {
3258                String nextImsStr = mInputMethodSplitter.next();
3259                mSubtypeSplitter.setString(nextImsStr);
3260                if (mSubtypeSplitter.hasNext()) {
3261                    String subtypeId = NOT_A_SUBTYPE_ID_STR;
3262                    // The first element is ime id.
3263                    String imeId = mSubtypeSplitter.next();
3264                    while (mSubtypeSplitter.hasNext()) {
3265                        subtypeId = mSubtypeSplitter.next();
3266                        break;
3267                    }
3268                    imsList.add(new Pair<String, String>(imeId, subtypeId));
3269                }
3270            }
3271            return imsList;
3272        }
3273
3274        private String getSubtypeHistoryStr() {
3275            if (DEBUG) {
3276                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
3277                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
3278            }
3279            return Settings.Secure.getString(
3280                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
3281        }
3282
3283        public void putSelectedInputMethod(String imeId) {
3284            Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
3285        }
3286
3287        public void putSelectedSubtype(int subtypeId) {
3288            Settings.Secure.putInt(
3289                    mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
3290        }
3291    }
3292
3293    private static class InputMethodFileManager {
3294        private static final String SYSTEM_PATH = "system";
3295        private static final String INPUT_METHOD_PATH = "inputmethod";
3296        private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
3297        private static final String NODE_SUBTYPES = "subtypes";
3298        private static final String NODE_SUBTYPE = "subtype";
3299        private static final String NODE_IMI = "imi";
3300        private static final String ATTR_ID = "id";
3301        private static final String ATTR_LABEL = "label";
3302        private static final String ATTR_ICON = "icon";
3303        private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
3304        private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
3305        private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
3306        private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
3307        private final AtomicFile mAdditionalInputMethodSubtypeFile;
3308        private final HashMap<String, InputMethodInfo> mMethodMap;
3309        private final HashMap<String, List<InputMethodSubtype>> mSubtypesMap =
3310                new HashMap<String, List<InputMethodSubtype>>();
3311        public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap) {
3312            if (methodMap == null) {
3313                throw new NullPointerException("methodMap is null");
3314            }
3315            mMethodMap = methodMap;
3316            final File systemDir = new File(Environment.getDataDirectory(), SYSTEM_PATH);
3317            final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
3318            if (!inputMethodDir.mkdirs()) {
3319                Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
3320            }
3321            final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
3322            mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
3323            if (!subtypeFile.exists()) {
3324                // If "subtypes.xml" doesn't exist, create a blank file.
3325                writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
3326                        methodMap);
3327            } else {
3328                readAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile);
3329            }
3330        }
3331
3332        private void deleteAllInputMethodSubtypes(String imiId) {
3333            synchronized (mMethodMap) {
3334                mSubtypesMap.remove(imiId);
3335                writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
3336                        mMethodMap);
3337            }
3338        }
3339
3340        public void addInputMethodSubtypes(
3341                InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
3342            synchronized (mMethodMap) {
3343                final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
3344                final int N = additionalSubtypes.length;
3345                for (int i = 0; i < N; ++i) {
3346                    final InputMethodSubtype subtype = additionalSubtypes[i];
3347                    if (!subtypes.contains(subtype)) {
3348                        subtypes.add(subtype);
3349                    }
3350                }
3351                mSubtypesMap.put(imi.getId(), subtypes);
3352                writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
3353                        mMethodMap);
3354            }
3355        }
3356
3357        public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
3358            synchronized (mMethodMap) {
3359                return mSubtypesMap;
3360            }
3361        }
3362
3363        private static void writeAdditionalInputMethodSubtypes(
3364                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
3365                HashMap<String, InputMethodInfo> methodMap) {
3366            // Safety net for the case that this function is called before methodMap is set.
3367            final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
3368            FileOutputStream fos = null;
3369            try {
3370                fos = subtypesFile.startWrite();
3371                final XmlSerializer out = new FastXmlSerializer();
3372                out.setOutput(fos, "utf-8");
3373                out.startDocument(null, true);
3374                out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
3375                out.startTag(null, NODE_SUBTYPES);
3376                for (String imiId : allSubtypes.keySet()) {
3377                    if (isSetMethodMap && !methodMap.containsKey(imiId)) {
3378                        Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
3379                        continue;
3380                    }
3381                    out.startTag(null, NODE_IMI);
3382                    out.attribute(null, ATTR_ID, imiId);
3383                    final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
3384                    final int N = subtypesList.size();
3385                    for (int i = 0; i < N; ++i) {
3386                        final InputMethodSubtype subtype = subtypesList.get(i);
3387                        out.startTag(null, NODE_SUBTYPE);
3388                        out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
3389                        out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
3390                        out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
3391                        out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
3392                        out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
3393                        out.attribute(null, ATTR_IS_AUXILIARY,
3394                                String.valueOf(subtype.isAuxiliary() ? 1 : 0));
3395                        out.endTag(null, NODE_SUBTYPE);
3396                    }
3397                    out.endTag(null, NODE_IMI);
3398                }
3399                out.endTag(null, NODE_SUBTYPES);
3400                out.endDocument();
3401                subtypesFile.finishWrite(fos);
3402            } catch (java.io.IOException e) {
3403                Slog.w(TAG, "Error writing subtypes", e);
3404                if (fos != null) {
3405                    subtypesFile.failWrite(fos);
3406                }
3407            }
3408        }
3409
3410        private static void readAdditionalInputMethodSubtypes(
3411                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
3412            if (allSubtypes == null || subtypesFile == null) return;
3413            allSubtypes.clear();
3414            FileInputStream fis = null;
3415            try {
3416                fis = subtypesFile.openRead();
3417                final XmlPullParser parser = Xml.newPullParser();
3418                parser.setInput(fis, null);
3419                int type = parser.getEventType();
3420                // Skip parsing until START_TAG
3421                while ((type = parser.next()) != XmlPullParser.START_TAG
3422                        && type != XmlPullParser.END_DOCUMENT) {}
3423                String firstNodeName = parser.getName();
3424                if (!NODE_SUBTYPES.equals(firstNodeName)) {
3425                    throw new XmlPullParserException("Xml doesn't start with subtypes");
3426                }
3427                final int depth =parser.getDepth();
3428                String currentImiId = null;
3429                ArrayList<InputMethodSubtype> tempSubtypesArray = null;
3430                while (((type = parser.next()) != XmlPullParser.END_TAG
3431                        || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
3432                    if (type != XmlPullParser.START_TAG)
3433                        continue;
3434                    final String nodeName = parser.getName();
3435                    if (NODE_IMI.equals(nodeName)) {
3436                        currentImiId = parser.getAttributeValue(null, ATTR_ID);
3437                        if (TextUtils.isEmpty(currentImiId)) {
3438                            Slog.w(TAG, "Invalid imi id found in subtypes.xml");
3439                            continue;
3440                        }
3441                        tempSubtypesArray = new ArrayList<InputMethodSubtype>();
3442                        allSubtypes.put(currentImiId, tempSubtypesArray);
3443                    } else if (NODE_SUBTYPE.equals(nodeName)) {
3444                        if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
3445                            Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
3446                            continue;
3447                        }
3448                        final int icon = Integer.valueOf(
3449                                parser.getAttributeValue(null, ATTR_ICON));
3450                        final int label = Integer.valueOf(
3451                                parser.getAttributeValue(null, ATTR_LABEL));
3452                        final String imeSubtypeLocale =
3453                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
3454                        final String imeSubtypeMode =
3455                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
3456                        final String imeSubtypeExtraValue =
3457                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
3458                        final boolean isAuxiliary = "1".equals(String.valueOf(
3459                                parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
3460                        final InputMethodSubtype subtype =
3461                                new InputMethodSubtype(label, icon, imeSubtypeLocale,
3462                                        imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
3463                        tempSubtypesArray.add(subtype);
3464                    }
3465                }
3466            } catch (XmlPullParserException e) {
3467                Slog.w(TAG, "Error reading subtypes: " + e);
3468                return;
3469            } catch (java.io.IOException e) {
3470                Slog.w(TAG, "Error reading subtypes: " + e);
3471                return;
3472            } catch (NumberFormatException e) {
3473                Slog.w(TAG, "Error reading subtypes: " + e);
3474                return;
3475            } finally {
3476                if (fis != null) {
3477                    try {
3478                        fis.close();
3479                    } catch (java.io.IOException e1) {
3480                        Slog.w(TAG, "Failed to close.");
3481                    }
3482                }
3483            }
3484        }
3485    }
3486
3487    // ----------------------------------------------------------------------
3488
3489    @Override
3490    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3491        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
3492                != PackageManager.PERMISSION_GRANTED) {
3493
3494            pw.println("Permission Denial: can't dump InputMethodManager from from pid="
3495                    + Binder.getCallingPid()
3496                    + ", uid=" + Binder.getCallingUid());
3497            return;
3498        }
3499
3500        IInputMethod method;
3501        ClientState client;
3502
3503        final Printer p = new PrintWriterPrinter(pw);
3504
3505        synchronized (mMethodMap) {
3506            p.println("Current Input Method Manager state:");
3507            int N = mMethodList.size();
3508            p.println("  Input Methods:");
3509            for (int i=0; i<N; i++) {
3510                InputMethodInfo info = mMethodList.get(i);
3511                p.println("  InputMethod #" + i + ":");
3512                info.dump(p, "    ");
3513            }
3514            p.println("  Clients:");
3515            for (ClientState ci : mClients.values()) {
3516                p.println("  Client " + ci + ":");
3517                p.println("    client=" + ci.client);
3518                p.println("    inputContext=" + ci.inputContext);
3519                p.println("    sessionRequested=" + ci.sessionRequested);
3520                p.println("    curSession=" + ci.curSession);
3521            }
3522            p.println("  mCurMethodId=" + mCurMethodId);
3523            client = mCurClient;
3524            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
3525            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
3526            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
3527                    + " mBoundToMethod=" + mBoundToMethod);
3528            p.println("  mCurToken=" + mCurToken);
3529            p.println("  mCurIntent=" + mCurIntent);
3530            method = mCurMethod;
3531            p.println("  mCurMethod=" + mCurMethod);
3532            p.println("  mEnabledSession=" + mEnabledSession);
3533            p.println("  mShowRequested=" + mShowRequested
3534                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
3535                    + " mShowForced=" + mShowForced
3536                    + " mInputShown=" + mInputShown);
3537            p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
3538        }
3539
3540        p.println(" ");
3541        if (client != null) {
3542            pw.flush();
3543            try {
3544                client.client.asBinder().dump(fd, args);
3545            } catch (RemoteException e) {
3546                p.println("Input method client dead: " + e);
3547            }
3548        } else {
3549            p.println("No input method client.");
3550        }
3551
3552        p.println(" ");
3553        if (method != null) {
3554            pw.flush();
3555            try {
3556                method.asBinder().dump(fd, args);
3557            } catch (RemoteException e) {
3558                p.println("Input method service dead: " + e);
3559            }
3560        } else {
3561            p.println("No input method service.");
3562        }
3563    }
3564}
3565