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