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