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 android.view.Display.DEFAULT_DISPLAY;
19import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
20import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
21import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
22import static java.lang.annotation.RetentionPolicy.SOURCE;
23
24import com.android.internal.annotations.GuardedBy;
25import com.android.internal.content.PackageMonitor;
26import com.android.internal.inputmethod.IInputContentUriToken;
27import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
28import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
29import com.android.internal.inputmethod.InputMethodUtils;
30import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
31import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
32import com.android.internal.notification.SystemNotificationChannels;
33import com.android.internal.os.HandlerCaller;
34import com.android.internal.os.SomeArgs;
35import com.android.internal.os.TransferPipe;
36import com.android.internal.util.DumpUtils;
37import com.android.internal.util.FastXmlSerializer;
38import com.android.internal.util.IndentingPrintWriter;
39import com.android.internal.view.IInputContext;
40import com.android.internal.view.IInputMethod;
41import com.android.internal.view.IInputMethodClient;
42import com.android.internal.view.IInputMethodManager;
43import com.android.internal.view.IInputMethodSession;
44import com.android.internal.view.IInputSessionCallback;
45import com.android.internal.view.InputBindResult;
46import com.android.internal.view.InputMethodClient;
47import com.android.server.statusbar.StatusBarManagerService;
48
49import org.xmlpull.v1.XmlPullParser;
50import org.xmlpull.v1.XmlPullParserException;
51import org.xmlpull.v1.XmlSerializer;
52
53import android.Manifest;
54import android.annotation.AnyThread;
55import android.annotation.BinderThread;
56import android.annotation.ColorInt;
57import android.annotation.IntDef;
58import android.annotation.MainThread;
59import android.annotation.NonNull;
60import android.annotation.Nullable;
61import android.annotation.RequiresPermission;
62import android.annotation.TestApi;
63import android.annotation.UserIdInt;
64import android.app.ActivityManager;
65import android.app.ActivityManagerInternal;
66import android.app.ActivityThread;
67import android.app.AlertDialog;
68import android.app.AppGlobals;
69import android.app.AppOpsManager;
70import android.app.KeyguardManager;
71import android.app.Notification;
72import android.app.NotificationManager;
73import android.app.PendingIntent;
74import android.content.BroadcastReceiver;
75import android.content.ComponentName;
76import android.content.ContentProvider;
77import android.content.ContentResolver;
78import android.content.Context;
79import android.content.DialogInterface;
80import android.content.DialogInterface.OnCancelListener;
81import android.content.DialogInterface.OnClickListener;
82import android.content.Intent;
83import android.content.IntentFilter;
84import android.content.ServiceConnection;
85import android.content.pm.ApplicationInfo;
86import android.content.pm.IPackageManager;
87import android.content.pm.PackageManager;
88import android.content.pm.ResolveInfo;
89import android.content.pm.ServiceInfo;
90import android.content.res.Configuration;
91import android.content.res.Resources;
92import android.content.res.TypedArray;
93import android.database.ContentObserver;
94import android.graphics.drawable.Drawable;
95import android.inputmethodservice.InputMethodService;
96import android.net.Uri;
97import android.os.Binder;
98import android.os.Bundle;
99import android.os.Debug;
100import android.os.Environment;
101import android.os.Handler;
102import android.os.IBinder;
103import android.os.IInterface;
104import android.os.Message;
105import android.os.LocaleList;
106import android.os.Parcel;
107import android.os.Process;
108import android.os.RemoteException;
109import android.os.ResultReceiver;
110import android.os.ServiceManager;
111import android.os.ShellCallback;
112import android.os.ShellCommand;
113import android.os.SystemClock;
114import android.os.SystemProperties;
115import android.os.UserHandle;
116import android.os.UserManager;
117import android.provider.Settings;
118import android.service.vr.IVrManager;
119import android.service.vr.IVrStateCallbacks;
120import android.text.TextUtils;
121import android.text.style.SuggestionSpan;
122import android.util.ArraySet;
123import android.util.AtomicFile;
124import android.util.EventLog;
125import android.util.LruCache;
126import android.util.Pair;
127import android.util.PrintWriterPrinter;
128import android.util.Printer;
129import android.util.Slog;
130import android.util.Xml;
131import android.view.ContextThemeWrapper;
132import android.view.IWindowManager;
133import android.view.InputChannel;
134import android.view.LayoutInflater;
135import android.view.View;
136import android.view.ViewGroup;
137import android.view.Window;
138import android.view.WindowManager;
139import android.view.inputmethod.EditorInfo;
140import android.view.inputmethod.InputBinding;
141import android.view.inputmethod.InputConnection;
142import android.view.inputmethod.InputConnectionInspector;
143import android.view.inputmethod.InputMethod;
144import android.view.inputmethod.InputMethodInfo;
145import android.view.inputmethod.InputMethodManager;
146import android.view.inputmethod.InputMethodManagerInternal;
147import android.view.inputmethod.InputMethodSubtype;
148import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
149import android.widget.ArrayAdapter;
150import android.widget.CompoundButton;
151import android.widget.CompoundButton.OnCheckedChangeListener;
152import android.widget.RadioButton;
153import android.widget.Switch;
154import android.widget.TextView;
155import android.widget.Toast;
156
157import com.android.server.wm.WindowManagerInternal;
158
159import java.io.File;
160import java.io.FileDescriptor;
161import java.io.FileInputStream;
162import java.io.FileOutputStream;
163import java.io.IOException;
164import java.io.PrintWriter;
165import java.lang.annotation.Retention;
166import java.nio.charset.StandardCharsets;
167import java.security.InvalidParameterException;
168import java.text.SimpleDateFormat;
169import java.util.ArrayList;
170import java.util.Collections;
171import java.util.Date;
172import java.util.HashMap;
173import java.util.List;
174import java.util.Locale;
175import java.util.WeakHashMap;
176import java.util.concurrent.atomic.AtomicInteger;
177
178/**
179 * This class provides a system service that manages input methods.
180 */
181public class InputMethodManagerService extends IInputMethodManager.Stub
182        implements ServiceConnection, Handler.Callback {
183    static final boolean DEBUG = false;
184    static final String TAG = "InputMethodManagerService";
185
186    @Retention(SOURCE)
187    @IntDef({ShellCommandResult.SUCCESS, ShellCommandResult.FAILURE})
188    private @interface ShellCommandResult {
189        int SUCCESS = 0;
190        int FAILURE = -1;
191    }
192
193    static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
194    static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2;
195    static final int MSG_SHOW_IM_CONFIG = 3;
196
197    static final int MSG_UNBIND_INPUT = 1000;
198    static final int MSG_BIND_INPUT = 1010;
199    static final int MSG_SHOW_SOFT_INPUT = 1020;
200    static final int MSG_HIDE_SOFT_INPUT = 1030;
201    static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035;
202    static final int MSG_ATTACH_TOKEN = 1040;
203    static final int MSG_CREATE_SESSION = 1050;
204
205    static final int MSG_START_INPUT = 2000;
206    static final int MSG_START_VR_INPUT = 2010;
207
208    static final int MSG_UNBIND_CLIENT = 3000;
209    static final int MSG_BIND_CLIENT = 3010;
210    static final int MSG_SET_ACTIVE = 3020;
211    static final int MSG_SET_INTERACTIVE = 3030;
212    static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
213    static final int MSG_REPORT_FULLSCREEN_MODE = 3045;
214    static final int MSG_SWITCH_IME = 3050;
215
216    static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
217
218    static final int MSG_SYSTEM_UNLOCK_USER = 5000;
219
220    static final long TIME_TO_RECONNECT = 3 * 1000;
221
222    static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
223
224    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
225    private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
226
227    /**
228     * Binding flags for establishing connection to the {@link InputMethodService}.
229     */
230    private static final int IME_CONNECTION_BIND_FLAGS =
231            Context.BIND_AUTO_CREATE
232            | Context.BIND_NOT_VISIBLE
233            | Context.BIND_NOT_FOREGROUND
234            | Context.BIND_IMPORTANT_BACKGROUND;
235
236    /**
237     * Binding flags used only while the {@link InputMethodService} is showing window.
238     */
239    private static final int IME_VISIBLE_BIND_FLAGS =
240            Context.BIND_AUTO_CREATE
241            | Context.BIND_TREAT_LIKE_ACTIVITY
242            | Context.BIND_FOREGROUND_SERVICE
243            | Context.BIND_SHOWING_UI;
244
245    @Retention(SOURCE)
246    @IntDef({HardKeyboardBehavior.WIRELESS_AFFORDANCE, HardKeyboardBehavior.WIRED_AFFORDANCE})
247    private @interface  HardKeyboardBehavior {
248        int WIRELESS_AFFORDANCE = 0;
249        int WIRED_AFFORDANCE = 1;
250    }
251
252    /**
253     * A protected broadcast intent action for internal use for {@link PendingIntent} in
254     * the notification.
255     */
256    private static final String ACTION_SHOW_INPUT_METHOD_PICKER =
257            "com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER";
258
259    /**
260     * Debug flag for overriding runtime {@link SystemProperties}.
261     */
262    @AnyThread
263    private static final class DebugFlag {
264        private static final Object LOCK = new Object();
265        private final String mKey;
266        private final boolean mDefaultValue;
267        @GuardedBy("LOCK")
268        private boolean mValue;
269
270        public DebugFlag(String key, boolean defaultValue) {
271            mKey = key;
272            mDefaultValue = defaultValue;
273            mValue = SystemProperties.getBoolean(key, defaultValue);
274        }
275
276        void refresh() {
277            synchronized (LOCK) {
278                mValue = SystemProperties.getBoolean(mKey, mDefaultValue);
279            }
280        }
281
282        boolean value() {
283            synchronized (LOCK) {
284                return mValue;
285            }
286        }
287    }
288
289    /**
290     * Debug flags that can be overridden using "adb shell setprop <key>"
291     * Note: These flags are cached. To refresh, run "adb shell ime refresh_debug_properties".
292     */
293    private static final class DebugFlags {
294        static final DebugFlag FLAG_OPTIMIZE_START_INPUT =
295                new DebugFlag("debug.optimize_startinput", false);
296    }
297
298
299    final Context mContext;
300    final Resources mRes;
301    final Handler mHandler;
302    final InputMethodSettings mSettings;
303    final SettingsObserver mSettingsObserver;
304    final IWindowManager mIWindowManager;
305    final WindowManagerInternal mWindowManagerInternal;
306    final HandlerCaller mCaller;
307    final boolean mHasFeature;
308    private InputMethodFileManager mFileManager;
309    private final HardKeyboardListener mHardKeyboardListener;
310    private final AppOpsManager mAppOpsManager;
311    private final UserManager mUserManager;
312
313    // All known input methods.  mMethodMap also serves as the global
314    // lock for this class.
315    final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
316    final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();
317    private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
318            new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
319    private final InputMethodSubtypeSwitchingController mSwitchingController;
320
321    /**
322     * Tracks how many times {@link #mMethodMap} was updated.
323     */
324    @GuardedBy("mMethodMap")
325    private int mMethodMapUpdateCount = 0;
326
327    // Used to bring IME service up to visible adjustment while it is being shown.
328    final ServiceConnection mVisibleConnection = new ServiceConnection() {
329        @Override public void onServiceConnected(ComponentName name, IBinder service) {
330        }
331
332        @Override public void onServiceDisconnected(ComponentName name) {
333        }
334    };
335    boolean mVisibleBound = false;
336
337    // Ongoing notification
338    private NotificationManager mNotificationManager;
339    private KeyguardManager mKeyguardManager;
340    private @Nullable StatusBarManagerService mStatusBar;
341    private Notification.Builder mImeSwitcherNotification;
342    private PendingIntent mImeSwitchPendingIntent;
343    private boolean mShowOngoingImeSwitcherForPhones;
344    private boolean mNotificationShown;
345
346    static class SessionState {
347        final ClientState client;
348        final IInputMethod method;
349
350        IInputMethodSession session;
351        InputChannel channel;
352
353        @Override
354        public String toString() {
355            return "SessionState{uid " + client.uid + " pid " + client.pid
356                    + " method " + Integer.toHexString(
357                            System.identityHashCode(method))
358                    + " session " + Integer.toHexString(
359                            System.identityHashCode(session))
360                    + " channel " + channel
361                    + "}";
362        }
363
364        SessionState(ClientState _client, IInputMethod _method,
365                IInputMethodSession _session, InputChannel _channel) {
366            client = _client;
367            method = _method;
368            session = _session;
369            channel = _channel;
370        }
371    }
372
373    /**
374     * VR state callback.
375     * Listens for when VR mode finishes.
376     */
377    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
378        @Override
379        public void onVrStateChanged(boolean enabled) {
380            if (!enabled) {
381                restoreNonVrImeFromSettingsNoCheck();
382            }
383        }
384    };
385
386    private void restoreNonVrImeFromSettingsNoCheck() {
387        // switch back to non-VR InputMethod from settings.
388        synchronized (mMethodMap) {
389            final String lastInputId = mSettings.getSelectedInputMethod();
390            setInputMethodLocked(lastInputId,
391                    mSettings.getSelectedInputMethodSubtypeId(lastInputId));
392        }
393    }
394
395    static final class ClientState {
396        final IInputMethodClient client;
397        final IInputContext inputContext;
398        final int uid;
399        final int pid;
400        final InputBinding binding;
401
402        boolean sessionRequested;
403        SessionState curSession;
404
405        @Override
406        public String toString() {
407            return "ClientState{" + Integer.toHexString(
408                    System.identityHashCode(this)) + " uid " + uid
409                    + " pid " + pid + "}";
410        }
411
412        ClientState(IInputMethodClient _client, IInputContext _inputContext,
413                int _uid, int _pid) {
414            client = _client;
415            inputContext = _inputContext;
416            uid = _uid;
417            pid = _pid;
418            binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
419        }
420    }
421
422    final HashMap<IBinder, ClientState> mClients = new HashMap<>();
423
424    /**
425     * Set once the system is ready to run third party code.
426     */
427    boolean mSystemReady;
428
429    /**
430     * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
431     * method.  This is to be synchronized with the secure settings keyed with
432     * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
433     *
434     * <p>This can be transiently {@code null} when the system is re-initializing input method
435     * settings, e.g., the system locale is just changed.</p>
436     *
437     * <p>Note that {@link #mCurId} is used to track which IME is being connected to
438     * {@link InputMethodManagerService}.</p>
439     *
440     * @see #mCurId
441     */
442    @Nullable
443    String mCurMethodId;
444
445    /**
446     * The current binding sequence number, incremented every time there is
447     * a new bind performed.
448     */
449    int mCurSeq;
450
451    /**
452     * The client that is currently bound to an input method.
453     */
454    ClientState mCurClient;
455
456    /**
457     * The last window token that we confirmed to be focused.  This is always updated upon reports
458     * from the input method client.  If the window state is already changed before the report is
459     * handled, this field just keeps the last value.
460     */
461    IBinder mCurFocusedWindow;
462
463    /**
464     * {@link WindowManager.LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
465     *
466     * @see #mCurFocusedWindow
467     */
468    int mCurFocusedWindowSoftInputMode;
469
470    /**
471     * The client by which {@link #mCurFocusedWindow} was reported.
472     */
473    ClientState mCurFocusedWindowClient;
474
475    /**
476     * The input context last provided by the current client.
477     */
478    IInputContext mCurInputContext;
479
480    /**
481     * The missing method flags for the input context last provided by the current client.
482     *
483     * @see android.view.inputmethod.InputConnectionInspector.MissingMethodFlags
484     */
485    int mCurInputContextMissingMethods;
486
487    /**
488     * The attributes last provided by the current client.
489     */
490    EditorInfo mCurAttribute;
491
492    /**
493     * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
494     * connected to or in the process of connecting to.
495     *
496     * <p>This can be {@code null} when no input method is connected.</p>
497     *
498     * @see #mCurMethodId
499     */
500    @Nullable
501    String mCurId;
502
503    /**
504     * The current subtype of the current input method.
505     */
506    private InputMethodSubtype mCurrentSubtype;
507
508    // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
509    private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
510            mShortcutInputMethodsAndSubtypes = new HashMap<>();
511
512    // Was the keyguard locked when this client became current?
513    private boolean mCurClientInKeyguard;
514
515    /**
516     * Set to true if our ServiceConnection is currently actively bound to
517     * a service (whether or not we have gotten its IBinder back yet).
518     */
519    boolean mHaveConnection;
520
521    /**
522     * Set if the client has asked for the input method to be shown.
523     */
524    boolean mShowRequested;
525
526    /**
527     * Set if we were explicitly told to show the input method.
528     */
529    boolean mShowExplicitlyRequested;
530
531    /**
532     * Set if we were forced to be shown.
533     */
534    boolean mShowForced;
535
536    /**
537     * Set if we last told the input method to show itself.
538     */
539    boolean mInputShown;
540
541    /**
542     * {@code true} if the current input method is in fullscreen mode.
543     */
544    boolean mInFullscreenMode;
545
546    /**
547     * The Intent used to connect to the current input method.
548     */
549    Intent mCurIntent;
550
551    /**
552     * The token we have made for the currently active input method, to
553     * identify it in the future.
554     */
555    IBinder mCurToken;
556
557    /**
558     * If non-null, this is the input method service we are currently connected
559     * to.
560     */
561    IInputMethod mCurMethod;
562
563    /**
564     * Time that we last initiated a bind to the input method, to determine
565     * if we should try to disconnect and reconnect to it.
566     */
567    long mLastBindTime;
568
569    /**
570     * Have we called mCurMethod.bindInput()?
571     */
572    boolean mBoundToMethod;
573
574    /**
575     * Currently enabled session.  Only touched by service thread, not
576     * protected by a lock.
577     */
578    SessionState mEnabledSession;
579
580    /**
581     * True if the device is currently interactive with user.  The value is true initially.
582     */
583    boolean mIsInteractive = true;
584
585    int mCurUserActionNotificationSequenceNumber = 0;
586
587    int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
588
589    /**
590     * A set of status bits regarding the active IME.
591     *
592     * <p>This value is a combination of following two bits:</p>
593     * <dl>
594     * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
595     * <dd>
596     *   If this bit is ON, connected IME is ready to accept touch/key events.
597     * </dd>
598     * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
599     * <dd>
600     *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
601     * </dd>
602     * </dl>
603     * <em>Do not update this value outside of setImeWindowStatus.</em>
604     */
605    int mImeWindowVis;
606
607    private AlertDialog.Builder mDialogBuilder;
608    private AlertDialog mSwitchingDialog;
609    private IBinder mSwitchingDialogToken = new Binder();
610    private View mSwitchingDialogTitleView;
611    private Toast mSubtypeSwitchedByShortCutToast;
612    private InputMethodInfo[] mIms;
613    private int[] mSubtypeIds;
614    private LocaleList mLastSystemLocales;
615    private boolean mShowImeWithHardKeyboard;
616    private boolean mAccessibilityRequestingNoSoftKeyboard;
617    private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
618    private final IPackageManager mIPackageManager;
619    private final String mSlotIme;
620    @HardKeyboardBehavior
621    private final int mHardKeyboardBehavior;
622
623    /**
624     * Whether we temporarily allow IMEs implemented in instant apps to run for testing.
625     *
626     * <p>Note: This is quite dangerous.  Don't forget to reset after you finish testing.</p>
627     */
628    private boolean mBindInstantServiceAllowed = false;
629
630    /**
631     * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the
632     * internal message queue. Any subsequent state change inside {@link InputMethodManagerService}
633     * will not affect those tasks that are already posted.
634     *
635     * <p>Posting {@link #MSG_START_INPUT} message basically means that
636     * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
637     * back in the current IME process shortly, which will also affect what the current IME starts
638     * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
639     * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
640     * logical input session between the client application and the current IME.</p>
641     *
642     * <p>Be careful to not keep strong references to this object forever, which can prevent
643     * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
644     * </p>
645     */
646    private static class StartInputInfo {
647        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
648
649        final int mSequenceNumber;
650        final long mTimestamp;
651        final long mWallTime;
652        @NonNull
653        final IBinder mImeToken;
654        @NonNull
655        final String mImeId;
656        // @InputMethodClient.StartInputReason
657        final int mStartInputReason;
658        final boolean mRestarting;
659        @Nullable
660        final IBinder mTargetWindow;
661        @NonNull
662        final EditorInfo mEditorInfo;
663        final int mTargetWindowSoftInputMode;
664        final int mClientBindSequenceNumber;
665
666        StartInputInfo(@NonNull IBinder imeToken, @NonNull String imeId,
667                /* @InputMethodClient.StartInputReason */ int startInputReason, boolean restarting,
668                @Nullable IBinder targetWindow, @NonNull EditorInfo editorInfo,
669                int targetWindowSoftInputMode, int clientBindSequenceNumber) {
670            mSequenceNumber = sSequenceNumber.getAndIncrement();
671            mTimestamp = SystemClock.uptimeMillis();
672            mWallTime = System.currentTimeMillis();
673            mImeToken = imeToken;
674            mImeId = imeId;
675            mStartInputReason = startInputReason;
676            mRestarting = restarting;
677            mTargetWindow = targetWindow;
678            mEditorInfo = editorInfo;
679            mTargetWindowSoftInputMode = targetWindowSoftInputMode;
680            mClientBindSequenceNumber = clientBindSequenceNumber;
681        }
682    }
683
684    @GuardedBy("mMethodMap")
685    private final WeakHashMap<IBinder, StartInputInfo> mStartInputMap = new WeakHashMap<>();
686
687    /**
688     * A ring buffer to store the history of {@link StartInputInfo}.
689     */
690    private static final class StartInputHistory {
691        /**
692         * Entry size for non low-RAM devices.
693         *
694         * <p>TODO: Consider to follow what other system services have been doing to manage
695         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
696         */
697        private final static int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 16;
698
699        /**
700         * Entry size for non low-RAM devices.
701         *
702         * <p>TODO: Consider to follow what other system services have been doing to manage
703         * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
704         */
705        private final static int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
706
707        private static int getEntrySize() {
708            if (ActivityManager.isLowRamDeviceStatic()) {
709                return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
710            } else {
711                return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
712            }
713        }
714
715        /**
716         * Backing store for the ring bugger.
717         */
718        private final Entry[] mEntries = new Entry[getEntrySize()];
719
720        /**
721         * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
722         * write.
723         */
724        private int mNextIndex = 0;
725
726        /**
727         * Recyclable entry to store the information in {@link StartInputInfo}.
728         */
729        private static final class Entry {
730            int mSequenceNumber;
731            long mTimestamp;
732            long mWallTime;
733            @NonNull
734            String mImeTokenString;
735            @NonNull
736            String mImeId;
737            /* @InputMethodClient.StartInputReason */
738            int mStartInputReason;
739            boolean mRestarting;
740            @NonNull
741            String mTargetWindowString;
742            @NonNull
743            EditorInfo mEditorInfo;
744            int mTargetWindowSoftInputMode;
745            int mClientBindSequenceNumber;
746
747            Entry(@NonNull StartInputInfo original) {
748                set(original);
749            }
750
751            void set(@NonNull StartInputInfo original) {
752                mSequenceNumber = original.mSequenceNumber;
753                mTimestamp = original.mTimestamp;
754                mWallTime = original.mWallTime;
755                // Intentionally convert to String so as not to keep a strong reference to a Binder
756                // object.
757                mImeTokenString = String.valueOf(original.mImeToken);
758                mImeId = original.mImeId;
759                mStartInputReason = original.mStartInputReason;
760                mRestarting = original.mRestarting;
761                // Intentionally convert to String so as not to keep a strong reference to a Binder
762                // object.
763                mTargetWindowString = String.valueOf(original.mTargetWindow);
764                mEditorInfo = original.mEditorInfo;
765                mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
766                mClientBindSequenceNumber = original.mClientBindSequenceNumber;
767            }
768        }
769
770        /**
771         * Add a new entry and discard the oldest entry as needed.
772         * @param info {@lin StartInputInfo} to be added.
773         */
774        void addEntry(@NonNull StartInputInfo info) {
775            final int index = mNextIndex;
776            if (mEntries[index] == null) {
777                mEntries[index] = new Entry(info);
778            } else {
779                mEntries[index].set(info);
780            }
781            mNextIndex = (mNextIndex + 1) % mEntries.length;
782        }
783
784        void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
785            final SimpleDateFormat dataFormat =
786                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
787
788            for (int i = 0; i < mEntries.length; ++i) {
789                final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
790                if (entry == null) {
791                    continue;
792                }
793                pw.print(prefix);
794                pw.println("StartInput #" + entry.mSequenceNumber + ":");
795
796                pw.print(prefix);
797                pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
798                        + " (timestamp=" + entry.mTimestamp + ")"
799                        + " reason="
800                        + InputMethodClient.getStartInputReason(entry.mStartInputReason)
801                        + " restarting=" + entry.mRestarting);
802
803                pw.print(prefix);
804                pw.println(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
805
806                pw.print(prefix);
807                pw.println(" targetWin=" + entry.mTargetWindowString
808                        + " [" + entry.mEditorInfo.packageName + "]"
809                        + " clientBindSeq=" + entry.mClientBindSequenceNumber);
810
811                pw.print(prefix);
812                pw.println(" softInputMode=" + InputMethodClient.softInputModeToString(
813                                entry.mTargetWindowSoftInputMode));
814
815                pw.print(prefix);
816                pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
817                        + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
818                        + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
819                        + " fieldName=" + entry.mEditorInfo.fieldName
820                        + " actionId=" + entry.mEditorInfo.actionId
821                        + " actionLabel=" + entry.mEditorInfo.actionLabel);
822            }
823        }
824    }
825
826    @GuardedBy("mMethodMap")
827    @NonNull
828    private final StartInputHistory mStartInputHistory = new StartInputHistory();
829
830    class SettingsObserver extends ContentObserver {
831        int mUserId;
832        boolean mRegistered = false;
833        @NonNull
834        String mLastEnabled = "";
835
836        /**
837         * <em>This constructor must be called within the lock.</em>
838         */
839        SettingsObserver(Handler handler) {
840            super(handler);
841        }
842
843        public void registerContentObserverLocked(@UserIdInt int userId) {
844            if (mRegistered && mUserId == userId) {
845                return;
846            }
847            ContentResolver resolver = mContext.getContentResolver();
848            if (mRegistered) {
849                mContext.getContentResolver().unregisterContentObserver(this);
850                mRegistered = false;
851            }
852            if (mUserId != userId) {
853                mLastEnabled = "";
854                mUserId = userId;
855            }
856            resolver.registerContentObserver(Settings.Secure.getUriFor(
857                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this, userId);
858            resolver.registerContentObserver(Settings.Secure.getUriFor(
859                    Settings.Secure.ENABLED_INPUT_METHODS), false, this, userId);
860            resolver.registerContentObserver(Settings.Secure.getUriFor(
861                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId);
862            resolver.registerContentObserver(Settings.Secure.getUriFor(
863                    Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
864            resolver.registerContentObserver(Settings.Secure.getUriFor(
865                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
866            mRegistered = true;
867        }
868
869        @Override public void onChange(boolean selfChange, Uri uri) {
870            final Uri showImeUri = Settings.Secure.getUriFor(
871                    Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
872            final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
873                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
874            synchronized (mMethodMap) {
875                if (showImeUri.equals(uri)) {
876                    updateKeyboardFromSettingsLocked();
877                } else if (accessibilityRequestingNoImeUri.equals(uri)) {
878                    mAccessibilityRequestingNoSoftKeyboard = Settings.Secure.getIntForUser(
879                            mContext.getContentResolver(),
880                            Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
881                            0, mUserId) == 1;
882                    if (mAccessibilityRequestingNoSoftKeyboard) {
883                        final boolean showRequested = mShowRequested;
884                        hideCurrentInputLocked(0, null);
885                        mShowRequested = showRequested;
886                    } else if (mShowRequested) {
887                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
888                    }
889                } else {
890                    boolean enabledChanged = false;
891                    String newEnabled = mSettings.getEnabledInputMethodsStr();
892                    if (!mLastEnabled.equals(newEnabled)) {
893                        mLastEnabled = newEnabled;
894                        enabledChanged = true;
895                    }
896                    updateInputMethodsFromSettingsLocked(enabledChanged);
897                }
898            }
899        }
900
901        @Override
902        public String toString() {
903            return "SettingsObserver{mUserId=" + mUserId + " mRegistered=" + mRegistered
904                    + " mLastEnabled=" + mLastEnabled + "}";
905        }
906    }
907
908    class ImmsBroadcastReceiver extends BroadcastReceiver {
909        @Override
910        public void onReceive(Context context, Intent intent) {
911            final String action = intent.getAction();
912            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
913                hideInputMethodMenu();
914                // No need to update mIsInteractive
915                return;
916            } else if (Intent.ACTION_USER_ADDED.equals(action)
917                    || Intent.ACTION_USER_REMOVED.equals(action)) {
918                updateCurrentProfileIds();
919                return;
920            } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
921                onActionLocaleChanged();
922            } else if (ACTION_SHOW_INPUT_METHOD_PICKER.equals(action)) {
923                // ACTION_SHOW_INPUT_METHOD_PICKER action is a protected-broadcast and it is
924                // guaranteed to be send only from the system, so that there is no need for extra
925                // security check such as
926                // {@link #canShowInputMethodPickerLocked(IInputMethodClient)}.
927                mHandler.obtainMessage(
928                        MSG_SHOW_IM_SUBTYPE_PICKER,
929                        InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES,
930                        0 /* arg2 */)
931                        .sendToTarget();
932            } else {
933                Slog.w(TAG, "Unexpected intent " + intent);
934            }
935        }
936    }
937
938    /**
939     * Start a VR InputMethod that matches IME with package name of {@param component}.
940     * Note: This method is called from {@link android.app.VrManager}.
941     */
942    private void startVrInputMethodNoCheck(@Nullable ComponentName component) {
943        if (component == null) {
944            // clear the current VR-only IME (if any) and restore normal IME.
945            restoreNonVrImeFromSettingsNoCheck();
946            return;
947        }
948
949        synchronized (mMethodMap) {
950            String packageName = component.getPackageName();
951            for (InputMethodInfo info : mMethodList) {
952                if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) {
953                    // set this is as current inputMethod without updating settings.
954                    setInputMethodEnabledLocked(info.getId(), true);
955                    setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID);
956                    break;
957                }
958            }
959        }
960    }
961
962    /**
963     * Handles {@link Intent#ACTION_LOCALE_CHANGED}.
964     *
965     * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
966     * the users. We should ignore this event if this is about any background user's locale.</p>
967     *
968     * <p>Caution: This method must not be called when system is not ready.</p>
969     */
970    void onActionLocaleChanged() {
971        synchronized (mMethodMap) {
972            final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales();
973            if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) {
974                return;
975            }
976            buildInputMethodListLocked(true);
977            // If the locale is changed, needs to reset the default ime
978            resetDefaultImeLocked(mContext);
979            updateFromSettingsLocked(true);
980            mLastSystemLocales = possibleNewLocale;
981        }
982    }
983
984    final class MyPackageMonitor extends PackageMonitor {
985        /**
986         * Package names that are known to contain {@link InputMethodService}.
987         *
988         * <p>No need to include packages because of direct-boot unaware IMEs since we always rescan
989         * all the packages when the user is unlocked, and direct-boot awareness will not be changed
990         * dynamically unless the entire package is updated, which also always triggers package
991         * rescanning.</p>
992         */
993        @GuardedBy("mMethodMap")
994        final private ArraySet<String> mKnownImePackageNames = new ArraySet<>();
995
996        /**
997         * Packages that are appeared, disappeared, or modified for whatever reason.
998         *
999         * <p>Note: For now we intentionally use {@link ArrayList} instead of {@link ArraySet}
1000         * because 1) the number of elements is almost always 1 or so, and 2) we do not care
1001         * duplicate elements for our use case.</p>
1002         *
1003         * <p>This object must be accessed only from callback methods in {@link PackageMonitor},
1004         * which should be bound to {@link #getRegisteredHandler()}.</p>
1005         */
1006        private final ArrayList<String> mChangedPackages = new ArrayList<>();
1007
1008        /**
1009         * {@code true} if one or more packages that contain {@link InputMethodService} appeared.
1010         *
1011         * <p>This field must be accessed only from callback methods in {@link PackageMonitor},
1012         * which should be bound to {@link #getRegisteredHandler()}.</p>
1013         */
1014        private boolean mImePackageAppeared = false;
1015
1016        @GuardedBy("mMethodMap")
1017        void clearKnownImePackageNamesLocked() {
1018            mKnownImePackageNames.clear();
1019        }
1020
1021        @GuardedBy("mMethodMap")
1022        final void addKnownImePackageNameLocked(@NonNull String packageName) {
1023            mKnownImePackageNames.add(packageName);
1024        }
1025
1026        @GuardedBy("mMethodMap")
1027        private boolean isChangingPackagesOfCurrentUserLocked() {
1028            final int userId = getChangingUserId();
1029            final boolean retval = userId == mSettings.getCurrentUserId();
1030            if (DEBUG) {
1031                if (!retval) {
1032                    Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
1033                }
1034            }
1035            return retval;
1036        }
1037
1038        @Override
1039        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
1040            synchronized (mMethodMap) {
1041                if (!isChangingPackagesOfCurrentUserLocked()) {
1042                    return false;
1043                }
1044                String curInputMethodId = mSettings.getSelectedInputMethod();
1045                final int N = mMethodList.size();
1046                if (curInputMethodId != null) {
1047                    for (int i=0; i<N; i++) {
1048                        InputMethodInfo imi = mMethodList.get(i);
1049                        if (imi.getId().equals(curInputMethodId)) {
1050                            for (String pkg : packages) {
1051                                if (imi.getPackageName().equals(pkg)) {
1052                                    if (!doit) {
1053                                        return true;
1054                                    }
1055                                    resetSelectedInputMethodAndSubtypeLocked("");
1056                                    chooseNewDefaultIMELocked();
1057                                    return true;
1058                                }
1059                            }
1060                        }
1061                    }
1062                }
1063            }
1064            return false;
1065        }
1066
1067        @Override
1068        public void onBeginPackageChanges() {
1069            clearPackageChangeState();
1070        }
1071
1072        @Override
1073        public void onPackageAppeared(String packageName, int reason) {
1074            if (!mImePackageAppeared) {
1075                final PackageManager pm = mContext.getPackageManager();
1076                final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
1077                        new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName),
1078                        getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS),
1079                        getChangingUserId());
1080                // No need to lock this because we access it only on getRegisteredHandler().
1081                if (!services.isEmpty()) {
1082                    mImePackageAppeared = true;
1083                }
1084            }
1085            // No need to lock this because we access it only on getRegisteredHandler().
1086            mChangedPackages.add(packageName);
1087        }
1088
1089        @Override
1090        public void onPackageDisappeared(String packageName, int reason) {
1091            // No need to lock this because we access it only on getRegisteredHandler().
1092            mChangedPackages.add(packageName);
1093        }
1094
1095        @Override
1096        public void onPackageModified(String packageName) {
1097            // No need to lock this because we access it only on getRegisteredHandler().
1098            mChangedPackages.add(packageName);
1099        }
1100
1101        @Override
1102        public void onPackagesSuspended(String[] packages) {
1103            // No need to lock this because we access it only on getRegisteredHandler().
1104            for (String packageName : packages) {
1105                mChangedPackages.add(packageName);
1106            }
1107        }
1108
1109        @Override
1110        public void onPackagesUnsuspended(String[] packages) {
1111            // No need to lock this because we access it only on getRegisteredHandler().
1112            for (String packageName : packages) {
1113                mChangedPackages.add(packageName);
1114            }
1115        }
1116
1117        @Override
1118        public void onFinishPackageChanges() {
1119            onFinishPackageChangesInternal();
1120            clearPackageChangeState();
1121        }
1122
1123        private void clearPackageChangeState() {
1124            // No need to lock them because we access these fields only on getRegisteredHandler().
1125            mChangedPackages.clear();
1126            mImePackageAppeared = false;
1127        }
1128
1129        @GuardedBy("mMethodMap")
1130        private boolean shouldRebuildInputMethodListLocked() {
1131            // This method is guaranteed to be called only by getRegisteredHandler().
1132
1133            // If there is any new package that contains at least one IME, then rebuilt the list
1134            // of IMEs.
1135            if (mImePackageAppeared) {
1136                return true;
1137            }
1138
1139            // Otherwise, check if mKnownImePackageNames and mChangedPackages have any intersection.
1140            // TODO: Consider to create a utility method to do the following test. List.retainAll()
1141            // is an option, but it may still do some extra operations that we do not need here.
1142            final int N = mChangedPackages.size();
1143            for (int i = 0; i < N; ++i) {
1144                final String packageName = mChangedPackages.get(i);
1145                if (mKnownImePackageNames.contains(packageName)) {
1146                    return true;
1147                }
1148            }
1149            return false;
1150        }
1151
1152        private void onFinishPackageChangesInternal() {
1153            synchronized (mMethodMap) {
1154                if (!isChangingPackagesOfCurrentUserLocked()) {
1155                    return;
1156                }
1157                if (!shouldRebuildInputMethodListLocked()) {
1158                    return;
1159                }
1160
1161                InputMethodInfo curIm = null;
1162                String curInputMethodId = mSettings.getSelectedInputMethod();
1163                final int N = mMethodList.size();
1164                if (curInputMethodId != null) {
1165                    for (int i=0; i<N; i++) {
1166                        InputMethodInfo imi = mMethodList.get(i);
1167                        final String imiId = imi.getId();
1168                        if (imiId.equals(curInputMethodId)) {
1169                            curIm = imi;
1170                        }
1171
1172                        int change = isPackageDisappearing(imi.getPackageName());
1173                        if (isPackageModified(imi.getPackageName())) {
1174                            mFileManager.deleteAllInputMethodSubtypes(imiId);
1175                        }
1176                        if (change == PACKAGE_TEMPORARY_CHANGE
1177                                || change == PACKAGE_PERMANENT_CHANGE) {
1178                            Slog.i(TAG, "Input method uninstalled, disabling: "
1179                                    + imi.getComponent());
1180                            setInputMethodEnabledLocked(imi.getId(), false);
1181                        }
1182                    }
1183                }
1184
1185                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
1186
1187                boolean changed = false;
1188
1189                if (curIm != null) {
1190                    int change = isPackageDisappearing(curIm.getPackageName());
1191                    if (change == PACKAGE_TEMPORARY_CHANGE
1192                            || change == PACKAGE_PERMANENT_CHANGE) {
1193                        ServiceInfo si = null;
1194                        try {
1195                            si = mIPackageManager.getServiceInfo(
1196                                    curIm.getComponent(), 0, mSettings.getCurrentUserId());
1197                        } catch (RemoteException ex) {
1198                        }
1199                        if (si == null) {
1200                            // Uh oh, current input method is no longer around!
1201                            // Pick another one...
1202                            Slog.i(TAG, "Current input method removed: " + curInputMethodId);
1203                            updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
1204                            if (!chooseNewDefaultIMELocked()) {
1205                                changed = true;
1206                                curIm = null;
1207                                Slog.i(TAG, "Unsetting current input method");
1208                                resetSelectedInputMethodAndSubtypeLocked("");
1209                            }
1210                        }
1211                    }
1212                }
1213
1214                if (curIm == null) {
1215                    // We currently don't have a default input method... is
1216                    // one now available?
1217                    changed = chooseNewDefaultIMELocked();
1218                } else if (!changed && isPackageModified(curIm.getPackageName())) {
1219                    // Even if the current input method is still available, mCurrentSubtype could
1220                    // be obsolete when the package is modified in practice.
1221                    changed = true;
1222                }
1223
1224                if (changed) {
1225                    updateFromSettingsLocked(false);
1226                }
1227            }
1228        }
1229    }
1230
1231    private static final class MethodCallback extends IInputSessionCallback.Stub {
1232        private final InputMethodManagerService mParentIMMS;
1233        private final IInputMethod mMethod;
1234        private final InputChannel mChannel;
1235
1236        MethodCallback(InputMethodManagerService imms, IInputMethod method,
1237                InputChannel channel) {
1238            mParentIMMS = imms;
1239            mMethod = method;
1240            mChannel = channel;
1241        }
1242
1243        @Override
1244        public void sessionCreated(IInputMethodSession session) {
1245            long ident = Binder.clearCallingIdentity();
1246            try {
1247                mParentIMMS.onSessionCreated(mMethod, session, mChannel);
1248            } finally {
1249                Binder.restoreCallingIdentity(ident);
1250            }
1251        }
1252    }
1253
1254    private class HardKeyboardListener
1255            implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
1256        @Override
1257        public void onHardKeyboardStatusChange(boolean available) {
1258            mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
1259                        available ? 1 : 0));
1260        }
1261
1262        public void handleHardKeyboardStatusChange(boolean available) {
1263            if (DEBUG) {
1264                Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
1265            }
1266            synchronized(mMethodMap) {
1267                if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
1268                        && mSwitchingDialog.isShowing()) {
1269                    mSwitchingDialogTitleView.findViewById(
1270                            com.android.internal.R.id.hard_keyboard_section).setVisibility(
1271                                    available ? View.VISIBLE : View.GONE);
1272                }
1273            }
1274        }
1275    }
1276
1277    public static final class Lifecycle extends SystemService {
1278        private InputMethodManagerService mService;
1279
1280        public Lifecycle(Context context) {
1281            super(context);
1282            mService = new InputMethodManagerService(context);
1283        }
1284
1285        @Override
1286        public void onStart() {
1287            LocalServices.addService(InputMethodManagerInternal.class,
1288                    new LocalServiceImpl(mService.mHandler));
1289            publishBinderService(Context.INPUT_METHOD_SERVICE, mService);
1290        }
1291
1292        @Override
1293        public void onSwitchUser(@UserIdInt int userHandle) {
1294            // Called on ActivityManager thread.
1295            // TODO: Dispatch this to a worker thread as needed.
1296            mService.onSwitchUser(userHandle);
1297        }
1298
1299        @Override
1300        public void onBootPhase(int phase) {
1301            // Called on ActivityManager thread.
1302            // TODO: Dispatch this to a worker thread as needed.
1303            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
1304                StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager
1305                        .getService(Context.STATUS_BAR_SERVICE);
1306                mService.systemRunning(statusBarService);
1307            }
1308        }
1309
1310        @Override
1311        public void onUnlockUser(final @UserIdInt int userHandle) {
1312            // Called on ActivityManager thread.
1313            mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER,
1314                    userHandle /* arg1 */, 0 /* arg2 */));
1315        }
1316    }
1317
1318    void onUnlockUser(@UserIdInt int userId) {
1319        synchronized(mMethodMap) {
1320            final int currentUserId = mSettings.getCurrentUserId();
1321            if (DEBUG) {
1322                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
1323            }
1324            if (userId != currentUserId) {
1325                return;
1326            }
1327            mSettings.switchCurrentUser(currentUserId, !mSystemReady);
1328            if (mSystemReady) {
1329                // We need to rebuild IMEs.
1330                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
1331                updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
1332            }
1333        }
1334    }
1335
1336    void onSwitchUser(@UserIdInt int userId) {
1337        synchronized (mMethodMap) {
1338            switchUserLocked(userId);
1339        }
1340    }
1341
1342    public InputMethodManagerService(Context context) {
1343        mIPackageManager = AppGlobals.getPackageManager();
1344        mContext = context;
1345        mRes = context.getResources();
1346        mHandler = new Handler(this);
1347        // Note: SettingsObserver doesn't register observers in its constructor.
1348        mSettingsObserver = new SettingsObserver(mHandler);
1349        mIWindowManager = IWindowManager.Stub.asInterface(
1350                ServiceManager.getService(Context.WINDOW_SERVICE));
1351        mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
1352        mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() {
1353            @Override
1354            public void executeMessage(Message msg) {
1355                handleMessage(msg);
1356            }
1357        }, true /*asyncHandler*/);
1358        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
1359        mUserManager = mContext.getSystemService(UserManager.class);
1360        mHardKeyboardListener = new HardKeyboardListener();
1361        mHasFeature = context.getPackageManager().hasSystemFeature(
1362                PackageManager.FEATURE_INPUT_METHODS);
1363        mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
1364        mHardKeyboardBehavior = mContext.getResources().getInteger(
1365                com.android.internal.R.integer.config_externalHardKeyboardBehavior);
1366
1367        Bundle extras = new Bundle();
1368        extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
1369        @ColorInt final int accentColor = mContext.getColor(
1370                com.android.internal.R.color.system_notification_accent_color);
1371        mImeSwitcherNotification =
1372                new Notification.Builder(mContext, SystemNotificationChannels.VIRTUAL_KEYBOARD)
1373                        .setSmallIcon(com.android.internal.R.drawable.ic_notification_ime_default)
1374                        .setWhen(0)
1375                        .setOngoing(true)
1376                        .addExtras(extras)
1377                        .setCategory(Notification.CATEGORY_SYSTEM)
1378                        .setColor(accentColor);
1379
1380        Intent intent = new Intent(ACTION_SHOW_INPUT_METHOD_PICKER)
1381                .setPackage(mContext.getPackageName());
1382        mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
1383
1384        mShowOngoingImeSwitcherForPhones = false;
1385
1386        mNotificationShown = false;
1387        int userId = 0;
1388        try {
1389            userId = ActivityManager.getService().getCurrentUser().id;
1390        } catch (RemoteException e) {
1391            Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
1392        }
1393
1394        // mSettings should be created before buildInputMethodListLocked
1395        mSettings = new InputMethodSettings(
1396                mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
1397
1398        updateCurrentProfileIds();
1399        mFileManager = new InputMethodFileManager(mMethodMap, userId);
1400        mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
1401                mSettings, context);
1402        // Register VR-state listener.
1403        IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE);
1404        if (vrManager != null) {
1405            try {
1406                vrManager.registerListener(mVrStateCallbacks);
1407            } catch (RemoteException e) {
1408                Slog.e(TAG, "Failed to register VR mode state listener.");
1409            }
1410        }
1411    }
1412
1413    private void resetDefaultImeLocked(Context context) {
1414        // Do not reset the default (current) IME when it is a 3rd-party IME
1415        if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
1416            return;
1417        }
1418        final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
1419                context, mSettings.getEnabledInputMethodListLocked());
1420        if (suitableImes.isEmpty()) {
1421            Slog.i(TAG, "No default found");
1422            return;
1423        }
1424        final InputMethodInfo defIm = suitableImes.get(0);
1425        if (DEBUG) {
1426            Slog.i(TAG, "Default found, using " + defIm.getId());
1427        }
1428        setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
1429    }
1430
1431    @GuardedBy("mMethodMap")
1432    private void switchUserLocked(int newUserId) {
1433        if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
1434                + " currentUserId=" + mSettings.getCurrentUserId());
1435
1436        // ContentObserver should be registered again when the user is changed
1437        mSettingsObserver.registerContentObserverLocked(newUserId);
1438
1439        // If the system is not ready or the device is not yed unlocked by the user, then we use
1440        // copy-on-write settings.
1441        final boolean useCopyOnWriteSettings =
1442                !mSystemReady || !mUserManager.isUserUnlockingOrUnlocked(newUserId);
1443        mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings);
1444        updateCurrentProfileIds();
1445        // InputMethodFileManager should be reset when the user is changed
1446        mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
1447        final String defaultImiId = mSettings.getSelectedInputMethod();
1448
1449        if (DEBUG) Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
1450                + " defaultImiId=" + defaultImiId);
1451
1452        // For secondary users, the list of enabled IMEs may not have been updated since the
1453        // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may
1454        // not be empty even if the IME has been uninstalled by the primary user.
1455        // Even in such cases, IMMS works fine because it will find the most applicable
1456        // IME for that user.
1457        final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
1458        mLastSystemLocales = mRes.getConfiguration().getLocales();
1459
1460        // TODO: Is it really possible that switchUserLocked() happens before system ready?
1461        if (mSystemReady) {
1462            hideCurrentInputLocked(0, null);
1463            resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_USER);
1464            buildInputMethodListLocked(initialUserSwitch);
1465            if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
1466                // This is the first time of the user switch and
1467                // set the current ime to the proper one.
1468                resetDefaultImeLocked(mContext);
1469            }
1470            updateFromSettingsLocked(true);
1471            try {
1472                startInputInnerLocked();
1473            } catch (RuntimeException e) {
1474                Slog.w(TAG, "Unexpected exception", e);
1475            }
1476        }
1477
1478        if (initialUserSwitch) {
1479            InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
1480                    mSettings.getEnabledInputMethodListLocked(), newUserId,
1481                    mContext.getBasePackageName());
1482        }
1483
1484        if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
1485                + " selectedIme=" + mSettings.getSelectedInputMethod());
1486    }
1487
1488    void updateCurrentProfileIds() {
1489        mSettings.setCurrentProfileIds(
1490                mUserManager.getProfileIdsWithDisabled(mSettings.getCurrentUserId()));
1491    }
1492
1493    @Override
1494    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
1495            throws RemoteException {
1496        try {
1497            return super.onTransact(code, data, reply, flags);
1498        } catch (RuntimeException e) {
1499            // The input method manager only throws security exceptions, so let's
1500            // log all others.
1501            if (!(e instanceof SecurityException)) {
1502                Slog.wtf(TAG, "Input Method Manager Crash", e);
1503            }
1504            throw e;
1505        }
1506    }
1507
1508    public void systemRunning(StatusBarManagerService statusBar) {
1509        synchronized (mMethodMap) {
1510            if (DEBUG) {
1511                Slog.d(TAG, "--- systemReady");
1512            }
1513            if (!mSystemReady) {
1514                mSystemReady = true;
1515                mLastSystemLocales = mRes.getConfiguration().getLocales();
1516                final int currentUserId = mSettings.getCurrentUserId();
1517                mSettings.switchCurrentUser(currentUserId,
1518                        !mUserManager.isUserUnlockingOrUnlocked(currentUserId));
1519                mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
1520                mNotificationManager = mContext.getSystemService(NotificationManager.class);
1521                mStatusBar = statusBar;
1522                if (mStatusBar != null) {
1523                    mStatusBar.setIconVisibility(mSlotIme, false);
1524                }
1525                updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
1526                mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
1527                        com.android.internal.R.bool.show_ongoing_ime_switcher);
1528                if (mShowOngoingImeSwitcherForPhones) {
1529                    mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
1530                            mHardKeyboardListener);
1531                }
1532
1533                mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
1534                mSettingsObserver.registerContentObserverLocked(currentUserId);
1535
1536                final IntentFilter broadcastFilter = new IntentFilter();
1537                broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
1538                broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
1539                broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
1540                broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
1541                broadcastFilter.addAction(ACTION_SHOW_INPUT_METHOD_PICKER);
1542                mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
1543
1544                final String defaultImiId = mSettings.getSelectedInputMethod();
1545                final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
1546                buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
1547                resetDefaultImeLocked(mContext);
1548                updateFromSettingsLocked(true);
1549                InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
1550                        mSettings.getEnabledInputMethodListLocked(), currentUserId,
1551                        mContext.getBasePackageName());
1552
1553                try {
1554                    startInputInnerLocked();
1555                } catch (RuntimeException e) {
1556                    Slog.w(TAG, "Unexpected exception", e);
1557                }
1558            }
1559        }
1560    }
1561
1562    // ---------------------------------------------------------------------------------------
1563    // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
1564    // 1) it comes from the system process
1565    // 2) the calling process' user id is identical to the current user id IMMS thinks.
1566    private boolean calledFromValidUser() {
1567        final int uid = Binder.getCallingUid();
1568        final int userId = UserHandle.getUserId(uid);
1569        if (DEBUG) {
1570            Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
1571                    + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
1572                    + " calling userId = " + userId + ", foreground user id = "
1573                    + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
1574                    + InputMethodUtils.getApiCallStack());
1575        }
1576        if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
1577            return true;
1578        }
1579
1580        // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the
1581        // foreground user, not for the user of that process. Accordingly InputMethodManagerService
1582        // must not manage background users' states in any functions.
1583        // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded
1584        // by a token.
1585        if (mContext.checkCallingOrSelfPermission(
1586                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
1587                        == PackageManager.PERMISSION_GRANTED) {
1588            if (DEBUG) {
1589                Slog.d(TAG, "--- Access granted because the calling process has "
1590                        + "the INTERACT_ACROSS_USERS_FULL permission");
1591            }
1592            return true;
1593        }
1594        // TODO(b/34886274): The semantics of this verification is actually not well-defined.
1595        Slog.w(TAG, "--- IPC called from background users. Ignore. callers="
1596                + Debug.getCallers(10));
1597        return false;
1598    }
1599
1600
1601    /**
1602     * Returns true iff the caller is identified to be the current input method with the token.
1603     * @param token The window token given to the input method when it was started.
1604     * @return true if and only if non-null valid token is specified.
1605     */
1606    private boolean calledWithValidToken(@Nullable IBinder token) {
1607        if (token == null && Binder.getCallingPid() == Process.myPid()) {
1608            if (DEBUG) {
1609                // TODO(b/34851776): Basically it's the caller's fault if we reach here.
1610                Slog.d(TAG, "Bug 34851776 is detected callers=" + Debug.getCallers(10));
1611            }
1612            return false;
1613        }
1614        if (token == null || token != mCurToken) {
1615            // TODO(b/34886274): The semantics of this verification is actually not well-defined.
1616            Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
1617                    + " uid:" + Binder.getCallingUid() + " token:" + token);
1618            return false;
1619        }
1620        return true;
1621    }
1622
1623    @GuardedBy("mMethodMap")
1624    private boolean bindCurrentInputMethodServiceLocked(
1625            Intent service, ServiceConnection conn, int flags) {
1626        if (service == null || conn == null) {
1627            Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
1628            return false;
1629        }
1630        if (mBindInstantServiceAllowed) {
1631            flags |= Context.BIND_ALLOW_INSTANT;
1632        }
1633        return mContext.bindServiceAsUser(service, conn, flags,
1634                new UserHandle(mSettings.getCurrentUserId()));
1635    }
1636
1637    @Override
1638    public List<InputMethodInfo> getInputMethodList() {
1639        return getInputMethodList(false /* isVrOnly */);
1640    }
1641
1642    public List<InputMethodInfo> getVrInputMethodList() {
1643        return getInputMethodList(true /* isVrOnly */);
1644    }
1645
1646    private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) {
1647        // TODO: Make this work even for non-current users?
1648        if (!calledFromValidUser()) {
1649            return Collections.emptyList();
1650        }
1651        synchronized (mMethodMap) {
1652            ArrayList<InputMethodInfo> methodList = new ArrayList<>();
1653            for (InputMethodInfo info : mMethodList) {
1654
1655                if (info.isVrOnly() == isVrOnly) {
1656                    methodList.add(info);
1657                }
1658            }
1659            return methodList;
1660        }
1661    }
1662
1663    @Override
1664    public List<InputMethodInfo> getEnabledInputMethodList() {
1665        // TODO: Make this work even for non-current users?
1666        if (!calledFromValidUser()) {
1667            return Collections.emptyList();
1668        }
1669        synchronized (mMethodMap) {
1670            return mSettings.getEnabledInputMethodListLocked();
1671        }
1672    }
1673
1674    /**
1675     * @param imiId if null, returns enabled subtypes for the current imi
1676     * @return enabled subtypes of the specified imi
1677     */
1678    @Override
1679    public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
1680            boolean allowsImplicitlySelectedSubtypes) {
1681        // TODO: Make this work even for non-current users?
1682        if (!calledFromValidUser()) {
1683            return Collections.emptyList();
1684        }
1685        synchronized (mMethodMap) {
1686            final InputMethodInfo imi;
1687            if (imiId == null && mCurMethodId != null) {
1688                imi = mMethodMap.get(mCurMethodId);
1689            } else {
1690                imi = mMethodMap.get(imiId);
1691            }
1692            if (imi == null) {
1693                return Collections.emptyList();
1694            }
1695            return mSettings.getEnabledInputMethodSubtypeListLocked(
1696                    mContext, imi, allowsImplicitlySelectedSubtypes);
1697        }
1698    }
1699
1700    @Override
1701    public void addClient(IInputMethodClient client,
1702            IInputContext inputContext, int uid, int pid) {
1703        if (!calledFromValidUser()) {
1704            return;
1705        }
1706        synchronized (mMethodMap) {
1707            mClients.put(client.asBinder(), new ClientState(client,
1708                    inputContext, uid, pid));
1709        }
1710    }
1711
1712    @Override
1713    public void removeClient(IInputMethodClient client) {
1714        if (!calledFromValidUser()) {
1715            return;
1716        }
1717        synchronized (mMethodMap) {
1718            ClientState cs = mClients.remove(client.asBinder());
1719            if (cs != null) {
1720                clearClientSessionLocked(cs);
1721                if (mCurClient == cs) {
1722                    if (mBoundToMethod) {
1723                        mBoundToMethod = false;
1724                        if (mCurMethod != null) {
1725                            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1726                                    MSG_UNBIND_INPUT, mCurMethod));
1727                        }
1728                    }
1729                    mCurClient = null;
1730                }
1731                if (mCurFocusedWindowClient == cs) {
1732                    mCurFocusedWindowClient = null;
1733                }
1734            }
1735        }
1736    }
1737
1738    void executeOrSendMessage(IInterface target, Message msg) {
1739         if (target.asBinder() instanceof Binder) {
1740             mCaller.sendMessage(msg);
1741         } else {
1742             handleMessage(msg);
1743             msg.recycle();
1744         }
1745    }
1746
1747    void unbindCurrentClientLocked(
1748            /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
1749        if (mCurClient != null) {
1750            if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client="
1751                    + mCurClient.client.asBinder());
1752            if (mBoundToMethod) {
1753                mBoundToMethod = false;
1754                if (mCurMethod != null) {
1755                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1756                            MSG_UNBIND_INPUT, mCurMethod));
1757                }
1758            }
1759
1760            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
1761                    MSG_SET_ACTIVE, 0, 0, mCurClient));
1762            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
1763                    MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client));
1764            mCurClient.sessionRequested = false;
1765            mCurClient = null;
1766
1767            hideInputMethodMenuLocked();
1768        }
1769    }
1770
1771    private int getImeShowFlags() {
1772        int flags = 0;
1773        if (mShowForced) {
1774            flags |= InputMethod.SHOW_FORCED
1775                    | InputMethod.SHOW_EXPLICIT;
1776        } else if (mShowExplicitlyRequested) {
1777            flags |= InputMethod.SHOW_EXPLICIT;
1778        }
1779        return flags;
1780    }
1781
1782    private int getAppShowFlags() {
1783        int flags = 0;
1784        if (mShowForced) {
1785            flags |= InputMethodManager.SHOW_FORCED;
1786        } else if (!mShowExplicitlyRequested) {
1787            flags |= InputMethodManager.SHOW_IMPLICIT;
1788        }
1789        return flags;
1790    }
1791
1792    @GuardedBy("mMethodMap")
1793    @NonNull
1794    InputBindResult attachNewInputLocked(
1795            /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
1796        if (!mBoundToMethod) {
1797            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1798                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
1799            mBoundToMethod = true;
1800        }
1801
1802        final Binder startInputToken = new Binder();
1803        final StartInputInfo info = new StartInputInfo(mCurToken, mCurId, startInputReason,
1804                !initial, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
1805                mCurSeq);
1806        mStartInputMap.put(startInputToken, info);
1807        mStartInputHistory.addEntry(info);
1808
1809        final SessionState session = mCurClient.curSession;
1810        executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
1811                MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
1812                startInputToken, session, mCurInputContext, mCurAttribute));
1813        if (mShowRequested) {
1814            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
1815            showCurrentInputLocked(getAppShowFlags(), null);
1816        }
1817        return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
1818                session.session, (session.channel != null ? session.channel.dup() : null),
1819                mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
1820    }
1821
1822    @GuardedBy("mMethodMap")
1823    @NonNull
1824    InputBindResult startInputLocked(
1825            /* @InputMethodClient.StartInputReason */ final int startInputReason,
1826            IInputMethodClient client, IInputContext inputContext,
1827            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1828            @Nullable EditorInfo attribute, int controlFlags) {
1829        // If no method is currently selected, do nothing.
1830        if (mCurMethodId == null) {
1831            return InputBindResult.NO_IME;
1832        }
1833
1834        ClientState cs = mClients.get(client.asBinder());
1835        if (cs == null) {
1836            throw new IllegalArgumentException("unknown client "
1837                    + client.asBinder());
1838        }
1839
1840        if (attribute == null) {
1841            Slog.w(TAG, "Ignoring startInput with null EditorInfo."
1842                    + " uid=" + cs.uid + " pid=" + cs.pid);
1843            return InputBindResult.NULL_EDITOR_INFO;
1844        }
1845
1846        try {
1847            if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
1848                // Check with the window manager to make sure this client actually
1849                // has a window with focus.  If not, reject.  This is thread safe
1850                // because if the focus changes some time before or after, the
1851                // next client receiving focus that has any interest in input will
1852                // be calling through here after that change happens.
1853                if (DEBUG) {
1854                    Slog.w(TAG, "Starting input on non-focused client " + cs.client
1855                            + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
1856                }
1857                return InputBindResult.NOT_IME_TARGET_WINDOW;
1858            }
1859        } catch (RemoteException e) {
1860        }
1861
1862        return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
1863                controlFlags, startInputReason);
1864    }
1865
1866    @GuardedBy("mMethodMap")
1867    @NonNull
1868    InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
1869            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1870            @NonNull EditorInfo attribute, int controlFlags,
1871            /* @InputMethodClient.StartInputReason */ final int startInputReason) {
1872        // If no method is currently selected, do nothing.
1873        if (mCurMethodId == null) {
1874            return InputBindResult.NO_IME;
1875        }
1876
1877        if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
1878                attribute.packageName)) {
1879            Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
1880                    + " uid=" + cs.uid + " package=" + attribute.packageName);
1881            return InputBindResult.INVALID_PACKAGE_NAME;
1882        }
1883
1884        if (mCurClient != cs) {
1885            // Was the keyguard locked when switching over to the new client?
1886            mCurClientInKeyguard = isKeyguardLocked();
1887            // If the client is changing, we need to switch over to the new
1888            // one.
1889            unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
1890            if (DEBUG) Slog.v(TAG, "switching to client: client="
1891                    + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
1892
1893            // If the screen is on, inform the new client it is active
1894            if (mIsInteractive) {
1895                executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
1896                        MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, cs));
1897            }
1898        }
1899
1900        // Bump up the sequence for this client and attach it.
1901        mCurSeq++;
1902        if (mCurSeq <= 0) mCurSeq = 1;
1903        mCurClient = cs;
1904        mCurInputContext = inputContext;
1905        mCurInputContextMissingMethods = missingMethods;
1906        mCurAttribute = attribute;
1907
1908        // Check if the input method is changing.
1909        if (mCurId != null && mCurId.equals(mCurMethodId)) {
1910            if (cs.curSession != null) {
1911                // Fast case: if we are already connected to the input method,
1912                // then just return it.
1913                return attachNewInputLocked(startInputReason,
1914                        (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
1915            }
1916            if (mHaveConnection) {
1917                if (mCurMethod != null) {
1918                    // Return to client, and we will get back with it when
1919                    // we have had a session made for it.
1920                    requestClientSessionLocked(cs);
1921                    return new InputBindResult(
1922                            InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
1923                            null, null, mCurId, mCurSeq,
1924                            mCurUserActionNotificationSequenceNumber);
1925                } else if (SystemClock.uptimeMillis()
1926                        < (mLastBindTime+TIME_TO_RECONNECT)) {
1927                    // In this case we have connected to the service, but
1928                    // don't yet have its interface.  If it hasn't been too
1929                    // long since we did the connection, we'll return to
1930                    // the client and wait to get the service interface so
1931                    // we can report back.  If it has been too long, we want
1932                    // to fall through so we can try a disconnect/reconnect
1933                    // to see if we can get back in touch with the service.
1934                    return new InputBindResult(
1935                            InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
1936                            null, null, mCurId, mCurSeq,
1937                            mCurUserActionNotificationSequenceNumber);
1938                } else {
1939                    EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
1940                            mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
1941                }
1942            }
1943        }
1944
1945        return startInputInnerLocked();
1946    }
1947
1948    InputBindResult startInputInnerLocked() {
1949        if (mCurMethodId == null) {
1950            return InputBindResult.NO_IME;
1951        }
1952
1953        if (!mSystemReady) {
1954            // If the system is not yet ready, we shouldn't be running third
1955            // party code.
1956            return new InputBindResult(
1957                    InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
1958                    null, null, mCurMethodId, mCurSeq,
1959                    mCurUserActionNotificationSequenceNumber);
1960        }
1961
1962        InputMethodInfo info = mMethodMap.get(mCurMethodId);
1963        if (info == null) {
1964            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1965        }
1966
1967        unbindCurrentMethodLocked(true);
1968
1969        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
1970        mCurIntent.setComponent(info.getComponent());
1971        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
1972                com.android.internal.R.string.input_method_binding_label);
1973        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
1974                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
1975        if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
1976            mLastBindTime = SystemClock.uptimeMillis();
1977            mHaveConnection = true;
1978            mCurId = info.getId();
1979            mCurToken = new Binder();
1980            try {
1981                if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
1982                mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, DEFAULT_DISPLAY);
1983            } catch (RemoteException e) {
1984            }
1985            return new InputBindResult(
1986                    InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
1987                    null, null, mCurId, mCurSeq,
1988                    mCurUserActionNotificationSequenceNumber);
1989        }
1990        mCurIntent = null;
1991        Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent);
1992        return InputBindResult.IME_NOT_CONNECTED;
1993    }
1994
1995    @NonNull
1996    private InputBindResult startInput(
1997            /* @InputMethodClient.StartInputReason */ final int startInputReason,
1998            IInputMethodClient client, IInputContext inputContext,
1999            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
2000            @Nullable EditorInfo attribute, int controlFlags) {
2001        if (!calledFromValidUser()) {
2002            return InputBindResult.INVALID_USER;
2003        }
2004        synchronized (mMethodMap) {
2005            if (DEBUG) {
2006                Slog.v(TAG, "startInput: reason="
2007                        + InputMethodClient.getStartInputReason(startInputReason)
2008                        + " client = " + client.asBinder()
2009                        + " inputContext=" + inputContext
2010                        + " missingMethods="
2011                        + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
2012                        + " attribute=" + attribute
2013                        + " controlFlags=#" + Integer.toHexString(controlFlags));
2014            }
2015            final long ident = Binder.clearCallingIdentity();
2016            try {
2017                return startInputLocked(startInputReason, client, inputContext, missingMethods,
2018                        attribute, controlFlags);
2019            } finally {
2020                Binder.restoreCallingIdentity(ident);
2021            }
2022        }
2023    }
2024
2025    @Override
2026    public void finishInput(IInputMethodClient client) {
2027    }
2028
2029    @Override
2030    public void onServiceConnected(ComponentName name, IBinder service) {
2031        synchronized (mMethodMap) {
2032            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
2033                mCurMethod = IInputMethod.Stub.asInterface(service);
2034                if (mCurToken == null) {
2035                    Slog.w(TAG, "Service connected without a token!");
2036                    unbindCurrentMethodLocked(false);
2037                    return;
2038                }
2039                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
2040                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
2041                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
2042                if (mCurClient != null) {
2043                    clearClientSessionLocked(mCurClient);
2044                    requestClientSessionLocked(mCurClient);
2045                }
2046            }
2047        }
2048    }
2049
2050    void onSessionCreated(IInputMethod method, IInputMethodSession session,
2051            InputChannel channel) {
2052        synchronized (mMethodMap) {
2053            if (mCurMethod != null && method != null
2054                    && mCurMethod.asBinder() == method.asBinder()) {
2055                if (mCurClient != null) {
2056                    clearClientSessionLocked(mCurClient);
2057                    mCurClient.curSession = new SessionState(mCurClient,
2058                            method, session, channel);
2059                    InputBindResult res = attachNewInputLocked(
2060                            InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
2061                    if (res.method != null) {
2062                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
2063                                MSG_BIND_CLIENT, mCurClient.client, res));
2064                    }
2065                    return;
2066                }
2067            }
2068        }
2069
2070        // Session abandoned.  Close its associated input channel.
2071        channel.dispose();
2072    }
2073
2074    void unbindCurrentMethodLocked(boolean savePosition) {
2075        if (mVisibleBound) {
2076            mContext.unbindService(mVisibleConnection);
2077            mVisibleBound = false;
2078        }
2079
2080        if (mHaveConnection) {
2081            mContext.unbindService(this);
2082            mHaveConnection = false;
2083        }
2084
2085        if (mCurToken != null) {
2086            try {
2087                if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
2088                if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) {
2089                    // The current IME is shown. Hence an IME switch (transition) is happening.
2090                    mWindowManagerInternal.saveLastInputMethodWindowForTransition();
2091                }
2092                mIWindowManager.removeWindowToken(mCurToken, DEFAULT_DISPLAY);
2093            } catch (RemoteException e) {
2094            }
2095            mCurToken = null;
2096        }
2097
2098        mCurId = null;
2099        clearCurMethodLocked();
2100    }
2101
2102    void resetCurrentMethodAndClient(
2103            /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
2104        mCurMethodId = null;
2105        unbindCurrentMethodLocked(false);
2106        unbindCurrentClientLocked(unbindClientReason);
2107    }
2108
2109    void requestClientSessionLocked(ClientState cs) {
2110        if (!cs.sessionRequested) {
2111            if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
2112            InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
2113            cs.sessionRequested = true;
2114            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
2115                    MSG_CREATE_SESSION, mCurMethod, channels[1],
2116                    new MethodCallback(this, mCurMethod, channels[0])));
2117        }
2118    }
2119
2120    void clearClientSessionLocked(ClientState cs) {
2121        finishSessionLocked(cs.curSession);
2122        cs.curSession = null;
2123        cs.sessionRequested = false;
2124    }
2125
2126    private void finishSessionLocked(SessionState sessionState) {
2127        if (sessionState != null) {
2128            if (sessionState.session != null) {
2129                try {
2130                    sessionState.session.finishSession();
2131                } catch (RemoteException e) {
2132                    Slog.w(TAG, "Session failed to close due to remote exception", e);
2133                    updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
2134                }
2135                sessionState.session = null;
2136            }
2137            if (sessionState.channel != null) {
2138                sessionState.channel.dispose();
2139                sessionState.channel = null;
2140            }
2141        }
2142    }
2143
2144    void clearCurMethodLocked() {
2145        if (mCurMethod != null) {
2146            for (ClientState cs : mClients.values()) {
2147                clearClientSessionLocked(cs);
2148            }
2149
2150            finishSessionLocked(mEnabledSession);
2151            mEnabledSession = null;
2152            mCurMethod = null;
2153        }
2154        if (mStatusBar != null) {
2155            mStatusBar.setIconVisibility(mSlotIme, false);
2156        }
2157        mInFullscreenMode = false;
2158    }
2159
2160    @Override
2161    public void onServiceDisconnected(ComponentName name) {
2162        // Note that mContext.unbindService(this) does not trigger this.  Hence if we are here the
2163        // disconnection is not intended by IMMS (e.g. triggered because the current IMS crashed),
2164        // which is irregular but can eventually happen for everyone just by continuing using the
2165        // device.  Thus it is important to make sure that all the internal states are properly
2166        // refreshed when this method is called back.  Running
2167        //    adb install -r <APK that implements the current IME>
2168        // would be a good way to trigger such a situation.
2169        synchronized (mMethodMap) {
2170            if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
2171                    + " mCurIntent=" + mCurIntent);
2172            if (mCurMethod != null && mCurIntent != null
2173                    && name.equals(mCurIntent.getComponent())) {
2174                clearCurMethodLocked();
2175                // We consider this to be a new bind attempt, since the system
2176                // should now try to restart the service for us.
2177                mLastBindTime = SystemClock.uptimeMillis();
2178                mShowRequested = mInputShown;
2179                mInputShown = false;
2180                unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_DISCONNECT_IME);
2181            }
2182        }
2183    }
2184
2185    @Override
2186    public void updateStatusIcon(IBinder token, String packageName, int iconId) {
2187        synchronized (mMethodMap) {
2188            if (!calledWithValidToken(token)) {
2189                return;
2190            }
2191            final long ident = Binder.clearCallingIdentity();
2192            try {
2193                if (iconId == 0) {
2194                    if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
2195                    if (mStatusBar != null) {
2196                        mStatusBar.setIconVisibility(mSlotIme, false);
2197                    }
2198                } else if (packageName != null) {
2199                    if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
2200                    CharSequence contentDescription = null;
2201                    try {
2202                        // Use PackageManager to load label
2203                        final PackageManager packageManager = mContext.getPackageManager();
2204                        contentDescription = packageManager.getApplicationLabel(
2205                                mIPackageManager.getApplicationInfo(packageName, 0,
2206                                        mSettings.getCurrentUserId()));
2207                    } catch (RemoteException e) {
2208                        /* ignore */
2209                    }
2210                    if (mStatusBar != null) {
2211                        mStatusBar.setIcon(mSlotIme, packageName, iconId, 0,
2212                                contentDescription  != null
2213                                        ? contentDescription.toString() : null);
2214                        mStatusBar.setIconVisibility(mSlotIme, true);
2215                    }
2216                }
2217            } finally {
2218                Binder.restoreCallingIdentity(ident);
2219            }
2220        }
2221    }
2222
2223    private boolean shouldShowImeSwitcherLocked(int visibility) {
2224        if (!mShowOngoingImeSwitcherForPhones) return false;
2225        if (mSwitchingDialog != null) return false;
2226        if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
2227                && mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false;
2228        if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false;
2229        if (mWindowManagerInternal.isHardKeyboardAvailable()) {
2230            if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) {
2231                // When physical keyboard is attached, we show the ime switcher (or notification if
2232                // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
2233                // exists in the IME switcher dialog.  Might be OK to remove this condition once
2234                // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live.
2235                return true;
2236            }
2237        } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
2238            return false;
2239        }
2240
2241        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
2242        final int N = imis.size();
2243        if (N > 2) return true;
2244        if (N < 1) return false;
2245        int nonAuxCount = 0;
2246        int auxCount = 0;
2247        InputMethodSubtype nonAuxSubtype = null;
2248        InputMethodSubtype auxSubtype = null;
2249        for(int i = 0; i < N; ++i) {
2250            final InputMethodInfo imi = imis.get(i);
2251            final List<InputMethodSubtype> subtypes =
2252                    mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
2253            final int subtypeCount = subtypes.size();
2254            if (subtypeCount == 0) {
2255                ++nonAuxCount;
2256            } else {
2257                for (int j = 0; j < subtypeCount; ++j) {
2258                    final InputMethodSubtype subtype = subtypes.get(j);
2259                    if (!subtype.isAuxiliary()) {
2260                        ++nonAuxCount;
2261                        nonAuxSubtype = subtype;
2262                    } else {
2263                        ++auxCount;
2264                        auxSubtype = subtype;
2265                    }
2266                }
2267            }
2268        }
2269        if (nonAuxCount > 1 || auxCount > 1) {
2270            return true;
2271        } else if (nonAuxCount == 1 && auxCount == 1) {
2272            if (nonAuxSubtype != null && auxSubtype != null
2273                    && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
2274                            || auxSubtype.overridesImplicitlyEnabledSubtype()
2275                            || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
2276                    && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
2277                return false;
2278            }
2279            return true;
2280        }
2281        return false;
2282    }
2283
2284    private boolean isKeyguardLocked() {
2285        return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
2286    }
2287
2288    @BinderThread
2289    @SuppressWarnings("deprecation")
2290    @Override
2291    public void setImeWindowStatus(IBinder token, IBinder startInputToken, int vis,
2292            int backDisposition) {
2293        if (!calledWithValidToken(token)) {
2294            return;
2295        }
2296
2297        final StartInputInfo info;
2298        synchronized (mMethodMap) {
2299            info = mStartInputMap.get(startInputToken);
2300            mImeWindowVis = vis;
2301            mBackDisposition = backDisposition;
2302            updateSystemUiLocked(token, vis, backDisposition);
2303        }
2304
2305        final boolean dismissImeOnBackKeyPressed;
2306        switch (backDisposition) {
2307            case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
2308                dismissImeOnBackKeyPressed = true;
2309                break;
2310            case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
2311                dismissImeOnBackKeyPressed = false;
2312                break;
2313            default:
2314            case InputMethodService.BACK_DISPOSITION_DEFAULT:
2315                dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0);
2316                break;
2317        }
2318        mWindowManagerInternal.updateInputMethodWindowStatus(token,
2319                (vis & InputMethodService.IME_VISIBLE) != 0,
2320                dismissImeOnBackKeyPressed, info != null ? info.mTargetWindow : null);
2321    }
2322
2323    private void updateSystemUi(IBinder token, int vis, int backDisposition) {
2324        synchronized (mMethodMap) {
2325            updateSystemUiLocked(token, vis, backDisposition);
2326        }
2327    }
2328
2329    // Caution! This method is called in this class. Handle multi-user carefully
2330    private void updateSystemUiLocked(IBinder token, int vis, int backDisposition) {
2331        if (!calledWithValidToken(token)) {
2332            return;
2333        }
2334
2335        // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
2336        // all updateSystemUi happens on system previlege.
2337        final long ident = Binder.clearCallingIdentity();
2338        try {
2339            // apply policy for binder calls
2340            if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
2341                vis = 0;
2342            }
2343            // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
2344            final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
2345            if (mStatusBar != null) {
2346                mStatusBar.setImeWindowStatus(token, vis, backDisposition,
2347                        needsToShowImeSwitcher);
2348            }
2349            final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2350            if (imi != null && needsToShowImeSwitcher) {
2351                // Used to load label
2352                final CharSequence title = mRes.getText(
2353                        com.android.internal.R.string.select_input_method);
2354                final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
2355                        mContext, imi, mCurrentSubtype);
2356                mImeSwitcherNotification.setContentTitle(title)
2357                        .setContentText(summary)
2358                        .setContentIntent(mImeSwitchPendingIntent);
2359                try {
2360                    if ((mNotificationManager != null)
2361                            && !mIWindowManager.hasNavigationBar()) {
2362                        if (DEBUG) {
2363                            Slog.d(TAG, "--- show notification: label =  " + summary);
2364                        }
2365                        mNotificationManager.notifyAsUser(null,
2366                                SystemMessage.NOTE_SELECT_INPUT_METHOD,
2367                                mImeSwitcherNotification.build(), UserHandle.ALL);
2368                        mNotificationShown = true;
2369                    }
2370                } catch (RemoteException e) {
2371                }
2372            } else {
2373                if (mNotificationShown && mNotificationManager != null) {
2374                    if (DEBUG) {
2375                        Slog.d(TAG, "--- hide notification");
2376                    }
2377                    mNotificationManager.cancelAsUser(null,
2378                            SystemMessage.NOTE_SELECT_INPUT_METHOD, UserHandle.ALL);
2379                    mNotificationShown = false;
2380                }
2381            }
2382        } finally {
2383            Binder.restoreCallingIdentity(ident);
2384        }
2385    }
2386
2387    @Override
2388    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
2389        if (!calledFromValidUser()) {
2390            return;
2391        }
2392        synchronized (mMethodMap) {
2393            final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
2394            for (int i = 0; i < spans.length; ++i) {
2395                SuggestionSpan ss = spans[i];
2396                if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
2397                    mSecureSuggestionSpans.put(ss, currentImi);
2398                }
2399            }
2400        }
2401    }
2402
2403    @Override
2404    public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
2405        if (!calledFromValidUser()) {
2406            return false;
2407        }
2408        synchronized (mMethodMap) {
2409            final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
2410            // TODO: Do not send the intent if the process of the targetImi is already dead.
2411            if (targetImi != null) {
2412                final String[] suggestions = span.getSuggestions();
2413                if (index < 0 || index >= suggestions.length) return false;
2414                final String className = span.getNotificationTargetClassName();
2415                final Intent intent = new Intent();
2416                // Ensures that only a class in the original IME package will receive the
2417                // notification.
2418                intent.setClassName(targetImi.getPackageName(), className);
2419                intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
2420                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
2421                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
2422                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
2423                final long ident = Binder.clearCallingIdentity();
2424                try {
2425                    mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
2426                } finally {
2427                    Binder.restoreCallingIdentity(ident);
2428                }
2429                return true;
2430            }
2431        }
2432        return false;
2433    }
2434
2435    void updateFromSettingsLocked(boolean enabledMayChange) {
2436        updateInputMethodsFromSettingsLocked(enabledMayChange);
2437        updateKeyboardFromSettingsLocked();
2438    }
2439
2440    void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
2441        if (enabledMayChange) {
2442            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2443            for (int i=0; i<enabled.size(); i++) {
2444                // We allow the user to select "disabled until used" apps, so if they
2445                // are enabling one of those here we now need to make it enabled.
2446                InputMethodInfo imm = enabled.get(i);
2447                try {
2448                    ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
2449                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
2450                            mSettings.getCurrentUserId());
2451                    if (ai != null && ai.enabledSetting
2452                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
2453                        if (DEBUG) {
2454                            Slog.d(TAG, "Update state(" + imm.getId()
2455                                    + "): DISABLED_UNTIL_USED -> DEFAULT");
2456                        }
2457                        mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
2458                                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
2459                                PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
2460                                mContext.getBasePackageName());
2461                    }
2462                } catch (RemoteException e) {
2463                }
2464            }
2465        }
2466        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
2467        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
2468        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
2469        // enabled.
2470        String id = mSettings.getSelectedInputMethod();
2471        // There is no input method selected, try to choose new applicable input method.
2472        if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
2473            id = mSettings.getSelectedInputMethod();
2474        }
2475        if (!TextUtils.isEmpty(id)) {
2476            try {
2477                setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
2478            } catch (IllegalArgumentException e) {
2479                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
2480                resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
2481            }
2482            mShortcutInputMethodsAndSubtypes.clear();
2483        } else {
2484            // There is no longer an input method set, so stop any current one.
2485            resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
2486        }
2487        // Here is not the perfect place to reset the switching controller. Ideally
2488        // mSwitchingController and mSettings should be able to share the same state.
2489        // TODO: Make sure that mSwitchingController and mSettings are sharing the
2490        // the same enabled IMEs list.
2491        mSwitchingController.resetCircularListLocked(mContext);
2492
2493    }
2494
2495    public void updateKeyboardFromSettingsLocked() {
2496        mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
2497        if (mSwitchingDialog != null
2498                && mSwitchingDialogTitleView != null
2499                && mSwitchingDialog.isShowing()) {
2500            final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
2501                    com.android.internal.R.id.hard_keyboard_switch);
2502            hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
2503        }
2504    }
2505
2506    /* package */ void setInputMethodLocked(String id, int subtypeId) {
2507        InputMethodInfo info = mMethodMap.get(id);
2508        if (info == null) {
2509            throw new IllegalArgumentException("Unknown id: " + id);
2510        }
2511
2512        // See if we need to notify a subtype change within the same IME.
2513        if (id.equals(mCurMethodId)) {
2514            final int subtypeCount = info.getSubtypeCount();
2515            if (subtypeCount <= 0) {
2516                return;
2517            }
2518            final InputMethodSubtype oldSubtype = mCurrentSubtype;
2519            final InputMethodSubtype newSubtype;
2520            if (subtypeId >= 0 && subtypeId < subtypeCount) {
2521                newSubtype = info.getSubtypeAt(subtypeId);
2522            } else {
2523                // If subtype is null, try to find the most applicable one from
2524                // getCurrentInputMethodSubtype.
2525                newSubtype = getCurrentInputMethodSubtypeLocked();
2526            }
2527            if (newSubtype == null || oldSubtype == null) {
2528                Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
2529                        + ", new subtype = " + newSubtype);
2530                return;
2531            }
2532            if (newSubtype != oldSubtype) {
2533                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
2534                if (mCurMethod != null) {
2535                    try {
2536                        updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
2537                        mCurMethod.changeInputMethodSubtype(newSubtype);
2538                    } catch (RemoteException e) {
2539                        Slog.w(TAG, "Failed to call changeInputMethodSubtype");
2540                    }
2541                }
2542            }
2543            return;
2544        }
2545
2546        // Changing to a different IME.
2547        final long ident = Binder.clearCallingIdentity();
2548        try {
2549            // Set a subtype to this input method.
2550            // subtypeId the name of a subtype which will be set.
2551            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
2552            // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
2553            // because mCurMethodId is stored as a history in
2554            // setSelectedInputMethodAndSubtypeLocked().
2555            mCurMethodId = id;
2556
2557            if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
2558                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
2559                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
2560                intent.putExtra("input_method_id", id);
2561                mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
2562            }
2563            unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
2564        } finally {
2565            Binder.restoreCallingIdentity(ident);
2566        }
2567    }
2568
2569    @Override
2570    public boolean showSoftInput(IInputMethodClient client, int flags,
2571            ResultReceiver resultReceiver) {
2572        if (!calledFromValidUser()) {
2573            return false;
2574        }
2575        int uid = Binder.getCallingUid();
2576        long ident = Binder.clearCallingIdentity();
2577        try {
2578            synchronized (mMethodMap) {
2579                if (mCurClient == null || client == null
2580                        || mCurClient.client.asBinder() != client.asBinder()) {
2581                    try {
2582                        // We need to check if this is the current client with
2583                        // focus in the window manager, to allow this call to
2584                        // be made before input is started in it.
2585                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
2586                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
2587                            return false;
2588                        }
2589                    } catch (RemoteException e) {
2590                        return false;
2591                    }
2592                }
2593
2594                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
2595                return showCurrentInputLocked(flags, resultReceiver);
2596            }
2597        } finally {
2598            Binder.restoreCallingIdentity(ident);
2599        }
2600    }
2601
2602    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
2603        mShowRequested = true;
2604        if (mAccessibilityRequestingNoSoftKeyboard) {
2605            return false;
2606        }
2607
2608        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
2609            mShowExplicitlyRequested = true;
2610            mShowForced = true;
2611        } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
2612            mShowExplicitlyRequested = true;
2613        }
2614
2615        if (!mSystemReady) {
2616            return false;
2617        }
2618
2619        boolean res = false;
2620        if (mCurMethod != null) {
2621            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
2622            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
2623                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
2624                    resultReceiver));
2625            mInputShown = true;
2626            if (mHaveConnection && !mVisibleBound) {
2627                bindCurrentInputMethodServiceLocked(
2628                        mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS);
2629                mVisibleBound = true;
2630            }
2631            res = true;
2632        } else if (mHaveConnection && SystemClock.uptimeMillis()
2633                >= (mLastBindTime+TIME_TO_RECONNECT)) {
2634            // The client has asked to have the input method shown, but
2635            // we have been sitting here too long with a connection to the
2636            // service and no interface received, so let's disconnect/connect
2637            // to try to prod things along.
2638            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
2639                    SystemClock.uptimeMillis()-mLastBindTime,1);
2640            Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
2641            mContext.unbindService(this);
2642            bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS);
2643        } else {
2644            if (DEBUG) {
2645                Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
2646                        + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
2647            }
2648        }
2649
2650        return res;
2651    }
2652
2653    @Override
2654    public boolean hideSoftInput(IInputMethodClient client, int flags,
2655            ResultReceiver resultReceiver) {
2656        if (!calledFromValidUser()) {
2657            return false;
2658        }
2659        int uid = Binder.getCallingUid();
2660        long ident = Binder.clearCallingIdentity();
2661        try {
2662            synchronized (mMethodMap) {
2663                if (mCurClient == null || client == null
2664                        || mCurClient.client.asBinder() != client.asBinder()) {
2665                    try {
2666                        // We need to check if this is the current client with
2667                        // focus in the window manager, to allow this call to
2668                        // be made before input is started in it.
2669                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
2670                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
2671                                    + uid + ": " + client);
2672                            return false;
2673                        }
2674                    } catch (RemoteException e) {
2675                        return false;
2676                    }
2677                }
2678
2679                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
2680                return hideCurrentInputLocked(flags, resultReceiver);
2681            }
2682        } finally {
2683            Binder.restoreCallingIdentity(ident);
2684        }
2685    }
2686
2687    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
2688        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
2689                && (mShowExplicitlyRequested || mShowForced)) {
2690            if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
2691            return false;
2692        }
2693        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
2694            if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
2695            return false;
2696        }
2697
2698        // There is a chance that IMM#hideSoftInput() is called in a transient state where
2699        // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
2700        // to be updated with the new value sent from IME process.  Even in such a transient state
2701        // historically we have accepted an incoming call of IMM#hideSoftInput() from the
2702        // application process as a valid request, and have even promised such a behavior with CTS
2703        // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
2704        // IMMS#InputShown indicates that the software keyboard is shown.
2705        // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
2706        final boolean shouldHideSoftInput = (mCurMethod != null) && (mInputShown ||
2707                (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
2708        boolean res;
2709        if (shouldHideSoftInput) {
2710            // The IME will report its visible state again after the following message finally
2711            // delivered to the IME process as an IPC.  Hence the inconsistency between
2712            // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
2713            // the final state.
2714            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
2715                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
2716            res = true;
2717        } else {
2718            res = false;
2719        }
2720        if (mHaveConnection && mVisibleBound) {
2721            mContext.unbindService(mVisibleConnection);
2722            mVisibleBound = false;
2723        }
2724        mInputShown = false;
2725        mShowRequested = false;
2726        mShowExplicitlyRequested = false;
2727        mShowForced = false;
2728        return res;
2729    }
2730
2731    @NonNull
2732    @Override
2733    public InputBindResult startInputOrWindowGainedFocus(
2734            /* @InputMethodClient.StartInputReason */ final int startInputReason,
2735            IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
2736            int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
2737            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
2738            int unverifiedTargetSdkVersion) {
2739        final InputBindResult result;
2740        if (windowToken != null) {
2741            result = windowGainedFocus(startInputReason, client, windowToken, controlFlags,
2742                    softInputMode, windowFlags, attribute, inputContext, missingMethods,
2743                    unverifiedTargetSdkVersion);
2744        } else {
2745            result = startInput(startInputReason, client, inputContext, missingMethods, attribute,
2746                    controlFlags);
2747        }
2748        if (result == null) {
2749            // This must never happen, but just in case.
2750            Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
2751                    + InputMethodClient.getStartInputReason(startInputReason)
2752                    + " windowFlags=#" + Integer.toHexString(windowFlags)
2753                    + " editorInfo=" + attribute);
2754            return InputBindResult.NULL;
2755        }
2756        return result;
2757    }
2758
2759    @NonNull
2760    private InputBindResult windowGainedFocus(
2761            /* @InputMethodClient.StartInputReason */ final int startInputReason,
2762            IInputMethodClient client, IBinder windowToken, int controlFlags,
2763            /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
2764            int windowFlags, EditorInfo attribute, IInputContext inputContext,
2765            /* @InputConnectionInspector.missingMethods */  final int missingMethods,
2766            int unverifiedTargetSdkVersion) {
2767        // Needs to check the validity before clearing calling identity
2768        final boolean calledFromValidUser = calledFromValidUser();
2769        InputBindResult res = null;
2770        long ident = Binder.clearCallingIdentity();
2771        try {
2772            synchronized (mMethodMap) {
2773                if (DEBUG) Slog.v(TAG, "windowGainedFocus: reason="
2774                        + InputMethodClient.getStartInputReason(startInputReason)
2775                        + " client=" + client.asBinder()
2776                        + " inputContext=" + inputContext
2777                        + " missingMethods="
2778                        + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
2779                        + " attribute=" + attribute
2780                        + " controlFlags=#" + Integer.toHexString(controlFlags)
2781                        + " softInputMode=" + InputMethodClient.softInputModeToString(softInputMode)
2782                        + " windowFlags=#" + Integer.toHexString(windowFlags)
2783                        + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion);
2784
2785                ClientState cs = mClients.get(client.asBinder());
2786                if (cs == null) {
2787                    throw new IllegalArgumentException("unknown client "
2788                            + client.asBinder());
2789                }
2790
2791                try {
2792                    if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
2793                        // Check with the window manager to make sure this client actually
2794                        // has a window with focus.  If not, reject.  This is thread safe
2795                        // because if the focus changes some time before or after, the
2796                        // next client receiving focus that has any interest in input will
2797                        // be calling through here after that change happens.
2798                        if (DEBUG) {
2799                            Slog.w(TAG, "Focus gain on non-focused client " + cs.client
2800                                    + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
2801                        }
2802                        return InputBindResult.NOT_IME_TARGET_WINDOW;
2803                    }
2804                } catch (RemoteException e) {
2805                }
2806
2807                if (!calledFromValidUser) {
2808                    Slog.w(TAG, "A background user is requesting window. Hiding IME.");
2809                    Slog.w(TAG, "If you want to interect with IME, you need "
2810                            + "android.permission.INTERACT_ACROSS_USERS_FULL");
2811                    hideCurrentInputLocked(0, null);
2812                    return InputBindResult.INVALID_USER;
2813                }
2814
2815                if (mCurFocusedWindow == windowToken) {
2816                    if (DEBUG) {
2817                        Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
2818                                + " attribute=" + attribute + ", token = " + windowToken);
2819                    }
2820                    if (attribute != null) {
2821                        return startInputUncheckedLocked(cs, inputContext, missingMethods,
2822                                attribute, controlFlags, startInputReason);
2823                    }
2824                    return new InputBindResult(
2825                            InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
2826                            null, null, null, -1, -1);
2827                }
2828                mCurFocusedWindow = windowToken;
2829                mCurFocusedWindowSoftInputMode = softInputMode;
2830                mCurFocusedWindowClient = cs;
2831
2832                // Should we auto-show the IME even if the caller has not
2833                // specified what should be done with it?
2834                // We only do this automatically if the window can resize
2835                // to accommodate the IME (so what the user sees will give
2836                // them good context without input information being obscured
2837                // by the IME) or if running on a large screen where there
2838                // is more room for the target window + IME.
2839                final boolean doAutoShow =
2840                        (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
2841                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
2842                        || mRes.getConfiguration().isLayoutSizeAtLeast(
2843                                Configuration.SCREENLAYOUT_SIZE_LARGE);
2844                final boolean isTextEditor =
2845                        (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
2846
2847                // We want to start input before showing the IME, but after closing
2848                // it.  We want to do this after closing it to help the IME disappear
2849                // more quickly (not get stuck behind it initializing itself for the
2850                // new focused input, even if its window wants to hide the IME).
2851                boolean didStart = false;
2852
2853                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
2854                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
2855                        if (!isTextEditor || !doAutoShow) {
2856                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
2857                                // There is no focus view, and this window will
2858                                // be behind any soft input window, so hide the
2859                                // soft input window if it is shown.
2860                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
2861                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
2862                            }
2863                        } else if (isTextEditor && doAutoShow && (softInputMode &
2864                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2865                            // There is a focus view, and we are navigating forward
2866                            // into the window, so show the input window for the user.
2867                            // We only do this automatically if the window can resize
2868                            // to accommodate the IME (so what the user sees will give
2869                            // them good context without input information being obscured
2870                            // by the IME) or if running on a large screen where there
2871                            // is more room for the target window + IME.
2872                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
2873                            if (attribute != null) {
2874                                res = startInputUncheckedLocked(cs, inputContext,
2875                                        missingMethods, attribute, controlFlags, startInputReason);
2876                                didStart = true;
2877                            }
2878                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2879                        }
2880                        break;
2881                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
2882                        // Do nothing.
2883                        break;
2884                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
2885                        if ((softInputMode &
2886                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2887                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
2888                            hideCurrentInputLocked(0, null);
2889                        }
2890                        break;
2891                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
2892                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
2893                        hideCurrentInputLocked(0, null);
2894                        break;
2895                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
2896                        if ((softInputMode &
2897                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2898                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
2899                            if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
2900                                    unverifiedTargetSdkVersion, controlFlags)) {
2901                                if (attribute != null) {
2902                                    res = startInputUncheckedLocked(cs, inputContext,
2903                                            missingMethods, attribute, controlFlags,
2904                                            startInputReason);
2905                                    didStart = true;
2906                                }
2907                                showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2908                            } else {
2909                                Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
2910                                        + " there is no focused view that also returns true from"
2911                                        + " View#onCheckIsTextEditor()");
2912                            }
2913                        }
2914                        break;
2915                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
2916                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
2917                        if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
2918                                unverifiedTargetSdkVersion, controlFlags)) {
2919                            if (attribute != null) {
2920                                res = startInputUncheckedLocked(cs, inputContext, missingMethods,
2921                                        attribute, controlFlags, startInputReason);
2922                                didStart = true;
2923                            }
2924                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2925                        } else {
2926                            Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
2927                                    + " there is no focused view that also returns true from"
2928                                    + " View#onCheckIsTextEditor()");
2929                        }
2930                        break;
2931                }
2932
2933                if (!didStart) {
2934                    if (attribute != null) {
2935                        if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
2936                                || (controlFlags
2937                                & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
2938                            res = startInputUncheckedLocked(cs, inputContext, missingMethods,
2939                                    attribute,
2940                                    controlFlags, startInputReason);
2941                        } else {
2942                            res = InputBindResult.NO_EDITOR;
2943                        }
2944                    } else {
2945                        res = InputBindResult.NULL_EDITOR_INFO;
2946                    }
2947                }
2948            }
2949        } finally {
2950            Binder.restoreCallingIdentity(ident);
2951        }
2952
2953        return res;
2954    }
2955
2956    private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
2957        final int uid = Binder.getCallingUid();
2958        if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
2959            return true;
2960        } else if (mCurFocusedWindowClient != null && client != null
2961                && mCurFocusedWindowClient.client.asBinder() == client.asBinder()) {
2962            return true;
2963        } else if (mCurIntent != null && InputMethodUtils.checkIfPackageBelongsToUid(
2964                mAppOpsManager,
2965                uid,
2966                mCurIntent.getComponent().getPackageName())) {
2967            return true;
2968        } else if (mContext.checkCallingPermission(
2969                android.Manifest.permission.WRITE_SECURE_SETTINGS)
2970                == PackageManager.PERMISSION_GRANTED) {
2971            return true;
2972        }
2973
2974        return false;
2975    }
2976
2977    @Override
2978    public void showInputMethodPickerFromClient(
2979            IInputMethodClient client, int auxiliarySubtypeMode) {
2980        if (!calledFromValidUser()) {
2981            return;
2982        }
2983        synchronized (mMethodMap) {
2984            if(!canShowInputMethodPickerLocked(client)) {
2985                Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
2986                        + Binder.getCallingUid() + ": " + client);
2987                return;
2988            }
2989
2990            // Always call subtype picker, because subtype picker is a superset of input method
2991            // picker.
2992            mHandler.sendMessage(mCaller.obtainMessageI(
2993                    MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode));
2994        }
2995    }
2996
2997    public boolean isInputMethodPickerShownForTest() {
2998        synchronized(mMethodMap) {
2999            if (mSwitchingDialog == null) {
3000                return false;
3001            }
3002            return mSwitchingDialog.isShowing();
3003        }
3004    }
3005
3006    @Override
3007    public void setInputMethod(IBinder token, String id) {
3008        if (!calledFromValidUser()) {
3009            return;
3010        }
3011        setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
3012    }
3013
3014    @Override
3015    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
3016        if (!calledFromValidUser()) {
3017            return;
3018        }
3019        synchronized (mMethodMap) {
3020            if (subtype != null) {
3021                setInputMethodWithSubtypeIdLocked(token, id,
3022                        InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
3023                                subtype.hashCode()));
3024            } else {
3025                setInputMethod(token, id);
3026            }
3027        }
3028    }
3029
3030    @Override
3031    public void showInputMethodAndSubtypeEnablerFromClient(
3032            IInputMethodClient client, String inputMethodId) {
3033        if (!calledFromValidUser()) {
3034            return;
3035        }
3036        synchronized (mMethodMap) {
3037            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
3038                    MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
3039        }
3040    }
3041
3042    @Override
3043    public boolean switchToPreviousInputMethod(IBinder token) {
3044        if (!calledFromValidUser()) {
3045            return false;
3046        }
3047        synchronized (mMethodMap) {
3048            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
3049            final InputMethodInfo lastImi;
3050            if (lastIme != null) {
3051                lastImi = mMethodMap.get(lastIme.first);
3052            } else {
3053                lastImi = null;
3054            }
3055            String targetLastImiId = null;
3056            int subtypeId = NOT_A_SUBTYPE_ID;
3057            if (lastIme != null && lastImi != null) {
3058                final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
3059                final int lastSubtypeHash = Integer.parseInt(lastIme.second);
3060                final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
3061                        : mCurrentSubtype.hashCode();
3062                // If the last IME is the same as the current IME and the last subtype is not
3063                // defined, there is no need to switch to the last IME.
3064                if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
3065                    targetLastImiId = lastIme.first;
3066                    subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
3067                }
3068            }
3069
3070            if (TextUtils.isEmpty(targetLastImiId)
3071                    && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
3072                // This is a safety net. If the currentSubtype can't be added to the history
3073                // and the framework couldn't find the last ime, we will make the last ime be
3074                // the most applicable enabled keyboard subtype of the system imes.
3075                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
3076                if (enabled != null) {
3077                    final int N = enabled.size();
3078                    final String locale = mCurrentSubtype == null
3079                            ? mRes.getConfiguration().locale.toString()
3080                            : mCurrentSubtype.getLocale();
3081                    for (int i = 0; i < N; ++i) {
3082                        final InputMethodInfo imi = enabled.get(i);
3083                        if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
3084                            InputMethodSubtype keyboardSubtype =
3085                                    InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
3086                                            InputMethodUtils.getSubtypes(imi),
3087                                            InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
3088                            if (keyboardSubtype != null) {
3089                                targetLastImiId = imi.getId();
3090                                subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3091                                        imi, keyboardSubtype.hashCode());
3092                                if(keyboardSubtype.getLocale().equals(locale)) {
3093                                    break;
3094                                }
3095                            }
3096                        }
3097                    }
3098                }
3099            }
3100
3101            if (!TextUtils.isEmpty(targetLastImiId)) {
3102                if (DEBUG) {
3103                    Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
3104                            + ", from: " + mCurMethodId + ", " + subtypeId);
3105                }
3106                setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
3107                return true;
3108            } else {
3109                return false;
3110            }
3111        }
3112    }
3113
3114    @Override
3115    public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
3116        if (!calledFromValidUser()) {
3117            return false;
3118        }
3119        synchronized (mMethodMap) {
3120            if (!calledWithValidToken(token)) {
3121                return false;
3122            }
3123            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
3124                    onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype,
3125                    true /* forward */);
3126            if (nextSubtype == null) {
3127                return false;
3128            }
3129            setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
3130                    nextSubtype.mSubtypeId);
3131            return true;
3132        }
3133    }
3134
3135    @Override
3136    public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
3137        if (!calledFromValidUser()) {
3138            return false;
3139        }
3140        synchronized (mMethodMap) {
3141            if (!calledWithValidToken(token)) {
3142                return false;
3143            }
3144            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
3145                    false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype,
3146                    true /* forward */);
3147            if (nextSubtype == null) {
3148                return false;
3149            }
3150            return true;
3151        }
3152    }
3153
3154    @Override
3155    public InputMethodSubtype getLastInputMethodSubtype() {
3156        if (!calledFromValidUser()) {
3157            return null;
3158        }
3159        synchronized (mMethodMap) {
3160            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
3161            // TODO: Handle the case of the last IME with no subtypes
3162            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
3163                    || TextUtils.isEmpty(lastIme.second)) return null;
3164            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
3165            if (lastImi == null) return null;
3166            try {
3167                final int lastSubtypeHash = Integer.parseInt(lastIme.second);
3168                final int lastSubtypeId =
3169                        InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
3170                if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
3171                    return null;
3172                }
3173                return lastImi.getSubtypeAt(lastSubtypeId);
3174            } catch (NumberFormatException e) {
3175                return null;
3176            }
3177        }
3178    }
3179
3180    @Override
3181    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
3182        if (!calledFromValidUser()) {
3183            return;
3184        }
3185        // By this IPC call, only a process which shares the same uid with the IME can add
3186        // additional input method subtypes to the IME.
3187        if (TextUtils.isEmpty(imiId) || subtypes == null) return;
3188        synchronized (mMethodMap) {
3189            if (!mSystemReady) {
3190                return;
3191            }
3192            final InputMethodInfo imi = mMethodMap.get(imiId);
3193            if (imi == null) return;
3194            final String[] packageInfos;
3195            try {
3196                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
3197            } catch (RemoteException e) {
3198                Slog.e(TAG, "Failed to get package infos");
3199                return;
3200            }
3201            if (packageInfos != null) {
3202                final int packageNum = packageInfos.length;
3203                for (int i = 0; i < packageNum; ++i) {
3204                    if (packageInfos[i].equals(imi.getPackageName())) {
3205                        mFileManager.addInputMethodSubtypes(imi, subtypes);
3206                        final long ident = Binder.clearCallingIdentity();
3207                        try {
3208                            buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
3209                        } finally {
3210                            Binder.restoreCallingIdentity(ident);
3211                        }
3212                        return;
3213                    }
3214                }
3215            }
3216        }
3217        return;
3218    }
3219
3220    @Override
3221    public int getInputMethodWindowVisibleHeight() {
3222        return mWindowManagerInternal.getInputMethodWindowVisibleHeight();
3223    }
3224
3225    @Override
3226    public void clearLastInputMethodWindowForTransition(IBinder token) {
3227        if (!calledFromValidUser()) {
3228            return;
3229        }
3230        synchronized (mMethodMap) {
3231            if (!calledWithValidToken(token)) {
3232                return;
3233            }
3234        }
3235        mWindowManagerInternal.clearLastInputMethodWindowForTransition();
3236    }
3237
3238    @Override
3239    public void notifyUserAction(int sequenceNumber) {
3240        if (DEBUG) {
3241            Slog.d(TAG, "Got the notification of a user action. sequenceNumber:" + sequenceNumber);
3242        }
3243        synchronized (mMethodMap) {
3244            if (mCurUserActionNotificationSequenceNumber != sequenceNumber) {
3245                if (DEBUG) {
3246                    Slog.d(TAG, "Ignoring the user action notification due to the sequence number "
3247                            + "mismatch. expected:" + mCurUserActionNotificationSequenceNumber
3248                            + " actual: " + sequenceNumber);
3249                }
3250                return;
3251            }
3252            final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3253            if (imi != null) {
3254                mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
3255            }
3256        }
3257    }
3258
3259    private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
3260        synchronized (mMethodMap) {
3261            setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
3262        }
3263    }
3264
3265    private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
3266        if (token == null) {
3267            if (mContext.checkCallingOrSelfPermission(
3268                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
3269                    != PackageManager.PERMISSION_GRANTED) {
3270                throw new SecurityException(
3271                        "Using null token requires permission "
3272                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
3273            }
3274        } else if (mCurToken != token) {
3275            Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
3276                    + " token: " + token);
3277            return;
3278        }
3279
3280        final long ident = Binder.clearCallingIdentity();
3281        try {
3282            setInputMethodLocked(id, subtypeId);
3283        } finally {
3284            Binder.restoreCallingIdentity(ident);
3285        }
3286    }
3287
3288    @Override
3289    public void hideMySoftInput(IBinder token, int flags) {
3290        if (!calledFromValidUser()) {
3291            return;
3292        }
3293        synchronized (mMethodMap) {
3294            if (!calledWithValidToken(token)) {
3295                return;
3296            }
3297            long ident = Binder.clearCallingIdentity();
3298            try {
3299                hideCurrentInputLocked(flags, null);
3300            } finally {
3301                Binder.restoreCallingIdentity(ident);
3302            }
3303        }
3304    }
3305
3306    @Override
3307    public void showMySoftInput(IBinder token, int flags) {
3308        if (!calledFromValidUser()) {
3309            return;
3310        }
3311        synchronized (mMethodMap) {
3312            if (!calledWithValidToken(token)) {
3313                return;
3314            }
3315            long ident = Binder.clearCallingIdentity();
3316            try {
3317                showCurrentInputLocked(flags, null);
3318            } finally {
3319                Binder.restoreCallingIdentity(ident);
3320            }
3321        }
3322    }
3323
3324    void setEnabledSessionInMainThread(SessionState session) {
3325        if (mEnabledSession != session) {
3326            if (mEnabledSession != null && mEnabledSession.session != null) {
3327                try {
3328                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
3329                    mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
3330                } catch (RemoteException e) {
3331                }
3332            }
3333            mEnabledSession = session;
3334            if (mEnabledSession != null && mEnabledSession.session != null) {
3335                try {
3336                    if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
3337                    mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
3338                } catch (RemoteException e) {
3339                }
3340            }
3341        }
3342    }
3343
3344    @MainThread
3345    @Override
3346    public boolean handleMessage(Message msg) {
3347        SomeArgs args;
3348        switch (msg.what) {
3349            case MSG_SHOW_IM_SUBTYPE_PICKER:
3350                final boolean showAuxSubtypes;
3351                switch (msg.arg1) {
3352                    case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO:
3353                        // This is undocumented so far, but IMM#showInputMethodPicker() has been
3354                        // implemented so that auxiliary subtypes will be excluded when the soft
3355                        // keyboard is invisible.
3356                        showAuxSubtypes = mInputShown;
3357                        break;
3358                    case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
3359                        showAuxSubtypes = true;
3360                        break;
3361                    case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES:
3362                        showAuxSubtypes = false;
3363                        break;
3364                    default:
3365                        Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
3366                        return false;
3367                }
3368                showInputMethodMenu(showAuxSubtypes);
3369                return true;
3370
3371            case MSG_SHOW_IM_SUBTYPE_ENABLER:
3372                showInputMethodAndSubtypeEnabler((String)msg.obj);
3373                return true;
3374
3375            case MSG_SHOW_IM_CONFIG:
3376                showConfigureInputMethods();
3377                return true;
3378
3379            // ---------------------------------------------------------
3380
3381            case MSG_UNBIND_INPUT:
3382                try {
3383                    ((IInputMethod)msg.obj).unbindInput();
3384                } catch (RemoteException e) {
3385                    // There is nothing interesting about the method dying.
3386                }
3387                return true;
3388            case MSG_BIND_INPUT:
3389                args = (SomeArgs)msg.obj;
3390                try {
3391                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
3392                } catch (RemoteException e) {
3393                }
3394                args.recycle();
3395                return true;
3396            case MSG_SHOW_SOFT_INPUT:
3397                args = (SomeArgs)msg.obj;
3398                try {
3399                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
3400                            + msg.arg1 + ", " + args.arg2 + ")");
3401                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
3402                } catch (RemoteException e) {
3403                }
3404                args.recycle();
3405                return true;
3406            case MSG_HIDE_SOFT_INPUT:
3407                args = (SomeArgs)msg.obj;
3408                try {
3409                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
3410                            + args.arg2 + ")");
3411                    ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
3412                } catch (RemoteException e) {
3413                }
3414                args.recycle();
3415                return true;
3416            case MSG_HIDE_CURRENT_INPUT_METHOD:
3417                synchronized (mMethodMap) {
3418                    hideCurrentInputLocked(0, null);
3419                }
3420                return true;
3421            case MSG_ATTACH_TOKEN:
3422                args = (SomeArgs)msg.obj;
3423                try {
3424                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
3425                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
3426                } catch (RemoteException e) {
3427                }
3428                args.recycle();
3429                return true;
3430            case MSG_CREATE_SESSION: {
3431                args = (SomeArgs)msg.obj;
3432                IInputMethod method = (IInputMethod)args.arg1;
3433                InputChannel channel = (InputChannel)args.arg2;
3434                try {
3435                    method.createSession(channel, (IInputSessionCallback)args.arg3);
3436                } catch (RemoteException e) {
3437                } finally {
3438                    // Dispose the channel if the input method is not local to this process
3439                    // because the remote proxy will get its own copy when unparceled.
3440                    if (channel != null && Binder.isProxy(method)) {
3441                        channel.dispose();
3442                    }
3443                }
3444                args.recycle();
3445                return true;
3446            }
3447            // ---------------------------------------------------------
3448
3449            case MSG_START_INPUT: {
3450                final int missingMethods = msg.arg1;
3451                final boolean restarting = msg.arg2 != 0;
3452                args = (SomeArgs) msg.obj;
3453                final IBinder startInputToken = (IBinder) args.arg1;
3454                final SessionState session = (SessionState) args.arg2;
3455                final IInputContext inputContext = (IInputContext) args.arg3;
3456                final EditorInfo editorInfo = (EditorInfo) args.arg4;
3457                try {
3458                    setEnabledSessionInMainThread(session);
3459                    session.method.startInput(startInputToken, inputContext, missingMethods,
3460                            editorInfo, restarting);
3461                } catch (RemoteException e) {
3462                }
3463                args.recycle();
3464                return true;
3465            }
3466
3467            // ---------------------------------------------------------
3468
3469            case MSG_UNBIND_CLIENT:
3470                try {
3471                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
3472                } catch (RemoteException e) {
3473                    // There is nothing interesting about the last client dying.
3474                }
3475                return true;
3476            case MSG_BIND_CLIENT: {
3477                args = (SomeArgs)msg.obj;
3478                IInputMethodClient client = (IInputMethodClient)args.arg1;
3479                InputBindResult res = (InputBindResult)args.arg2;
3480                try {
3481                    client.onBindMethod(res);
3482                } catch (RemoteException e) {
3483                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
3484                } finally {
3485                    // Dispose the channel if the input method is not local to this process
3486                    // because the remote proxy will get its own copy when unparceled.
3487                    if (res.channel != null && Binder.isProxy(client)) {
3488                        res.channel.dispose();
3489                    }
3490                }
3491                args.recycle();
3492                return true;
3493            }
3494            case MSG_SET_ACTIVE:
3495                try {
3496                    ((ClientState)msg.obj).client.setActive(msg.arg1 != 0, msg.arg2 != 0);
3497                } catch (RemoteException e) {
3498                    Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
3499                            + ((ClientState)msg.obj).pid + " uid "
3500                            + ((ClientState)msg.obj).uid);
3501                }
3502                return true;
3503            case MSG_SET_INTERACTIVE:
3504                handleSetInteractive(msg.arg1 != 0);
3505                return true;
3506            case MSG_START_VR_INPUT:
3507                startVrInputMethodNoCheck((ComponentName) msg.obj);
3508                return true;
3509            case MSG_SWITCH_IME:
3510                handleSwitchInputMethod(msg.arg1 != 0);
3511                return true;
3512            case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
3513                final int sequenceNumber = msg.arg1;
3514                final ClientState clientState = (ClientState)msg.obj;
3515                try {
3516                    clientState.client.setUserActionNotificationSequenceNumber(sequenceNumber);
3517                } catch (RemoteException e) {
3518                    Slog.w(TAG, "Got RemoteException sending "
3519                            + "setUserActionNotificationSequenceNumber("
3520                            + sequenceNumber + ") notification to pid "
3521                            + clientState.pid + " uid "
3522                            + clientState.uid);
3523                }
3524                return true;
3525            }
3526            case MSG_REPORT_FULLSCREEN_MODE: {
3527                final boolean fullscreen = msg.arg1 != 0;
3528                final ClientState clientState = (ClientState)msg.obj;
3529                try {
3530                    clientState.client.reportFullscreenMode(fullscreen);
3531                } catch (RemoteException e) {
3532                    Slog.w(TAG, "Got RemoteException sending "
3533                            + "reportFullscreen(" + fullscreen + ") notification to pid="
3534                            + clientState.pid + " uid=" + clientState.uid);
3535                }
3536                return true;
3537            }
3538
3539            // --------------------------------------------------------------
3540            case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
3541                mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
3542                return true;
3543            case MSG_SYSTEM_UNLOCK_USER:
3544                final int userId = msg.arg1;
3545                onUnlockUser(userId);
3546                return true;
3547        }
3548        return false;
3549    }
3550
3551    private void handleSetInteractive(final boolean interactive) {
3552        synchronized (mMethodMap) {
3553            mIsInteractive = interactive;
3554            updateSystemUiLocked(mCurToken, interactive ? mImeWindowVis : 0, mBackDisposition);
3555
3556            // Inform the current client of the change in active status
3557            if (mCurClient != null && mCurClient.client != null) {
3558                executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
3559                        MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, mInFullscreenMode ? 1 : 0,
3560                        mCurClient));
3561            }
3562        }
3563    }
3564
3565    private void handleSwitchInputMethod(final boolean forwardDirection) {
3566        synchronized (mMethodMap) {
3567            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
3568                    false, mMethodMap.get(mCurMethodId), mCurrentSubtype, forwardDirection);
3569            if (nextSubtype == null) {
3570                return;
3571            }
3572            setInputMethodLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
3573            final InputMethodInfo newInputMethodInfo = mMethodMap.get(mCurMethodId);
3574            if (newInputMethodInfo == null) {
3575                return;
3576            }
3577            final CharSequence toastText = InputMethodUtils.getImeAndSubtypeDisplayName(mContext,
3578                    newInputMethodInfo, mCurrentSubtype);
3579            if (!TextUtils.isEmpty(toastText)) {
3580                if (mSubtypeSwitchedByShortCutToast == null) {
3581                    mSubtypeSwitchedByShortCutToast = Toast.makeText(mContext, toastText,
3582                            Toast.LENGTH_SHORT);
3583                } else {
3584                    mSubtypeSwitchedByShortCutToast.setText(toastText);
3585                }
3586                mSubtypeSwitchedByShortCutToast.show();
3587            }
3588        }
3589    }
3590
3591    private boolean chooseNewDefaultIMELocked() {
3592        final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
3593                mSettings.getEnabledInputMethodListLocked());
3594        if (imi != null) {
3595            if (DEBUG) {
3596                Slog.d(TAG, "New default IME was selected: " + imi.getId());
3597            }
3598            resetSelectedInputMethodAndSubtypeLocked(imi.getId());
3599            return true;
3600        }
3601
3602        return false;
3603    }
3604
3605    @PackageManager.ResolveInfoFlags
3606    private int getComponentMatchingFlags(@PackageManager.ResolveInfoFlags int baseFlags) {
3607        synchronized (mMethodMap) {
3608            if (mBindInstantServiceAllowed) {
3609                baseFlags |= PackageManager.MATCH_INSTANT;
3610            }
3611            return baseFlags;
3612        }
3613    }
3614
3615    @GuardedBy("mMethodMap")
3616    void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
3617        if (DEBUG) {
3618            Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
3619                    + " \n ------ caller=" + Debug.getCallers(10));
3620        }
3621        if (!mSystemReady) {
3622            Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
3623            return;
3624        }
3625        mMethodList.clear();
3626        mMethodMap.clear();
3627        mMethodMapUpdateCount++;
3628        mMyPackageMonitor.clearKnownImePackageNamesLocked();
3629
3630        // Use for queryIntentServicesAsUser
3631        final PackageManager pm = mContext.getPackageManager();
3632
3633        // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
3634        // behavior of PackageManager is exactly what we want.  It by default picks up appropriate
3635        // services depending on the unlock state for the specified user.
3636        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
3637                new Intent(InputMethod.SERVICE_INTERFACE),
3638                getComponentMatchingFlags(PackageManager.GET_META_DATA
3639                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
3640                mSettings.getCurrentUserId());
3641
3642        final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
3643                mFileManager.getAllAdditionalInputMethodSubtypes();
3644        for (int i = 0; i < services.size(); ++i) {
3645            ResolveInfo ri = services.get(i);
3646            ServiceInfo si = ri.serviceInfo;
3647            final String imeId = InputMethodInfo.computeId(ri);
3648            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
3649                Slog.w(TAG, "Skipping input method " + imeId
3650                        + ": it does not require the permission "
3651                        + android.Manifest.permission.BIND_INPUT_METHOD);
3652                continue;
3653            }
3654
3655            if (DEBUG) Slog.d(TAG, "Checking " + imeId);
3656
3657            final List<InputMethodSubtype> additionalSubtypes = additionalSubtypeMap.get(imeId);
3658            try {
3659                InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
3660                mMethodList.add(p);
3661                final String id = p.getId();
3662                mMethodMap.put(id, p);
3663
3664                if (DEBUG) {
3665                    Slog.d(TAG, "Found an input method " + p);
3666                }
3667            } catch (Exception e) {
3668                Slog.wtf(TAG, "Unable to load input method " + imeId, e);
3669            }
3670        }
3671
3672        // Construct the set of possible IME packages for onPackageChanged() to avoid false
3673        // negatives when the package state remains to be the same but only the component state is
3674        // changed.
3675        {
3676            // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
3677            // of this query is to avoid false negatives.  PackageManager.MATCH_ALL could be more
3678            // conservative, but it seems we cannot use it for now (Issue 35176630).
3679            final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser(
3680                    new Intent(InputMethod.SERVICE_INTERFACE),
3681                    getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS),
3682                    mSettings.getCurrentUserId());
3683            final int N = allInputMethodServices.size();
3684            for (int i = 0; i < N; ++i) {
3685                final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
3686                if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
3687                    mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName);
3688                }
3689            }
3690        }
3691
3692        boolean reenableMinimumNonAuxSystemImes = false;
3693        // TODO: The following code should find better place to live.
3694        if (!resetDefaultEnabledIme) {
3695            boolean enabledImeFound = false;
3696            boolean enabledNonAuxImeFound = false;
3697            final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
3698            final int N = enabledImes.size();
3699            for (int i = 0; i < N; ++i) {
3700                final InputMethodInfo imi = enabledImes.get(i);
3701                if (mMethodList.contains(imi)) {
3702                    enabledImeFound = true;
3703                    if (!imi.isAuxiliaryIme()) {
3704                        enabledNonAuxImeFound = true;
3705                        break;
3706                    }
3707                }
3708            }
3709            if (!enabledImeFound) {
3710                if (DEBUG) {
3711                    Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
3712                }
3713                resetDefaultEnabledIme = true;
3714                resetSelectedInputMethodAndSubtypeLocked("");
3715            } else if (!enabledNonAuxImeFound) {
3716                if (DEBUG) {
3717                    Slog.i(TAG, "All the enabled non-Aux IMEs are gone. Do partial reset.");
3718                }
3719                reenableMinimumNonAuxSystemImes = true;
3720            }
3721        }
3722
3723        if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
3724            final ArrayList<InputMethodInfo> defaultEnabledIme =
3725                    InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList,
3726                            reenableMinimumNonAuxSystemImes);
3727            final int N = defaultEnabledIme.size();
3728            for (int i = 0; i < N; ++i) {
3729                final InputMethodInfo imi =  defaultEnabledIme.get(i);
3730                if (DEBUG) {
3731                    Slog.d(TAG, "--- enable ime = " + imi);
3732                }
3733                setInputMethodEnabledLocked(imi.getId(), true);
3734            }
3735        }
3736
3737        final String defaultImiId = mSettings.getSelectedInputMethod();
3738        if (!TextUtils.isEmpty(defaultImiId)) {
3739            if (!mMethodMap.containsKey(defaultImiId)) {
3740                Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
3741                if (chooseNewDefaultIMELocked()) {
3742                    updateInputMethodsFromSettingsLocked(true);
3743                }
3744            } else {
3745                // Double check that the default IME is certainly enabled.
3746                setInputMethodEnabledLocked(defaultImiId, true);
3747            }
3748        }
3749        // Here is not the perfect place to reset the switching controller. Ideally
3750        // mSwitchingController and mSettings should be able to share the same state.
3751        // TODO: Make sure that mSwitchingController and mSettings are sharing the
3752        // the same enabled IMEs list.
3753        mSwitchingController.resetCircularListLocked(mContext);
3754    }
3755
3756    // ----------------------------------------------------------------------
3757
3758    private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
3759        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
3760        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
3761                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
3762                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
3763        if (!TextUtils.isEmpty(inputMethodId)) {
3764            intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
3765        }
3766        final int userId;
3767        synchronized (mMethodMap) {
3768            userId = mSettings.getCurrentUserId();
3769        }
3770        mContext.startActivityAsUser(intent, null, UserHandle.of(userId));
3771    }
3772
3773    private void showConfigureInputMethods() {
3774        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
3775        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
3776                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
3777                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
3778        mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
3779    }
3780
3781    private boolean isScreenLocked() {
3782        return mKeyguardManager != null
3783                && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
3784    }
3785
3786    private void showInputMethodMenu(boolean showAuxSubtypes) {
3787        if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
3788
3789        final boolean isScreenLocked = isScreenLocked();
3790
3791        final String lastInputMethodId = mSettings.getSelectedInputMethod();
3792        int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
3793        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
3794
3795        synchronized (mMethodMap) {
3796            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
3797                    mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
3798                            mContext);
3799            if (immis == null || immis.size() == 0) {
3800                return;
3801            }
3802
3803            hideInputMethodMenuLocked();
3804
3805            final List<ImeSubtypeListItem> imList =
3806                    mSwitchingController.getSortedInputMethodAndSubtypeListLocked(
3807                            showAuxSubtypes, isScreenLocked);
3808
3809            if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
3810                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
3811                if (currentSubtype != null) {
3812                    final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
3813                    lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3814                            currentImi, currentSubtype.hashCode());
3815                }
3816            }
3817
3818            final int N = imList.size();
3819            mIms = new InputMethodInfo[N];
3820            mSubtypeIds = new int[N];
3821            int checkedItem = 0;
3822            for (int i = 0; i < N; ++i) {
3823                final ImeSubtypeListItem item = imList.get(i);
3824                mIms[i] = item.mImi;
3825                mSubtypeIds[i] = item.mSubtypeId;
3826                if (mIms[i].getId().equals(lastInputMethodId)) {
3827                    int subtypeId = mSubtypeIds[i];
3828                    if ((subtypeId == NOT_A_SUBTYPE_ID)
3829                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
3830                            || (subtypeId == lastInputMethodSubtypeId)) {
3831                        checkedItem = i;
3832                    }
3833                }
3834            }
3835
3836            final Context settingsContext = new ContextThemeWrapper(
3837                    ActivityThread.currentActivityThread().getSystemUiContext(),
3838                    com.android.internal.R.style.Theme_DeviceDefault_Settings);
3839
3840            mDialogBuilder = new AlertDialog.Builder(settingsContext);
3841            mDialogBuilder.setOnCancelListener(new OnCancelListener() {
3842                @Override
3843                public void onCancel(DialogInterface dialog) {
3844                    hideInputMethodMenu();
3845                }
3846            });
3847
3848            final Context dialogContext = mDialogBuilder.getContext();
3849            final TypedArray a = dialogContext.obtainStyledAttributes(null,
3850                    com.android.internal.R.styleable.DialogPreference,
3851                    com.android.internal.R.attr.alertDialogStyle, 0);
3852            final Drawable dialogIcon = a.getDrawable(
3853                    com.android.internal.R.styleable.DialogPreference_dialogIcon);
3854            a.recycle();
3855
3856            mDialogBuilder.setIcon(dialogIcon);
3857
3858            final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
3859            final View tv = inflater.inflate(
3860                    com.android.internal.R.layout.input_method_switch_dialog_title, null);
3861            mDialogBuilder.setCustomTitle(tv);
3862
3863            // Setup layout for a toggle switch of the hardware keyboard
3864            mSwitchingDialogTitleView = tv;
3865            mSwitchingDialogTitleView
3866                    .findViewById(com.android.internal.R.id.hard_keyboard_section)
3867                    .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
3868                            ? View.VISIBLE : View.GONE);
3869            final Switch hardKeySwitch = (Switch) mSwitchingDialogTitleView.findViewById(
3870                    com.android.internal.R.id.hard_keyboard_switch);
3871            hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
3872            hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
3873                @Override
3874                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
3875                    mSettings.setShowImeWithHardKeyboard(isChecked);
3876                    // Ensure that the input method dialog is dismissed when changing
3877                    // the hardware keyboard state.
3878                    hideInputMethodMenu();
3879                }
3880            });
3881
3882            final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
3883                    com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
3884            final OnClickListener choiceListener = new OnClickListener() {
3885                @Override
3886                public void onClick(final DialogInterface dialog, final int which) {
3887                    synchronized (mMethodMap) {
3888                        if (mIms == null || mIms.length <= which || mSubtypeIds == null
3889                                || mSubtypeIds.length <= which) {
3890                            return;
3891                        }
3892                        final InputMethodInfo im = mIms[which];
3893                        int subtypeId = mSubtypeIds[which];
3894                        adapter.mCheckedItem = which;
3895                        adapter.notifyDataSetChanged();
3896                        hideInputMethodMenu();
3897                        if (im != null) {
3898                            if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
3899                                subtypeId = NOT_A_SUBTYPE_ID;
3900                            }
3901                            setInputMethodLocked(im.getId(), subtypeId);
3902                        }
3903                    }
3904                }
3905            };
3906            mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
3907
3908            mSwitchingDialog = mDialogBuilder.create();
3909            mSwitchingDialog.setCanceledOnTouchOutside(true);
3910            final Window w = mSwitchingDialog.getWindow();
3911            final WindowManager.LayoutParams attrs = w.getAttributes();
3912            w.setType(TYPE_INPUT_METHOD_DIALOG);
3913            // Use an alternate token for the dialog for that window manager can group the token
3914            // with other IME windows based on type vs. grouping based on whichever token happens
3915            // to get selected by the system later on.
3916            attrs.token = mSwitchingDialogToken;
3917            attrs.privateFlags |= PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
3918            attrs.setTitle("Select input method");
3919            w.setAttributes(attrs);
3920            updateSystemUi(mCurToken, mImeWindowVis, mBackDisposition);
3921            mSwitchingDialog.show();
3922        }
3923    }
3924
3925    private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
3926        private final LayoutInflater mInflater;
3927        private final int mTextViewResourceId;
3928        private final List<ImeSubtypeListItem> mItemsList;
3929        public int mCheckedItem;
3930        public ImeSubtypeListAdapter(Context context, int textViewResourceId,
3931                List<ImeSubtypeListItem> itemsList, int checkedItem) {
3932            super(context, textViewResourceId, itemsList);
3933
3934            mTextViewResourceId = textViewResourceId;
3935            mItemsList = itemsList;
3936            mCheckedItem = checkedItem;
3937            mInflater = context.getSystemService(LayoutInflater.class);
3938        }
3939
3940        @Override
3941        public View getView(int position, View convertView, ViewGroup parent) {
3942            final View view = convertView != null ? convertView
3943                    : mInflater.inflate(mTextViewResourceId, null);
3944            if (position < 0 || position >= mItemsList.size()) return view;
3945            final ImeSubtypeListItem item = mItemsList.get(position);
3946            final CharSequence imeName = item.mImeName;
3947            final CharSequence subtypeName = item.mSubtypeName;
3948            final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
3949            final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
3950            if (TextUtils.isEmpty(subtypeName)) {
3951                firstTextView.setText(imeName);
3952                secondTextView.setVisibility(View.GONE);
3953            } else {
3954                firstTextView.setText(subtypeName);
3955                secondTextView.setText(imeName);
3956                secondTextView.setVisibility(View.VISIBLE);
3957            }
3958            final RadioButton radioButton =
3959                    (RadioButton)view.findViewById(com.android.internal.R.id.radio);
3960            radioButton.setChecked(position == mCheckedItem);
3961            return view;
3962        }
3963    }
3964
3965    void hideInputMethodMenu() {
3966        synchronized (mMethodMap) {
3967            hideInputMethodMenuLocked();
3968        }
3969    }
3970
3971    void hideInputMethodMenuLocked() {
3972        if (DEBUG) Slog.v(TAG, "Hide switching menu");
3973
3974        if (mSwitchingDialog != null) {
3975            mSwitchingDialog.dismiss();
3976            mSwitchingDialog = null;
3977            mSwitchingDialogTitleView = null;
3978        }
3979
3980        updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
3981        mDialogBuilder = null;
3982        mIms = null;
3983    }
3984
3985    // ----------------------------------------------------------------------
3986
3987    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
3988        // Make sure this is a valid input method.
3989        InputMethodInfo imm = mMethodMap.get(id);
3990        if (imm == null) {
3991            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
3992        }
3993
3994        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
3995                .getEnabledInputMethodsAndSubtypeListLocked();
3996
3997        if (enabled) {
3998            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
3999                if (pair.first.equals(id)) {
4000                    // We are enabling this input method, but it is already enabled.
4001                    // Nothing to do. The previous state was enabled.
4002                    return true;
4003                }
4004            }
4005            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
4006            // Previous state was disabled.
4007            return false;
4008        } else {
4009            StringBuilder builder = new StringBuilder();
4010            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
4011                    builder, enabledInputMethodsList, id)) {
4012                // Disabled input method is currently selected, switch to another one.
4013                final String selId = mSettings.getSelectedInputMethod();
4014                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
4015                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
4016                    resetSelectedInputMethodAndSubtypeLocked("");
4017                }
4018                // Previous state was enabled.
4019                return true;
4020            } else {
4021                // We are disabling the input method but it is already disabled.
4022                // Nothing to do.  The previous state was disabled.
4023                return false;
4024            }
4025        }
4026    }
4027
4028    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
4029            boolean setSubtypeOnly) {
4030        // Updates to InputMethod are transient in VR mode. Its not included in history.
4031        final boolean isVrInput = imi != null && imi.isVrOnly();
4032        if (!isVrInput) {
4033            // Update the history of InputMethod and Subtype
4034            mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
4035        }
4036
4037        mCurUserActionNotificationSequenceNumber =
4038                Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
4039        if (DEBUG) {
4040            Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
4041                    + mCurUserActionNotificationSequenceNumber);
4042        }
4043
4044        if (mCurClient != null && mCurClient.client != null) {
4045            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
4046                    MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
4047                    mCurUserActionNotificationSequenceNumber, mCurClient));
4048        }
4049
4050        if (isVrInput) {
4051            // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
4052            return;
4053        }
4054
4055        // Set Subtype here
4056        if (imi == null || subtypeId < 0) {
4057            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
4058            mCurrentSubtype = null;
4059        } else {
4060            if (subtypeId < imi.getSubtypeCount()) {
4061                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
4062                mSettings.putSelectedSubtype(subtype.hashCode());
4063                mCurrentSubtype = subtype;
4064            } else {
4065                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
4066                // If the subtype is not specified, choose the most applicable one
4067                mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
4068            }
4069        }
4070
4071        if (!setSubtypeOnly) {
4072            // Set InputMethod here
4073            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
4074        }
4075    }
4076
4077    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
4078        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
4079        int lastSubtypeId = NOT_A_SUBTYPE_ID;
4080        // newDefaultIme is empty when there is no candidate for the selected IME.
4081        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
4082            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
4083            if (subtypeHashCode != null) {
4084                try {
4085                    lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
4086                            imi, Integer.parseInt(subtypeHashCode));
4087                } catch (NumberFormatException e) {
4088                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
4089                }
4090            }
4091        }
4092        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
4093    }
4094
4095    // If there are no selected shortcuts, tries finding the most applicable ones.
4096    private Pair<InputMethodInfo, InputMethodSubtype>
4097            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
4098        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
4099        InputMethodInfo mostApplicableIMI = null;
4100        InputMethodSubtype mostApplicableSubtype = null;
4101        boolean foundInSystemIME = false;
4102
4103        // Search applicable subtype for each InputMethodInfo
4104        for (InputMethodInfo imi: imis) {
4105            final String imiId = imi.getId();
4106            if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
4107                continue;
4108            }
4109            InputMethodSubtype subtype = null;
4110            final List<InputMethodSubtype> enabledSubtypes =
4111                    mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
4112            // 1. Search by the current subtype's locale from enabledSubtypes.
4113            if (mCurrentSubtype != null) {
4114                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
4115                        mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
4116            }
4117            // 2. Search by the system locale from enabledSubtypes.
4118            // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
4119            if (subtype == null) {
4120                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
4121                        mRes, enabledSubtypes, mode, null, true);
4122            }
4123            final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
4124                    InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
4125            final ArrayList<InputMethodSubtype> subtypesForSearch =
4126                    overridingImplicitlyEnabledSubtypes.isEmpty()
4127                            ? InputMethodUtils.getSubtypes(imi)
4128                            : overridingImplicitlyEnabledSubtypes;
4129            // 4. Search by the current subtype's locale from all subtypes.
4130            if (subtype == null && mCurrentSubtype != null) {
4131                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
4132                        mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
4133            }
4134            // 5. Search by the system locale from all subtypes.
4135            // 6. Search the first enabled subtype matched with mode from all subtypes.
4136            if (subtype == null) {
4137                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
4138                        mRes, subtypesForSearch, mode, null, true);
4139            }
4140            if (subtype != null) {
4141                if (imiId.equals(mCurMethodId)) {
4142                    // The current input method is the most applicable IME.
4143                    mostApplicableIMI = imi;
4144                    mostApplicableSubtype = subtype;
4145                    break;
4146                } else if (!foundInSystemIME) {
4147                    // The system input method is 2nd applicable IME.
4148                    mostApplicableIMI = imi;
4149                    mostApplicableSubtype = subtype;
4150                    if ((imi.getServiceInfo().applicationInfo.flags
4151                            & ApplicationInfo.FLAG_SYSTEM) != 0) {
4152                        foundInSystemIME = true;
4153                    }
4154                }
4155            }
4156        }
4157        if (DEBUG) {
4158            if (mostApplicableIMI != null) {
4159                Slog.w(TAG, "Most applicable shortcut input method was:"
4160                        + mostApplicableIMI.getId());
4161                if (mostApplicableSubtype != null) {
4162                    Slog.w(TAG, "Most applicable shortcut input method subtype was:"
4163                            + "," + mostApplicableSubtype.getMode() + ","
4164                            + mostApplicableSubtype.getLocale());
4165                }
4166            }
4167        }
4168        if (mostApplicableIMI != null) {
4169            return new Pair<> (mostApplicableIMI, mostApplicableSubtype);
4170        } else {
4171            return null;
4172        }
4173    }
4174
4175    /**
4176     * @return Return the current subtype of this input method.
4177     */
4178    @Override
4179    public InputMethodSubtype getCurrentInputMethodSubtype() {
4180        // TODO: Make this work even for non-current users?
4181        if (!calledFromValidUser()) {
4182            return null;
4183        }
4184        synchronized (mMethodMap) {
4185            return getCurrentInputMethodSubtypeLocked();
4186        }
4187    }
4188
4189    private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
4190        if (mCurMethodId == null) {
4191            return null;
4192        }
4193        final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
4194        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
4195        if (imi == null || imi.getSubtypeCount() == 0) {
4196            return null;
4197        }
4198        if (!subtypeIsSelected || mCurrentSubtype == null
4199                || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
4200            int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
4201            if (subtypeId == NOT_A_SUBTYPE_ID) {
4202                // If there are no selected subtypes, the framework will try to find
4203                // the most applicable subtype from explicitly or implicitly enabled
4204                // subtypes.
4205                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
4206                        mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
4207                // If there is only one explicitly or implicitly enabled subtype,
4208                // just returns it.
4209                if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
4210                    mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
4211                } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
4212                    mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
4213                            mRes, explicitlyOrImplicitlyEnabledSubtypes,
4214                            InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
4215                    if (mCurrentSubtype == null) {
4216                        mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
4217                                mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
4218                                true);
4219                    }
4220                }
4221            } else {
4222                mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
4223            }
4224        }
4225        return mCurrentSubtype;
4226    }
4227
4228    // TODO: We should change the return type from List to List<Parcelable>
4229    @SuppressWarnings("rawtypes")
4230    @Override
4231    public List getShortcutInputMethodsAndSubtypes() {
4232        synchronized (mMethodMap) {
4233            ArrayList<Object> ret = new ArrayList<>();
4234            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
4235                // If there are no selected shortcut subtypes, the framework will try to find
4236                // the most applicable subtype from all subtypes whose mode is
4237                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
4238                Pair<InputMethodInfo, InputMethodSubtype> info =
4239                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
4240                            InputMethodUtils.SUBTYPE_MODE_VOICE);
4241                if (info != null) {
4242                    ret.add(info.first);
4243                    ret.add(info.second);
4244                }
4245                return ret;
4246            }
4247            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
4248                ret.add(imi);
4249                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
4250                    ret.add(subtype);
4251                }
4252            }
4253            return ret;
4254        }
4255    }
4256
4257    @Override
4258    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
4259        // TODO: Make this work even for non-current users?
4260        if (!calledFromValidUser()) {
4261            return false;
4262        }
4263        synchronized (mMethodMap) {
4264            if (subtype != null && mCurMethodId != null) {
4265                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
4266                int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
4267                if (subtypeId != NOT_A_SUBTYPE_ID) {
4268                    setInputMethodLocked(mCurMethodId, subtypeId);
4269                    return true;
4270                }
4271            }
4272            return false;
4273        }
4274    }
4275
4276    // TODO: Cache the state for each user and reset when the cached user is removed.
4277    private static class InputMethodFileManager {
4278        private static final String SYSTEM_PATH = "system";
4279        private static final String INPUT_METHOD_PATH = "inputmethod";
4280        private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
4281        private static final String NODE_SUBTYPES = "subtypes";
4282        private static final String NODE_SUBTYPE = "subtype";
4283        private static final String NODE_IMI = "imi";
4284        private static final String ATTR_ID = "id";
4285        private static final String ATTR_LABEL = "label";
4286        private static final String ATTR_ICON = "icon";
4287        private static final String ATTR_IME_SUBTYPE_ID = "subtypeId";
4288        private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
4289        private static final String ATTR_IME_SUBTYPE_LANGUAGE_TAG = "languageTag";
4290        private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
4291        private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
4292        private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
4293        private static final String ATTR_IS_ASCII_CAPABLE = "isAsciiCapable";
4294        private final AtomicFile mAdditionalInputMethodSubtypeFile;
4295        private final HashMap<String, InputMethodInfo> mMethodMap;
4296        private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
4297                new HashMap<>();
4298        public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
4299            if (methodMap == null) {
4300                throw new NullPointerException("methodMap is null");
4301            }
4302            mMethodMap = methodMap;
4303            final File systemDir = userId == UserHandle.USER_SYSTEM
4304                    ? new File(Environment.getDataDirectory(), SYSTEM_PATH)
4305                    : Environment.getUserSystemDirectory(userId);
4306            final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
4307            if (!inputMethodDir.exists() && !inputMethodDir.mkdirs()) {
4308                Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
4309            }
4310            final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
4311            mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile, "input-subtypes");
4312            if (!subtypeFile.exists()) {
4313                // If "subtypes.xml" doesn't exist, create a blank file.
4314                writeAdditionalInputMethodSubtypes(
4315                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
4316            } else {
4317                readAdditionalInputMethodSubtypes(
4318                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
4319            }
4320        }
4321
4322        private void deleteAllInputMethodSubtypes(String imiId) {
4323            synchronized (mMethodMap) {
4324                mAdditionalSubtypesMap.remove(imiId);
4325                writeAdditionalInputMethodSubtypes(
4326                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
4327            }
4328        }
4329
4330        public void addInputMethodSubtypes(
4331                InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
4332            synchronized (mMethodMap) {
4333                final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
4334                final int N = additionalSubtypes.length;
4335                for (int i = 0; i < N; ++i) {
4336                    final InputMethodSubtype subtype = additionalSubtypes[i];
4337                    if (!subtypes.contains(subtype)) {
4338                        subtypes.add(subtype);
4339                    } else {
4340                        Slog.w(TAG, "Duplicated subtype definition found: "
4341                                + subtype.getLocale() + ", " + subtype.getMode());
4342                    }
4343                }
4344                mAdditionalSubtypesMap.put(imi.getId(), subtypes);
4345                writeAdditionalInputMethodSubtypes(
4346                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
4347            }
4348        }
4349
4350        public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
4351            synchronized (mMethodMap) {
4352                return mAdditionalSubtypesMap;
4353            }
4354        }
4355
4356        private static void writeAdditionalInputMethodSubtypes(
4357                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
4358                HashMap<String, InputMethodInfo> methodMap) {
4359            // Safety net for the case that this function is called before methodMap is set.
4360            final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
4361            FileOutputStream fos = null;
4362            try {
4363                fos = subtypesFile.startWrite();
4364                final XmlSerializer out = new FastXmlSerializer();
4365                out.setOutput(fos, StandardCharsets.UTF_8.name());
4366                out.startDocument(null, true);
4367                out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
4368                out.startTag(null, NODE_SUBTYPES);
4369                for (String imiId : allSubtypes.keySet()) {
4370                    if (isSetMethodMap && !methodMap.containsKey(imiId)) {
4371                        Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
4372                        continue;
4373                    }
4374                    out.startTag(null, NODE_IMI);
4375                    out.attribute(null, ATTR_ID, imiId);
4376                    final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
4377                    final int N = subtypesList.size();
4378                    for (int i = 0; i < N; ++i) {
4379                        final InputMethodSubtype subtype = subtypesList.get(i);
4380                        out.startTag(null, NODE_SUBTYPE);
4381                        if (subtype.hasSubtypeId()) {
4382                            out.attribute(null, ATTR_IME_SUBTYPE_ID,
4383                                    String.valueOf(subtype.getSubtypeId()));
4384                        }
4385                        out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
4386                        out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
4387                        out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
4388                        out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
4389                                subtype.getLanguageTag());
4390                        out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
4391                        out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
4392                        out.attribute(null, ATTR_IS_AUXILIARY,
4393                                String.valueOf(subtype.isAuxiliary() ? 1 : 0));
4394                        out.attribute(null, ATTR_IS_ASCII_CAPABLE,
4395                                String.valueOf(subtype.isAsciiCapable() ? 1 : 0));
4396                        out.endTag(null, NODE_SUBTYPE);
4397                    }
4398                    out.endTag(null, NODE_IMI);
4399                }
4400                out.endTag(null, NODE_SUBTYPES);
4401                out.endDocument();
4402                subtypesFile.finishWrite(fos);
4403            } catch (java.io.IOException e) {
4404                Slog.w(TAG, "Error writing subtypes", e);
4405                if (fos != null) {
4406                    subtypesFile.failWrite(fos);
4407                }
4408            }
4409        }
4410
4411        private static void readAdditionalInputMethodSubtypes(
4412                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
4413            if (allSubtypes == null || subtypesFile == null) return;
4414            allSubtypes.clear();
4415            try (final FileInputStream fis = subtypesFile.openRead()) {
4416                final XmlPullParser parser = Xml.newPullParser();
4417                parser.setInput(fis, StandardCharsets.UTF_8.name());
4418                int type = parser.getEventType();
4419                // Skip parsing until START_TAG
4420                while ((type = parser.next()) != XmlPullParser.START_TAG
4421                        && type != XmlPullParser.END_DOCUMENT) {}
4422                String firstNodeName = parser.getName();
4423                if (!NODE_SUBTYPES.equals(firstNodeName)) {
4424                    throw new XmlPullParserException("Xml doesn't start with subtypes");
4425                }
4426                final int depth =parser.getDepth();
4427                String currentImiId = null;
4428                ArrayList<InputMethodSubtype> tempSubtypesArray = null;
4429                while (((type = parser.next()) != XmlPullParser.END_TAG
4430                        || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
4431                    if (type != XmlPullParser.START_TAG)
4432                        continue;
4433                    final String nodeName = parser.getName();
4434                    if (NODE_IMI.equals(nodeName)) {
4435                        currentImiId = parser.getAttributeValue(null, ATTR_ID);
4436                        if (TextUtils.isEmpty(currentImiId)) {
4437                            Slog.w(TAG, "Invalid imi id found in subtypes.xml");
4438                            continue;
4439                        }
4440                        tempSubtypesArray = new ArrayList<>();
4441                        allSubtypes.put(currentImiId, tempSubtypesArray);
4442                    } else if (NODE_SUBTYPE.equals(nodeName)) {
4443                        if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
4444                            Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
4445                            continue;
4446                        }
4447                        final int icon = Integer.parseInt(
4448                                parser.getAttributeValue(null, ATTR_ICON));
4449                        final int label = Integer.parseInt(
4450                                parser.getAttributeValue(null, ATTR_LABEL));
4451                        final String imeSubtypeLocale =
4452                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
4453                        final String languageTag =
4454                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG);
4455                        final String imeSubtypeMode =
4456                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
4457                        final String imeSubtypeExtraValue =
4458                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
4459                        final boolean isAuxiliary = "1".equals(String.valueOf(
4460                                parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
4461                        final boolean isAsciiCapable = "1".equals(String.valueOf(
4462                                parser.getAttributeValue(null, ATTR_IS_ASCII_CAPABLE)));
4463                        final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder()
4464                                .setSubtypeNameResId(label)
4465                                .setSubtypeIconResId(icon)
4466                                .setSubtypeLocale(imeSubtypeLocale)
4467                                .setLanguageTag(languageTag)
4468                                .setSubtypeMode(imeSubtypeMode)
4469                                .setSubtypeExtraValue(imeSubtypeExtraValue)
4470                                .setIsAuxiliary(isAuxiliary)
4471                                .setIsAsciiCapable(isAsciiCapable);
4472                        final String subtypeIdString =
4473                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_ID);
4474                        if (subtypeIdString != null) {
4475                            builder.setSubtypeId(Integer.parseInt(subtypeIdString));
4476                        }
4477                        tempSubtypesArray.add(builder.build());
4478                    }
4479                }
4480            } catch (XmlPullParserException | IOException | NumberFormatException e) {
4481                Slog.w(TAG, "Error reading subtypes", e);
4482                return;
4483            }
4484        }
4485    }
4486
4487    private static final class LocalServiceImpl implements InputMethodManagerInternal {
4488        @NonNull
4489        private final Handler mHandler;
4490
4491        LocalServiceImpl(@NonNull final Handler handler) {
4492            mHandler = handler;
4493        }
4494
4495        @Override
4496        public void setInteractive(boolean interactive) {
4497            // Do everything in handler so as not to block the caller.
4498            mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_INTERACTIVE,
4499                    interactive ? 1 : 0, 0));
4500        }
4501
4502        @Override
4503        public void switchInputMethod(boolean forwardDirection) {
4504            // Do everything in handler so as not to block the caller.
4505            mHandler.sendMessage(mHandler.obtainMessage(MSG_SWITCH_IME,
4506                    forwardDirection ? 1 : 0, 0));
4507        }
4508
4509        @Override
4510        public void hideCurrentInputMethod() {
4511            mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
4512            mHandler.sendEmptyMessage(MSG_HIDE_CURRENT_INPUT_METHOD);
4513        }
4514
4515        @Override
4516        public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
4517            mHandler.sendMessage(mHandler.obtainMessage(MSG_START_VR_INPUT, componentName));
4518        }
4519    }
4520
4521    private static String imeWindowStatusToString(final int imeWindowVis) {
4522        final StringBuilder sb = new StringBuilder();
4523        boolean first = true;
4524        if ((imeWindowVis & InputMethodService.IME_ACTIVE) != 0) {
4525            sb.append("Active");
4526            first = false;
4527        }
4528        if ((imeWindowVis & InputMethodService.IME_VISIBLE) != 0) {
4529            if (!first) {
4530                sb.append("|");
4531            }
4532            sb.append("Visible");
4533        }
4534        return sb.toString();
4535    }
4536
4537    @Override
4538    public IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
4539            @Nullable Uri contentUri, @Nullable String packageName) {
4540        if (!calledFromValidUser()) {
4541            return null;
4542        }
4543
4544        if (token == null) {
4545            throw new NullPointerException("token");
4546        }
4547        if (packageName == null) {
4548            throw new NullPointerException("packageName");
4549        }
4550        if (contentUri == null) {
4551            throw new NullPointerException("contentUri");
4552        }
4553        final String contentUriScheme = contentUri.getScheme();
4554        if (!"content".equals(contentUriScheme)) {
4555            throw new InvalidParameterException("contentUri must have content scheme");
4556        }
4557
4558        synchronized (mMethodMap) {
4559            final int uid = Binder.getCallingUid();
4560            if (mCurMethodId == null) {
4561                return null;
4562            }
4563            if (mCurToken != token) {
4564                Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + mCurToken
4565                        + " token=" + token);
4566                return null;
4567            }
4568            // We cannot simply distinguish a bad IME that reports an arbitrary package name from
4569            // an unfortunate IME whose internal state is already obsolete due to the asynchronous
4570            // nature of our system.  Let's compare it with our internal record.
4571            if (!TextUtils.equals(mCurAttribute.packageName, packageName)) {
4572                Slog.e(TAG, "Ignoring createInputContentUriToken mCurAttribute.packageName="
4573                    + mCurAttribute.packageName + " packageName=" + packageName);
4574                return null;
4575            }
4576            // This user ID can never bee spoofed.
4577            final int imeUserId = UserHandle.getUserId(uid);
4578            // This user ID can never bee spoofed.
4579            final int appUserId = UserHandle.getUserId(mCurClient.uid);
4580            // This user ID may be invalid if "contentUri" embedded an invalid user ID.
4581            final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
4582                    imeUserId);
4583            final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
4584            // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
4585            // actually has the right to grant a read permission for "contentUriWithoutUserId" that
4586            // is claimed to belong to "contentUriOwnerUserId".  For example, specifying random
4587            // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
4588            // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
4589            // actually allowed to "uid", which is guaranteed to be the IME's one.
4590            return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
4591                    packageName, contentUriOwnerUserId, appUserId);
4592        }
4593    }
4594
4595    @Override
4596    public void reportFullscreenMode(IBinder token, boolean fullscreen) {
4597        if (!calledFromValidUser()) {
4598            return;
4599        }
4600        synchronized (mMethodMap) {
4601            if (!calledWithValidToken(token)) {
4602                return;
4603            }
4604            if (mCurClient != null && mCurClient.client != null) {
4605                mInFullscreenMode = fullscreen;
4606                executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
4607                        MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, mCurClient));
4608            }
4609        }
4610    }
4611
4612    @Override
4613    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
4614        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
4615
4616        IInputMethod method;
4617        ClientState client;
4618        ClientState focusedWindowClient;
4619
4620        final Printer p = new PrintWriterPrinter(pw);
4621
4622        synchronized (mMethodMap) {
4623            p.println("Current Input Method Manager state:");
4624            int N = mMethodList.size();
4625            p.println("  Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount
4626                    + " mBindInstantServiceAllowed=" + mBindInstantServiceAllowed);
4627            for (int i=0; i<N; i++) {
4628                InputMethodInfo info = mMethodList.get(i);
4629                p.println("  InputMethod #" + i + ":");
4630                info.dump(p, "    ");
4631            }
4632            p.println("  Clients:");
4633            for (ClientState ci : mClients.values()) {
4634                p.println("  Client " + ci + ":");
4635                p.println("    client=" + ci.client);
4636                p.println("    inputContext=" + ci.inputContext);
4637                p.println("    sessionRequested=" + ci.sessionRequested);
4638                p.println("    curSession=" + ci.curSession);
4639            }
4640            p.println("  mCurMethodId=" + mCurMethodId);
4641            client = mCurClient;
4642            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
4643            p.println("  mCurFocusedWindow=" + mCurFocusedWindow
4644                    + " softInputMode=" +
4645                    InputMethodClient.softInputModeToString(mCurFocusedWindowSoftInputMode)
4646                    + " client=" + mCurFocusedWindowClient);
4647            focusedWindowClient = mCurFocusedWindowClient;
4648            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
4649                    + " mBoundToMethod=" + mBoundToMethod);
4650            p.println("  mCurToken=" + mCurToken);
4651            p.println("  mCurIntent=" + mCurIntent);
4652            method = mCurMethod;
4653            p.println("  mCurMethod=" + mCurMethod);
4654            p.println("  mEnabledSession=" + mEnabledSession);
4655            p.println("  mImeWindowVis=" + imeWindowStatusToString(mImeWindowVis));
4656            p.println("  mShowRequested=" + mShowRequested
4657                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
4658                    + " mShowForced=" + mShowForced
4659                    + " mInputShown=" + mInputShown);
4660            p.println("  mInFullscreenMode=" + mInFullscreenMode);
4661            p.println("  mCurUserActionNotificationSequenceNumber="
4662                    + mCurUserActionNotificationSequenceNumber);
4663            p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
4664            p.println("  mSettingsObserver=" + mSettingsObserver);
4665            p.println("  mSwitchingController:");
4666            mSwitchingController.dump(p);
4667            p.println("  mSettings:");
4668            mSettings.dumpLocked(p, "    ");
4669
4670            p.println("  mStartInputHistory:");
4671            mStartInputHistory.dump(pw, "   ");
4672        }
4673
4674        p.println(" ");
4675        if (client != null) {
4676            pw.flush();
4677            try {
4678                TransferPipe.dumpAsync(client.client.asBinder(), fd, args);
4679            } catch (IOException | RemoteException e) {
4680                p.println("Failed to dump input method client: " + e);
4681            }
4682        } else {
4683            p.println("No input method client.");
4684        }
4685
4686        if (focusedWindowClient != null && client != focusedWindowClient) {
4687            p.println(" ");
4688            p.println("Warning: Current input method client doesn't match the last focused. "
4689                    + "window.");
4690            p.println("Dumping input method client in the last focused window just in case.");
4691            p.println(" ");
4692            pw.flush();
4693            try {
4694                TransferPipe.dumpAsync(focusedWindowClient.client.asBinder(), fd, args);
4695            } catch (IOException | RemoteException e) {
4696                p.println("Failed to dump input method client in focused window: " + e);
4697            }
4698        }
4699
4700        p.println(" ");
4701        if (method != null) {
4702            pw.flush();
4703            try {
4704                TransferPipe.dumpAsync(method.asBinder(), fd, args);
4705            } catch (IOException | RemoteException e) {
4706                p.println("Failed to dump input method service: " + e);
4707            }
4708        } else {
4709            p.println("No input method service.");
4710        }
4711    }
4712
4713    @BinderThread
4714    @Override
4715    public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
4716            @Nullable FileDescriptor err,
4717            @NonNull String[] args, @Nullable ShellCallback callback,
4718            @NonNull ResultReceiver resultReceiver) throws RemoteException {
4719        new ShellCommandImpl(this).exec(
4720                this, in, out, err, args, callback, resultReceiver);
4721    }
4722
4723    private static final class ShellCommandImpl extends ShellCommand {
4724        @NonNull
4725        final InputMethodManagerService mService;
4726
4727        ShellCommandImpl(InputMethodManagerService service) {
4728            mService = service;
4729        }
4730
4731        @BinderThread
4732        @ShellCommandResult
4733        @Override
4734        public int onCommand(@Nullable String cmd) {
4735            if ("refresh_debug_properties".equals(cmd)) {
4736                return refreshDebugProperties();
4737            }
4738            if ("set-bind-instant-service-allowed".equals(cmd)) {
4739                return setBindInstantServiceAllowed();
4740            }
4741
4742            // For existing "adb shell ime <command>".
4743            if ("ime".equals(cmd)) {
4744                final String imeCommand = getNextArg();
4745                if (imeCommand == null || "help".equals(imeCommand) || "-h".equals(imeCommand)) {
4746                    onImeCommandHelp();
4747                    return ShellCommandResult.SUCCESS;
4748                }
4749                switch (imeCommand) {
4750                    case "list":
4751                        return mService.handleShellCommandListInputMethods(this);
4752                    case "enable":
4753                        return mService.handleShellCommandEnableDisableInputMethod(this, true);
4754                    case "disable":
4755                        return mService.handleShellCommandEnableDisableInputMethod(this, false);
4756                    case "set":
4757                        return mService.handleShellCommandSetInputMethod(this);
4758                    case "reset":
4759                        return mService.handleShellCommandResetInputMethod(this);
4760                    default:
4761                        getOutPrintWriter().println("Unknown command: " + imeCommand);
4762                        return ShellCommandResult.FAILURE;
4763                }
4764            }
4765
4766            return handleDefaultCommands(cmd);
4767        }
4768
4769        @BinderThread
4770        @ShellCommandResult
4771        private int setBindInstantServiceAllowed() {
4772            return mService.handleSetBindInstantServiceAllowed(this);
4773        }
4774
4775        @BinderThread
4776        @ShellCommandResult
4777        private int refreshDebugProperties() {
4778            DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
4779            return ShellCommandResult.SUCCESS;
4780        }
4781
4782        @BinderThread
4783        @Override
4784        public void onHelp() {
4785            try (PrintWriter pw = getOutPrintWriter()) {
4786                pw.println("InputMethodManagerService commands:");
4787                pw.println("  help");
4788                pw.println("    Prints this help text.");
4789                pw.println("  dump [options]");
4790                pw.println("    Synonym of dumpsys.");
4791                pw.println("  ime <command> [options]");
4792                pw.println("    Manipulate IMEs.  Run \"ime help\" for details.");
4793                pw.println("  set-bind-instant-service-allowed true|false ");
4794                pw.println("    Set whether binding to services provided by instant apps is "
4795                        + "allowed.");
4796            }
4797        }
4798
4799        private void onImeCommandHelp() {
4800            try (IndentingPrintWriter pw =
4801                         new IndentingPrintWriter(getOutPrintWriter(), "  ", 100)) {
4802                pw.println("ime <command>:");
4803                pw.increaseIndent();
4804
4805                pw.println("list [-a] [-s]");
4806                pw.increaseIndent();
4807                pw.println("prints all enabled input methods.");
4808                pw.increaseIndent();
4809                pw.println("-a: see all input methods");
4810                pw.println("-s: only a single summary line of each");
4811                pw.decreaseIndent();
4812                pw.decreaseIndent();
4813
4814                pw.println("enable <ID>");
4815                pw.increaseIndent();
4816                pw.println("allows the given input method ID to be used.");
4817                pw.decreaseIndent();
4818
4819                pw.println("disable <ID>");
4820                pw.increaseIndent();
4821                pw.println("disallows the given input method ID to be used.");
4822                pw.decreaseIndent();
4823
4824                pw.println("set <ID>");
4825                pw.increaseIndent();
4826                pw.println("switches to the given input method ID.");
4827                pw.decreaseIndent();
4828
4829                pw.println("reset");
4830                pw.increaseIndent();
4831                pw.println("reset currently selected/enabled IMEs to the default ones as if "
4832                        + "the device is initially booted with the current locale.");
4833                pw.decreaseIndent();
4834
4835                pw.decreaseIndent();
4836            }
4837        }
4838    }
4839
4840    // ----------------------------------------------------------------------
4841    // Shell command handlers:
4842
4843    /**
4844     * Handles {@code adb shell cmd input_method set-bind-instant-service-allowed}.
4845     *
4846     * @param shellCommand {@link ShellCommand} object that is handling this command.
4847     * @return Exit code of the command.
4848     */
4849    @BinderThread
4850    @RequiresPermission(android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE)
4851    @ShellCommandResult
4852    private int handleSetBindInstantServiceAllowed(@NonNull ShellCommand shellCommand) {
4853        final String allowedString = shellCommand.getNextArgRequired();
4854        if (allowedString == null) {
4855            shellCommand.getErrPrintWriter().println("Error: no true/false specified");
4856            return ShellCommandResult.FAILURE;
4857        }
4858        final boolean allowed = Boolean.parseBoolean(allowedString);
4859        synchronized (mMethodMap) {
4860            if (mContext.checkCallingOrSelfPermission(
4861                    android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE)
4862                    != PackageManager.PERMISSION_GRANTED) {
4863                shellCommand.getErrPrintWriter().print(
4864                        "Caller must have MANAGE_BIND_INSTANT_SERVICE permission");
4865                return ShellCommandResult.FAILURE;
4866            }
4867
4868            if (mBindInstantServiceAllowed == allowed) {
4869                // Nothing to do.
4870                return ShellCommandResult.SUCCESS;
4871            }
4872            mBindInstantServiceAllowed = allowed;
4873
4874            // Rebuild everything.
4875            final long ident = Binder.clearCallingIdentity();
4876            try {
4877                // Reset the current IME
4878                resetSelectedInputMethodAndSubtypeLocked(null);
4879                // Also reset the settings of the current IME
4880                mSettings.putSelectedInputMethod(null);
4881                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
4882                updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
4883            } finally {
4884                Binder.restoreCallingIdentity(ident);
4885            }
4886        }
4887        return ShellCommandResult.SUCCESS;
4888    }
4889
4890    /**
4891     * Handles {@code adb shell ime list}.
4892     * @param shellCommand {@link ShellCommand} object that is handling this command.
4893     * @return Exit code of the command.
4894     */
4895    @BinderThread
4896    @ShellCommandResult
4897    private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) {
4898        boolean all = false;
4899        boolean brief = false;
4900        while (true) {
4901            final String nextOption = shellCommand.getNextOption();
4902            if (nextOption == null) {
4903                break;
4904            }
4905            switch (nextOption) {
4906                case "-a":
4907                    all = true;
4908                    break;
4909                case "-s":
4910                    brief = true;
4911                    break;
4912            }
4913        }
4914        final List<InputMethodInfo> methods = all ?
4915                getInputMethodList() : getEnabledInputMethodList();
4916        final PrintWriter pr = shellCommand.getOutPrintWriter();
4917        final Printer printer = x -> pr.println(x);
4918        final int N = methods.size();
4919        for (int i = 0; i < N; ++i) {
4920            if (brief) {
4921                pr.println(methods.get(i).getId());
4922            } else {
4923                pr.print(methods.get(i).getId()); pr.println(":");
4924                methods.get(i).dump(printer, "  ");
4925            }
4926        }
4927        return ShellCommandResult.SUCCESS;
4928    }
4929
4930    /**
4931     * Handles {@code adb shell ime enable} and {@code adb shell ime disable}.
4932     * @param shellCommand {@link ShellCommand} object that is handling this command.
4933     * @param enabled {@code true} if the command was {@code adb shell ime enable}.
4934     * @return Exit code of the command.
4935     */
4936    @BinderThread
4937    @ShellCommandResult
4938    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
4939    private int handleShellCommandEnableDisableInputMethod(
4940            @NonNull ShellCommand shellCommand, boolean enabled) {
4941        if (!calledFromValidUser()) {
4942            shellCommand.getErrPrintWriter().print(
4943                    "Must be called from the foreground user or with INTERACT_ACROSS_USERS_FULL");
4944            return ShellCommandResult.FAILURE;
4945        }
4946        final String id = shellCommand.getNextArgRequired();
4947
4948        final boolean previouslyEnabled;
4949        synchronized (mMethodMap) {
4950            if (mContext.checkCallingOrSelfPermission(
4951                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
4952                    != PackageManager.PERMISSION_GRANTED) {
4953                shellCommand.getErrPrintWriter().print(
4954                        "Caller must have WRITE_SECURE_SETTINGS permission");
4955                throw new SecurityException(
4956                        "Requires permission "
4957                                + android.Manifest.permission.WRITE_SECURE_SETTINGS);
4958            }
4959
4960            final long ident = Binder.clearCallingIdentity();
4961            try {
4962                previouslyEnabled = setInputMethodEnabledLocked(id, enabled);
4963            } finally {
4964                Binder.restoreCallingIdentity(ident);
4965            }
4966        }
4967        final PrintWriter pr = shellCommand.getOutPrintWriter();
4968        pr.print("Input method ");
4969        pr.print(id);
4970        pr.print(": ");
4971        pr.print((enabled == previouslyEnabled) ? "already " : "now ");
4972        pr.println(enabled ? "enabled" : "disabled");
4973        return ShellCommandResult.SUCCESS;
4974    }
4975
4976    /**
4977     * Handles {@code adb shell ime set}.
4978     * @param shellCommand {@link ShellCommand} object that is handling this command.
4979     * @return Exit code of the command.
4980     */
4981    @BinderThread
4982    @ShellCommandResult
4983    private int handleShellCommandSetInputMethod(@NonNull ShellCommand shellCommand) {
4984        final String id = shellCommand.getNextArgRequired();
4985        setInputMethod(null, id);
4986        final PrintWriter pr = shellCommand.getOutPrintWriter();
4987        pr.print("Input method ");
4988        pr.print(id);
4989        pr.println("  selected");
4990        return ShellCommandResult.SUCCESS;
4991    }
4992
4993    /**
4994     * Handles {@code adb shell ime reset-ime}.
4995     * @param shellCommand {@link ShellCommand} object that is handling this command.
4996     * @return Exit code of the command.
4997     */
4998    @BinderThread
4999    @ShellCommandResult
5000    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
5001    private int handleShellCommandResetInputMethod(@NonNull ShellCommand shellCommand) {
5002        if (!calledFromValidUser()) {
5003            shellCommand.getErrPrintWriter().print(
5004                    "Must be called from the foreground user or with INTERACT_ACROSS_USERS_FULL");
5005            return ShellCommandResult.FAILURE;
5006        }
5007        synchronized (mMethodMap) {
5008            if (mContext.checkCallingOrSelfPermission(
5009                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
5010                    != PackageManager.PERMISSION_GRANTED) {
5011                shellCommand.getErrPrintWriter().print(
5012                        "Caller must have WRITE_SECURE_SETTINGS permission");
5013                throw new SecurityException(
5014                        "Requires permission "
5015                                + android.Manifest.permission.WRITE_SECURE_SETTINGS);
5016            }
5017            final String nextIme;
5018            final List<InputMethodInfo> nextEnabledImes;
5019            final long ident = Binder.clearCallingIdentity();
5020            try {
5021                synchronized (mMethodMap) {
5022                    hideCurrentInputLocked(0, null);
5023                    unbindCurrentMethodLocked(false);
5024                    // Reset the current IME
5025                    resetSelectedInputMethodAndSubtypeLocked(null);
5026                    // Also reset the settings of the current IME
5027                    mSettings.putSelectedInputMethod(null);
5028                    // Disable all enabled IMEs.
5029                    {
5030                        final ArrayList<InputMethodInfo> enabledImes =
5031                                mSettings.getEnabledInputMethodListLocked();
5032                        final int N = enabledImes.size();
5033                        for (int i = 0; i < N; ++i) {
5034                            setInputMethodEnabledLocked(enabledImes.get(i).getId(), false);
5035                        }
5036                    }
5037                    // Re-enable with default enabled IMEs.
5038                    {
5039                        final ArrayList<InputMethodInfo> defaultEnabledIme =
5040                                InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList);
5041                        final int N = defaultEnabledIme.size();
5042                        for (int i = 0; i < N; ++i) {
5043                            setInputMethodEnabledLocked(defaultEnabledIme.get(i).getId(), true);
5044                        }
5045                    }
5046                    updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
5047                    InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
5048                            mSettings.getEnabledInputMethodListLocked(),
5049                            mSettings.getCurrentUserId(),
5050                            mContext.getBasePackageName());
5051                    nextIme = mSettings.getSelectedInputMethod();
5052                    nextEnabledImes = getEnabledInputMethodList();
5053                }
5054            } finally {
5055                Binder.restoreCallingIdentity(ident);
5056            }
5057            final PrintWriter pr = shellCommand.getOutPrintWriter();
5058            pr.println("Reset current and enabled IMEs");
5059            pr.println("Newly selected IME:");
5060            pr.print("  "); pr.println(nextIme);
5061            pr.println("Newly enabled IMEs:");
5062            {
5063                final int N = nextEnabledImes.size();
5064                for (int i = 0; i < N; ++i) {
5065                    pr.print("  ");
5066                    pr.println(nextEnabledImes.get(i).getId());
5067                }
5068            }
5069            return ShellCommandResult.SUCCESS;
5070        }
5071    }
5072}
5073