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