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