• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.inputmethod;
18 
19 import static android.content.Context.DEVICE_ID_DEFAULT;
20 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
21 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
22 import static android.os.IServiceManager.DUMP_FLAG_PROTO;
23 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
24 import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
25 import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
26 import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
27 import static android.server.inputmethod.InputMethodManagerServiceProto.CONCURRENT_MULTI_USER_MODE_ENABLED;
28 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
29 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
30 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
31 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID;
32 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID;
33 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_SEQ;
34 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN;
35 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN_DISPLAY_ID;
36 import static android.server.inputmethod.InputMethodManagerServiceProto.HAVE_CONNECTION;
37 import static android.server.inputmethod.InputMethodManagerServiceProto.IME_WINDOW_VISIBILITY;
38 import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE;
39 import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE;
40 import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME;
41 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD;
42 import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY;
43 import static android.view.Display.DEFAULT_DISPLAY;
44 import static android.view.Display.INVALID_DISPLAY;
45 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
46 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
47 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
48 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
49 
50 import static com.android.server.inputmethod.ImeProtoLogGroup.IMMS_DEBUG;
51 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
52 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
53 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
54 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
55 import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
56 import static com.android.server.inputmethod.InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
57 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
58 import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
59 import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
60 
61 import static java.lang.annotation.RetentionPolicy.SOURCE;
62 
63 import android.Manifest;
64 import android.annotation.AnyThread;
65 import android.annotation.BinderThread;
66 import android.annotation.DrawableRes;
67 import android.annotation.DurationMillisLong;
68 import android.annotation.IntDef;
69 import android.annotation.NonNull;
70 import android.annotation.Nullable;
71 import android.annotation.UiThread;
72 import android.annotation.UserIdInt;
73 import android.annotation.WorkerThread;
74 import android.app.ActivityManagerInternal;
75 import android.content.BroadcastReceiver;
76 import android.content.ComponentName;
77 import android.content.ContentProvider;
78 import android.content.Context;
79 import android.content.Intent;
80 import android.content.IntentFilter;
81 import android.content.pm.ApplicationInfo;
82 import android.content.pm.PackageManager;
83 import android.content.pm.PackageManagerInternal;
84 import android.content.pm.ResolveInfo;
85 import android.content.pm.ServiceInfo;
86 import android.content.pm.UserInfo;
87 import android.content.res.Resources;
88 import android.graphics.Region;
89 import android.hardware.display.DisplayManagerInternal;
90 import android.hardware.input.InputManager;
91 import android.inputmethodservice.InputMethodService;
92 import android.inputmethodservice.InputMethodService.BackDispositionMode;
93 import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
94 import android.media.AudioManagerInternal;
95 import android.net.Uri;
96 import android.os.Binder;
97 import android.os.Debug;
98 import android.os.Handler;
99 import android.os.IBinder;
100 import android.os.LocaleList;
101 import android.os.Looper;
102 import android.os.Message;
103 import android.os.Process;
104 import android.os.RemoteException;
105 import android.os.ResultReceiver;
106 import android.os.ShellCallback;
107 import android.os.ShellCommand;
108 import android.os.SystemClock;
109 import android.os.Trace;
110 import android.os.UserHandle;
111 import android.os.UserManager;
112 import android.provider.Settings;
113 import android.text.TextUtils;
114 import android.util.ArrayMap;
115 import android.util.ArraySet;
116 import android.util.EventLog;
117 import android.util.IndentingPrintWriter;
118 import android.util.IntArray;
119 import android.util.Pair;
120 import android.util.PrintWriterPrinter;
121 import android.util.Printer;
122 import android.util.Slog;
123 import android.util.SparseArray;
124 import android.util.proto.ProtoOutputStream;
125 import android.view.Display;
126 import android.view.InputChannel;
127 import android.view.InputDevice;
128 import android.view.MotionEvent;
129 import android.view.WindowManager;
130 import android.view.WindowManager.DisplayImePolicy;
131 import android.view.WindowManager.LayoutParams;
132 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
133 import android.view.inputmethod.CursorAnchorInfo;
134 import android.view.inputmethod.EditorInfo;
135 import android.view.inputmethod.Flags;
136 import android.view.inputmethod.ImeTracker;
137 import android.view.inputmethod.InputConnection;
138 import android.view.inputmethod.InputMethod;
139 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
140 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto;
141 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodManagerServiceTraceFileProto;
142 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodManagerServiceTraceProto;
143 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceFileProto;
144 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceProto;
145 import android.view.inputmethod.InputMethodInfo;
146 import android.view.inputmethod.InputMethodManager;
147 import android.view.inputmethod.InputMethodSubtype;
148 import android.window.ImeOnBackInvokedDispatcher;
149 
150 import com.android.internal.annotations.GuardedBy;
151 import com.android.internal.annotations.VisibleForTesting;
152 import com.android.internal.content.PackageMonitor;
153 import com.android.internal.infra.AndroidFuture;
154 import com.android.internal.inputmethod.DirectBootAwareness;
155 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
156 import com.android.internal.inputmethod.IBooleanListener;
157 import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
158 import com.android.internal.inputmethod.IImeTracker;
159 import com.android.internal.inputmethod.IInputContentUriToken;
160 import com.android.internal.inputmethod.IInputMethod;
161 import com.android.internal.inputmethod.IInputMethodClient;
162 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
163 import com.android.internal.inputmethod.IInputMethodSession;
164 import com.android.internal.inputmethod.IInputMethodSessionCallback;
165 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
166 import com.android.internal.inputmethod.IRemoteInputConnection;
167 import com.android.internal.inputmethod.ImeTracing;
168 import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
169 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
170 import com.android.internal.inputmethod.InputBindResult;
171 import com.android.internal.inputmethod.InputMethodDebug;
172 import com.android.internal.inputmethod.InputMethodInfoSafeList;
173 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
174 import com.android.internal.inputmethod.InputMethodSubtypeHandle;
175 import com.android.internal.inputmethod.SoftInputShowHideReason;
176 import com.android.internal.inputmethod.StartInputFlags;
177 import com.android.internal.inputmethod.StartInputReason;
178 import com.android.internal.inputmethod.UnbindReason;
179 import com.android.internal.os.TransferPipe;
180 import com.android.internal.protolog.ProtoLog;
181 import com.android.internal.util.ArrayUtils;
182 import com.android.internal.util.CollectionUtils;
183 import com.android.internal.util.DumpUtils;
184 import com.android.internal.util.Preconditions;
185 import com.android.server.AccessibilityManagerInternal;
186 import com.android.server.EventLogTags;
187 import com.android.server.LocalServices;
188 import com.android.server.ServiceThread;
189 import com.android.server.SystemService;
190 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
191 import com.android.server.input.InputManagerInternal;
192 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
193 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
194 import com.android.server.pm.UserManagerInternal;
195 import com.android.server.statusbar.StatusBarManagerInternal;
196 import com.android.server.utils.PriorityDump;
197 import com.android.server.wm.WindowManagerInternal;
198 
199 import java.io.FileDescriptor;
200 import java.io.IOException;
201 import java.io.PrintWriter;
202 import java.lang.annotation.ElementType;
203 import java.lang.annotation.Retention;
204 import java.lang.annotation.Target;
205 import java.security.InvalidParameterException;
206 import java.util.ArrayList;
207 import java.util.Arrays;
208 import java.util.Collections;
209 import java.util.List;
210 import java.util.Objects;
211 import java.util.OptionalInt;
212 import java.util.WeakHashMap;
213 import java.util.concurrent.CopyOnWriteArrayList;
214 import java.util.concurrent.TimeUnit;
215 import java.util.function.Consumer;
216 import java.util.function.IntFunction;
217 
218 /**
219  * This class provides a system service that manages input methods.
220  */
221 public final class InputMethodManagerService implements IInputMethodManagerImpl.Callback,
222         ZeroJankProxy.Callback, Handler.Callback {
223 
224     // Virtual device id for test.
225     private static final Integer VIRTUAL_STYLUS_ID_FOR_TEST = 999999;
226     static final boolean DEBUG = false;
227     static final String TAG = "InputMethodManagerService";
228     public static final String PROTO_ARG = "--proto";
229 
230     /**
231      * Timeout in milliseconds in {@link #systemRunning()} to make sure that users are initialized
232      * in {@link Lifecycle#initializeUsersAsync(int[])}.
233      */
234     @DurationMillisLong
235     private static final long SYSTEM_READY_USER_INIT_TIMEOUT = 3000;
236 
237     @Retention(SOURCE)
238     @IntDef({ShellCommandResult.SUCCESS, ShellCommandResult.FAILURE})
239     private @interface ShellCommandResult {
240         int SUCCESS = 0;
241         int FAILURE = -1;
242     }
243 
244     /**
245      * Indicates that the annotated field is shared by all the users.
246      *
247      * <p>See b/305849394 for details.</p>
248      */
249     @Retention(SOURCE)
250     @Target({ElementType.FIELD})
251     private @interface SharedByAllUsersField {
252     }
253 
254     /**
255      * Indicates that the annotated field is not yet ready for concurrent multi-user support.
256      *
257      * <p>See b/305849394 for details.</p>
258      */
259     @Retention(SOURCE)
260     @Target({ElementType.FIELD})
261     private @interface MultiUserUnawareField {
262     }
263 
264     private static final int MSG_HIDE_INPUT_METHOD = 1035;
265     private static final int MSG_REMOVE_IME_SURFACE = 1060;
266     private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
267 
268     private static final int MSG_RESET_HANDWRITING = 1090;
269     private static final int MSG_START_HANDWRITING = 1100;
270     private static final int MSG_FINISH_HANDWRITING = 1110;
271     private static final int MSG_REMOVE_HANDWRITING_WINDOW = 1120;
272 
273     private static final int MSG_PREPARE_HANDWRITING_DELEGATION = 1130;
274 
275     private static final int MSG_SET_INTERACTIVE = 3030;
276 
277     private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
278 
279     private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010;
280 
281     private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
282 
283     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
284     private static final String HANDLER_THREAD_NAME = "android.imms";
285     private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2";
286 
287     /**
288      * When set, {@link #startInputUncheckedLocked} will return
289      * {@link InputBindResult#NO_EDITOR} instead of starting an IME connection
290      * unless {@link StartInputFlags#IS_TEXT_EDITOR} is set. This behavior overrides
291      * {@link LayoutParams#SOFT_INPUT_STATE_VISIBLE SOFT_INPUT_STATE_VISIBLE} and
292      * {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE SOFT_INPUT_STATE_ALWAYS_VISIBLE}
293      * starting from {@link android.os.Build.VERSION_CODES#P}.
294      */
295     @SharedByAllUsersField
296     private final boolean mPreventImeStartupUnlessTextEditor;
297 
298     /**
299      * These IMEs are known not to behave well when evicted from memory and thus are exempt
300      * from the IME startup avoidance behavior that is enabled by
301      * {@link #mPreventImeStartupUnlessTextEditor}.
302      */
303     @SharedByAllUsersField
304     @NonNull
305     private final String[] mNonPreemptibleInputMethods;
306 
307     /**
308      * See {@link #shouldEnableConcurrentMultiUserMode(Context)} about when set to be {@code true}.
309      */
310     @SharedByAllUsersField
311     private final boolean mConcurrentMultiUserModeEnabled;
312 
313     /**
314      * Returns {@code true} if the concurrent multi-user mode is enabled.
315      *
316      * <p>Currently not compatible with profiles (e.g. work profile).</p>
317      *
318      * @param context {@link Context} to be used to query
319      *                {@link PackageManager#FEATURE_AUTOMOTIVE}
320      * @return {@code true} if the concurrent multi-user mode is enabled.
321      */
shouldEnableConcurrentMultiUserMode(@onNull Context context)322     static boolean shouldEnableConcurrentMultiUserMode(@NonNull Context context) {
323         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
324                 && UserManager.isVisibleBackgroundUsersEnabled()
325                 && context.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled)
326                 && Flags.concurrentInputMethods();
327     }
328 
329     /**
330      * Figures out the target IME user ID for a given {@link Binder} IPC.
331      *
332      * @param callingProcessUserId the user ID of the calling process
333      * @return the user ID to be used for this {@link Binder} call
334      */
335     @GuardedBy("ImfLock.class")
336     @UserIdInt
337     @BinderThread
resolveImeUserIdLocked(@serIdInt int callingProcessUserId)338     private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId) {
339         return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentImeUserId;
340     }
341 
342     /**
343      * Figures out the targetIMuser for a given {@link Binder} IPC. In case
344      * {@code callingProcessUserId} is SYSTEM user, then it will return the owner of the display
345      * associated with the {@code client} passed as parameter.
346      *
347      * @param callingProcessUserId the user ID of the calling process
348      * @param client               the input method client used to retrieve the user id in case
349      *                             {@code callingProcessUserId} is assigned to SYSTEM user
350      * @return the user ID to be used for this {@link Binder} call
351      */
352     @GuardedBy("ImfLock.class")
353     @UserIdInt
354     @BinderThread
resolveImeUserIdLocked(@serIdInt int callingProcessUserId, @NonNull IInputMethodClient client)355     private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId,
356             @NonNull IInputMethodClient client) {
357         if (mConcurrentMultiUserModeEnabled) {
358             if (callingProcessUserId == UserHandle.USER_SYSTEM) {
359                 final var clientState = mClientController.getClient(client.asBinder());
360                 return mUserManagerInternal.getUserAssignedToDisplay(
361                         clientState.mSelfReportedDisplayId);
362             }
363             return callingProcessUserId;
364         }
365         return mCurrentImeUserId;
366     }
367 
368     /**
369      * Figures out the target IME user ID associated with the given {@code displayId}.
370      *
371      * @param displayId the display ID to be queried about
372      * @return User ID to be used for this {@code displayId}.
373      */
374     @GuardedBy("ImfLock.class")
375     @UserIdInt
resolveImeUserIdFromDisplayIdLocked(int displayId)376     private int resolveImeUserIdFromDisplayIdLocked(int displayId) {
377         return mConcurrentMultiUserModeEnabled
378                 ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentImeUserId;
379     }
380 
381     /**
382      * Figures out the target IME user ID associated with the given {@code windowToken}.
383      *
384      * @param windowToken the Window token to be queried about
385      * @return User ID to be used for this {@code displayId}.
386      */
387     @GuardedBy("ImfLock.class")
388     @UserIdInt
resolveImeUserIdFromWindowLocked(@onNull IBinder windowToken)389     private int resolveImeUserIdFromWindowLocked(@NonNull IBinder windowToken) {
390         if (mConcurrentMultiUserModeEnabled) {
391             final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
392             return mUserManagerInternal.getUserAssignedToDisplay(displayId);
393         }
394         return mCurrentImeUserId;
395     }
396 
397     final Context mContext;
398     final Resources mRes;
399     private final Handler mHandler;
400 
401     private final InputMethodManagerInternal mInputMethodManagerInternal;
402     @NonNull
403     private final Handler mIoHandler;
404 
405     /**
406      * The user ID whose IME should be used if {@link #mConcurrentMultiUserModeEnabled} is
407      * {@code false}, otherwise remains to be the initial value, which is obtained by
408      * {@link ActivityManagerInternal#getCurrentUserId()} while the device is booting up.
409      *
410      * <p>Never get confused with {@link ActivityManagerInternal#getCurrentUserId()}, which is
411      * in general useless when designing and implementing interactions between apps and IMEs.</p>
412      *
413      * <p>You can also not assume that the IME client process belongs to {@link #mCurrentImeUserId}.
414      * A most important outlier is System UI process, which always runs under
415      * {@link UserHandle#USER_SYSTEM} in all the known configurations including Headless System User
416      * Mode (HSUM).</p>
417      */
418     @MultiUserUnawareField
419     @UserIdInt
420     @GuardedBy("ImfLock.class")
421     private int mCurrentImeUserId;
422 
423     /** Holds all user related data */
424     @SharedByAllUsersField
425     private final UserDataRepository mUserDataRepository;
426 
427     final WindowManagerInternal mWindowManagerInternal;
428     private final ActivityManagerInternal mActivityManagerInternal;
429     final PackageManagerInternal mPackageManagerInternal;
430     final InputManagerInternal mInputManagerInternal;
431     final ImePlatformCompatUtils mImePlatformCompatUtils;
432     @SharedByAllUsersField
433     final InputMethodDeviceConfigs mInputMethodDeviceConfigs;
434 
435     private final UserManagerInternal mUserManagerInternal;
436     @MultiUserUnawareField
437     private final InputMethodMenuController mMenuController;
438     private final InputMethodMenuControllerNew mMenuControllerNew;
439 
440     @GuardedBy("ImfLock.class")
441     @SharedByAllUsersField
442     @NonNull
443     private final DefaultImeVisibilityApplier mVisibilityApplier;
444 
445     /**
446      * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
447      *
448      * <p>This field is used only within {@link #handleMessage(Message)} hence synchronization is
449      * not necessary.</p>
450      */
451     @Nullable
452     private AudioManagerInternal mAudioManagerInternal = null;
453     @Nullable
454     private VirtualDeviceManagerInternal mVdmInternal = null;
455     @Nullable
456     private DisplayManagerInternal mDisplayManagerInternal = null;
457 
458     // Mapping from deviceId to the device-specific imeId for that device.
459     @GuardedBy("ImfLock.class")
460     @SharedByAllUsersField
461     private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
462 
463     @Nullable
464     private StatusBarManagerInternal mStatusBarManagerInternal;
465     @SharedByAllUsersField
466     private boolean mShowOngoingImeSwitcherForPhones;
467     @GuardedBy("ImfLock.class")
468     @MultiUserUnawareField
469     private final HandwritingModeController mHwController;
470     @GuardedBy("ImfLock.class")
471     @SharedByAllUsersField
472     private IntArray mStylusIds;
473 
474     private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
475         /**
476          * {@inheritDoc}
477          */
478         @Override
479         public void dumpToProto(ProtoOutputStream proto, @Nullable byte[] icProto) {
480             dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
481         }
482     };
483 
484     static class SessionState {
485 
486         @NonNull
487         final ClientState mClient;
488         @NonNull
489         final IInputMethodInvoker mMethod;
490 
491         @Nullable
492         IInputMethodSession mSession;
493         @Nullable
494         InputChannel mChannel;
495 
496         @UserIdInt
497         final int mUserId;
498 
499         @Override
toString()500         public String toString() {
501             return "SessionState{uid=" + mClient.mUid + " pid=" + mClient.mPid
502                     + " method=" + Integer.toHexString(
503                     IInputMethodInvoker.getBinderIdentityHashCode(mMethod))
504                     + " session=" + Integer.toHexString(System.identityHashCode(mSession))
505                     + " channel=" + mChannel
506                     + " userId=" + mUserId
507                     + "}";
508         }
509 
SessionState(@onNull ClientState client, @NonNull IInputMethodInvoker method, IInputMethodSession session, InputChannel channel, @UserIdInt int userId)510         SessionState(@NonNull ClientState client, @NonNull IInputMethodInvoker method,
511                 IInputMethodSession session, InputChannel channel, @UserIdInt int userId) {
512             mClient = client;
513             mMethod = method;
514             mSession = session;
515             mChannel = channel;
516             mUserId = userId;
517         }
518     }
519 
520     /**
521      * Record session state for an accessibility service.
522      */
523     static class AccessibilitySessionState {
524 
525         @NonNull
526         final ClientState mClient;
527         // Id of the accessibility service.
528         final int mId;
529 
530         @Nullable
531         IAccessibilityInputMethodSession mSession;
532 
533         @Override
toString()534         public String toString() {
535             return "AccessibilitySessionState{uid=" + mClient.mUid + " pid=" + mClient.mPid
536                     + " id=" + Integer.toHexString(mId)
537                     + " session=" + Integer.toHexString(System.identityHashCode(mSession))
538                     + "}";
539         }
540 
AccessibilitySessionState(@onNull ClientState client, int id, IAccessibilityInputMethodSession session)541         AccessibilitySessionState(@NonNull ClientState client, int id,
542                 IAccessibilityInputMethodSession session) {
543             mClient = client;
544             mId = id;
545             mSession = session;
546         }
547     }
548 
549     /**
550      * Manages the IME clients.
551      */
552     @SharedByAllUsersField
553     @NonNull
554     private final ClientController mClientController;
555 
556     /**
557      * Set once the system is ready to run third party code.
558      */
559     @SharedByAllUsersField
560     boolean mSystemReady;
561 
562     @AnyThread
563     @NonNull
getUserData(@serIdInt int userId)564     UserData getUserData(@UserIdInt int userId) {
565         return mUserDataRepository.getOrCreate(userId);
566     }
567 
568     @AnyThread
569     @NonNull
getInputMethodBindingController(@serIdInt int userId)570     InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
571         return getUserData(userId).mBindingController;
572     }
573 
574     /**
575      * Map of window perceptible states indexed by their associated window tokens.
576      *
577      * The value {@code true} indicates that IME has not been mostly hidden via
578      * {@link android.view.InsetsController} for the given window.
579      */
580     @GuardedBy("ImfLock.class")
581     @SharedByAllUsersField
582     private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>();
583 
584     /**
585      * The display ID of the input method indicates the fallback display which returned by
586      * {@link #computeImeDisplayIdForTarget}.
587      */
588     static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY;
589 
590     /**
591      * If non-null, this is the input method service we are currently connected
592      * to.
593      */
594     @GuardedBy("ImfLock.class")
595     @Nullable
getCurMethodLocked()596     IInputMethodInvoker getCurMethodLocked() {
597         return getInputMethodBindingController(mCurrentImeUserId).getCurMethod();
598     }
599 
600     /**
601      * True if the device is currently interactive with user.  The value is true initially.
602      */
603     @MultiUserUnawareField
604     boolean mIsInteractive = true;
605 
606     @SharedByAllUsersField
607     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
608 
609     @SharedByAllUsersField
610     private final String mSlotIme;
611 
612     /**
613      * Registered {@link InputMethodListListener}.
614      * This variable can be accessed from both of MainThread and BinderThread.
615      */
616     @SharedByAllUsersField
617     private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
618             new CopyOnWriteArrayList<>();
619 
620     @GuardedBy("ImfLock.class")
621     @SharedByAllUsersField
622     private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
623 
624     @GuardedBy("ImfLock.class")
625     @SharedByAllUsersField
626     @NonNull
627     private final StartInputHistory mStartInputHistory = new StartInputHistory();
628 
629     @GuardedBy("ImfLock.class")
630     @SharedByAllUsersField
631     @NonNull
632     private final SoftInputShowHideHistory mSoftInputShowHideHistory =
633             new SoftInputShowHideHistory();
634 
635     @SharedByAllUsersField
636     @NonNull
637     private final ImeTrackerService mImeTrackerService;
638 
639     @GuardedBy("ImfLock.class")
onSecureSettingsChangedLocked(@onNull String key, @UserIdInt int userId)640     private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) {
641         switch (key) {
642             case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
643                 if (!Flags.imeSwitcherRevamp()) {
644                     if (userId == mCurrentImeUserId) {
645                         mMenuController.updateKeyboardFromSettingsLocked(userId);
646                     }
647                 }
648                 break;
649             }
650             case Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE: {
651                 final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
652                         mContext.getContentResolver(),
653                         Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, userId);
654                 final var userData = getUserData(userId);
655                 final var visibilityStateComputer = userData.mVisibilityStateComputer;
656                 visibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
657                         accessibilitySoftKeyboardSetting);
658                 if (visibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
659                     if (Flags.refactorInsetsController()) {
660                         final var statsToken = createStatsTokenForFocusedClient(false /* show */,
661                                 SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, userId);
662                         setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
663                     } else {
664                         hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
665                                 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE,
666                                 userId);
667                     }
668                 } else if (isShowRequestedForCurrentWindow(userId)) {
669                     if (Flags.refactorInsetsController()) {
670                         final var statsToken = createStatsTokenForFocusedClient(true /* show */,
671                                 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId);
672                         setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
673                     } else {
674                         showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
675                                 InputMethodManager.SHOW_IMPLICIT,
676                                 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId);
677                     }
678                 }
679                 break;
680             }
681             case Settings.Secure.STYLUS_HANDWRITING_ENABLED: {
682                 InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
683                 InputMethodManager
684                         .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
685                 break;
686             }
687             case Settings.Secure.DEFAULT_INPUT_METHOD:
688             case Settings.Secure.ENABLED_INPUT_METHODS:
689             case Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE: {
690                 boolean enabledChanged = false;
691                 String newEnabled = InputMethodSettingsRepository.get(userId)
692                         .getEnabledInputMethodsStr();
693                 final var userData = getUserData(userId);
694                 if (!userData.mLastEnabledInputMethodsStr.equals(newEnabled)) {
695                     userData.mLastEnabledInputMethodsStr = newEnabled;
696                     enabledChanged = true;
697                 }
698                 updateInputMethodsFromSettingsLocked(enabledChanged, userId);
699                 break;
700             }
701         }
702     }
703 
704     /**
705      * {@link BroadcastReceiver} that is intended to listen to broadcasts sent to all the users.
706      */
707     private final class ImmsBroadcastReceiverForAllUsers extends BroadcastReceiver {
708         @Override
onReceive(Context context, Intent intent)709         public void onReceive(Context context, Intent intent) {
710             final String action = intent.getAction();
711             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
712                 final PendingResult pendingResult = getPendingResult();
713                 if (pendingResult == null) {
714                     return;
715                 }
716                 // sender userId can be a real user ID or USER_ALL.
717                 final int senderUserId = pendingResult.getSendingUserId();
718                 synchronized (ImfLock.class) {
719                     if (senderUserId != UserHandle.USER_ALL && senderUserId != mCurrentImeUserId) {
720                         // A background user is trying to hide the dialog. Ignore.
721                         return;
722                     }
723                     final int userId = mCurrentImeUserId;
724                     if (Flags.imeSwitcherRevamp()) {
725                         final var bindingController = getInputMethodBindingController(userId);
726                         mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
727                     } else {
728                         mMenuController.hideInputMethodMenuLocked(userId);
729                     }
730                 }
731             } else {
732                 Slog.w(TAG, "Unexpected intent " + intent);
733             }
734         }
735     }
736 
737     /**
738      * Handles {@link Intent#ACTION_LOCALE_CHANGED}.
739      *
740      * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
741      * the users.</p>
742      */
743     @WorkerThread
onActionLocaleChanged(@onNull LocaleList prevLocales, @NonNull LocaleList newLocales)744     void onActionLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales) {
745         ProtoLog.v(IMMS_DEBUG, "onActionLocaleChanged prev=%s new=%s", prevLocales, newLocales);
746         synchronized (ImfLock.class) {
747             if (!mSystemReady) {
748                 return;
749             }
750             for (int userId : mUserManagerInternal.getUserIds()) {
751                 // Does InputMethodInfo really have data dependency on system locale?
752                 // TODO(b/356679261): Check if we really need to update RawInputMethodInfo here.
753                 {
754                     final var userData = getUserData(userId);
755                     final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
756                     final var rawMethodMap = queryRawInputMethodServiceMap(mContext, userId);
757                     userData.mRawInputMethodMap.set(rawMethodMap);
758                     final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
759                             DirectBootAwareness.AUTO,
760                             userData.mIsUnlockingOrUnlocked.get());
761                     final var settings = InputMethodSettings.create(methodMap, userId);
762                     InputMethodSettingsRepository.put(userId, settings);
763                 }
764                 postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
765                 // If the locale is changed, needs to reset the default ime
766                 resetDefaultImeLocked(mContext, userId);
767                 updateFromSettingsLocked(true, userId);
768             }
769         }
770     }
771 
772     final class MyPackageMonitor extends PackageMonitor {
773         /**
774          * Remembers package names passed to {@link #onPackageDataCleared(String, int)}.
775          *
776          * <p>This field must be accessed only from callback methods in {@link PackageMonitor},
777          * which should be bound to {@link #getRegisteredHandler()}.</p>
778          */
779         private ArrayList<String> mDataClearedPackages = new ArrayList<>();
780 
MyPackageMonitor()781         private MyPackageMonitor() {
782             super(true);
783         }
784 
785         @Override
onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit)786         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
787             synchronized (ImfLock.class) {
788                 final int userId = getChangingUserId();
789                 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
790                 String curInputMethodId = settings.getSelectedInputMethod();
791                 final List<InputMethodInfo> methodList = settings.getMethodList();
792                 final int numImes = methodList.size();
793                 if (curInputMethodId != null) {
794                     for (int i = 0; i < numImes; i++) {
795                         InputMethodInfo imi = methodList.get(i);
796                         if (imi.getId().equals(curInputMethodId)) {
797                             for (String pkg : packages) {
798                                 if (imi.getPackageName().equals(pkg)) {
799                                     if (!doit) {
800                                         return true;
801                                     }
802                                     resetSelectedInputMethodAndSubtypeLocked("", userId);
803                                     chooseNewDefaultIMELocked(userId);
804                                     return true;
805                                 }
806                             }
807                         }
808                     }
809                 }
810             }
811             return false;
812         }
813 
814         @Override
onBeginPackageChanges()815         public void onBeginPackageChanges() {
816             clearPackageChangeState();
817         }
818 
819         @Override
onPackageDataCleared(String packageName, int uid)820         public void onPackageDataCleared(String packageName, int uid) {
821             mDataClearedPackages.add(packageName);
822         }
823 
824         @Override
onFinishPackageChanges()825         public void onFinishPackageChanges() {
826             onFinishPackageChangesInternal();
827             clearPackageChangeState();
828         }
829 
clearPackageChangeState()830         private void clearPackageChangeState() {
831             // No need to lock them because we access these fields only on getRegisteredHandler().
832             mDataClearedPackages.clear();
833         }
834 
onFinishPackageChangesInternal()835         private void onFinishPackageChangesInternal() {
836             final int userId = getChangingUserId();
837             final var userData = getUserData(userId);
838 
839             userData.mRawInputMethodMap.set(queryRawInputMethodServiceMap(mContext, userId));
840 
841             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
842 
843             InputMethodInfo curIm = null;
844             String curInputMethodId = settings.getSelectedInputMethod();
845             final List<InputMethodInfo> methodList = settings.getMethodList();
846 
847             final ArrayList<String> imesToClearAdditionalSubtypes = new ArrayList<>();
848             final ArrayList<String> imesToBeDisabled = new ArrayList<>();
849             final int numImes = methodList.size();
850             for (int i = 0; i < numImes; i++) {
851                 InputMethodInfo imi = methodList.get(i);
852                 final String imiId = imi.getId();
853                 if (imiId.equals(curInputMethodId)) {
854                     curIm = imi;
855                 }
856                 if (mDataClearedPackages.contains(imi.getPackageName())) {
857                     imesToClearAdditionalSubtypes.add(imiId);
858                 }
859                 int change = isPackageDisappearing(imi.getPackageName());
860                 if (change == PACKAGE_PERMANENT_CHANGE) {
861                     Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
862                     imesToBeDisabled.add(imi.getId());
863                 } else if (change == PACKAGE_UPDATING) {
864                     Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: "
865                             + imi.getComponent());
866                     imesToClearAdditionalSubtypes.add(imiId);
867                 }
868             }
869 
870             // Clear additional subtypes as a batch operation.
871             final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
872             final AdditionalSubtypeMap newAdditionalSubtypeMap =
873                     additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes);
874             final boolean additionalSubtypeChanged =
875                     (newAdditionalSubtypeMap != additionalSubtypeMap);
876             if (additionalSubtypeChanged) {
877                 AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
878                         settings.getMethodMap());
879             }
880 
881             final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
882                     newAdditionalSubtypeMap,
883                     DirectBootAwareness.AUTO,
884                     userData.mIsUnlockingOrUnlocked.get());
885 
886             final boolean noUpdate = InputMethodMap.areSame(settings.getMethodMap(), newMethodMap);
887             if (noUpdate && imesToBeDisabled.isEmpty()) {
888                 return;
889             }
890 
891             // Here we start remaining tasks that need to be done with the lock (b/340221861).
892             synchronized (ImfLock.class) {
893                 final int numImesToBeDisabled = imesToBeDisabled.size();
894                 for (int i = 0; i < numImesToBeDisabled; ++i) {
895                     setInputMethodEnabledLocked(imesToBeDisabled.get(i), false /* enabled */,
896                             userId);
897                 }
898                 if (noUpdate) {
899                     return;
900                 }
901                 InputMethodSettingsRepository.put(userId,
902                         InputMethodSettings.create(newMethodMap, userId));
903                 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
904 
905                 boolean changed = false;
906 
907                 if (curIm != null) {
908                     int change = isPackageDisappearing(curIm.getPackageName());
909                     if (change == PACKAGE_TEMPORARY_CHANGE
910                             || change == PACKAGE_PERMANENT_CHANGE) {
911                         final PackageManager userAwarePackageManager =
912                                 getPackageManagerForUser(mContext, userId);
913                         ServiceInfo si = null;
914                         try {
915                             si = userAwarePackageManager.getServiceInfo(curIm.getComponent(),
916                                     PackageManager.ComponentInfoFlags.of(0));
917                         } catch (PackageManager.NameNotFoundException ignored) {
918                         }
919                         if (si == null) {
920                             // Uh oh, current input method is no longer around!
921                             // Pick another one...
922                             Slog.i(TAG, "Current input method removed: " + curInputMethodId);
923                             final var bindingController = getInputMethodBindingController(userId);
924                             updateSystemUiLocked(0 /* vis */,
925                                     bindingController.getBackDisposition(), userId);
926                             if (!chooseNewDefaultIMELocked(userId)) {
927                                 changed = true;
928                                 curIm = null;
929                                 Slog.i(TAG, "Unsetting current input method");
930                                 resetSelectedInputMethodAndSubtypeLocked("", userId);
931                             }
932                         }
933                     }
934                 }
935 
936                 if (curIm == null) {
937                     // We currently don't have a default input method... is
938                     // one now available?
939                     changed = chooseNewDefaultIMELocked(userId);
940                 } else if (!changed && isPackageModified(curIm.getPackageName())) {
941                     // Even if the current input method is still available, current subtype could
942                     // be obsolete when the package is modified in practice.
943                     changed = true;
944                 }
945 
946                 if (changed) {
947                     updateFromSettingsLocked(false, userId);
948                 }
949             }
950         }
951     }
952 
953     private static final class UserSwitchHandlerTask implements Runnable {
954         final InputMethodManagerService mService;
955 
956         @UserIdInt
957         final int mToUserId;
958 
959         @Nullable
960         IInputMethodClientInvoker mClientToBeReset;
961 
UserSwitchHandlerTask(InputMethodManagerService service, @UserIdInt int toUserId, @Nullable IInputMethodClientInvoker clientToBeReset)962         UserSwitchHandlerTask(InputMethodManagerService service, @UserIdInt int toUserId,
963                 @Nullable IInputMethodClientInvoker clientToBeReset) {
964             mService = service;
965             mToUserId = toUserId;
966             mClientToBeReset = clientToBeReset;
967         }
968 
969         @Override
run()970         public void run() {
971             synchronized (ImfLock.class) {
972                 if (mService.mUserSwitchHandlerTask != this) {
973                     // This task was already canceled before it is handled here. So do nothing.
974                     return;
975                 }
976                 mService.switchUserOnHandlerLocked(mService.mUserSwitchHandlerTask.mToUserId,
977                         mClientToBeReset);
978                 mService.mUserSwitchHandlerTask = null;
979             }
980         }
981     }
982 
983     /**
984      * When non-{@code null}, this represents pending user-switch task, which is to be executed as
985      * a handler callback.  This needs to be set and unset only within the lock.
986      */
987     @Nullable
988     @GuardedBy("ImfLock.class")
989     @MultiUserUnawareField
990     private UserSwitchHandlerTask mUserSwitchHandlerTask;
991 
992     /**
993      * {@link SystemService} used to publish and manage the lifecycle of
994      * {@link InputMethodManagerService}.
995      */
996     public static final class Lifecycle extends SystemService
997             implements UserManagerInternal.UserLifecycleListener {
998         private final InputMethodManagerService mService;
999 
Lifecycle(Context context)1000         public Lifecycle(Context context) {
1001             this(context, createServiceForProduction(context));
1002 
1003             // For production code, hook up user lifecycle
1004             mService.mUserManagerInternal.addUserLifecycleListener(this);
1005 
1006             // Hook up resource change first before initializeUsersAsync() starts reading the
1007             // seemingly initial data so that we can eliminate the race condition.
1008             InputMethodDrawsNavBarResourceMonitor.registerCallback(context, mService.mIoHandler,
1009                     mService::onUpdateResourceOverlay);
1010 
1011             // Also schedule user init tasks onto an I/O thread.
1012             initializeUsersAsync(mService.mUserManagerInternal.getUserIds());
1013         }
1014 
1015         @VisibleForTesting
Lifecycle(Context context, @NonNull InputMethodManagerService inputMethodManagerService)1016         Lifecycle(Context context, @NonNull InputMethodManagerService inputMethodManagerService) {
1017             super(context);
1018             mService = inputMethodManagerService;
1019         }
1020 
1021         /**
1022          * Does initialization then instantiate {@link InputMethodManagerService} for production
1023          * configurations.
1024          *
1025          * <p>We have this abstraction just because several unit tests directly initialize
1026          * {@link InputMethodManagerService} with some mocked/emulated dependencies.</p>
1027          *
1028          * @param context {@link Context} to be used to set up
1029          * @return {@link InputMethodManagerService} object to be used
1030          */
1031         @NonNull
createServiceForProduction( @onNull Context context)1032         private static InputMethodManagerService createServiceForProduction(
1033                 @NonNull Context context) {
1034             final ServiceThread thread = new ServiceThread(HANDLER_THREAD_NAME,
1035                     Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
1036             thread.start();
1037 
1038             final ServiceThread ioThread = new ServiceThread(PACKAGE_MONITOR_THREAD_NAME,
1039                     Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
1040             ioThread.start();
1041 
1042             SecureSettingsWrapper.setContentResolver(context.getContentResolver());
1043 
1044             return new InputMethodManagerService(context,
1045                     shouldEnableConcurrentMultiUserMode(context), thread.getLooper(),
1046                     Handler.createAsync(ioThread.getLooper()),
1047                     null /* bindingControllerForTesting */);
1048         }
1049 
1050         @Override
onStart()1051         public void onStart() {
1052             mService.publishLocalService();
1053             IInputMethodManagerImpl.Callback service;
1054             if (Flags.useZeroJankProxy()) {
1055                 service = new ZeroJankProxy(mService.mHandler::post, mService);
1056             } else {
1057                 service = mService;
1058             }
1059             publishBinderService(Context.INPUT_METHOD_SERVICE,
1060                     IInputMethodManagerImpl.create(service), false /*allowIsolated*/,
1061                     DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
1062             if (Flags.refactorInsetsController()) {
1063                 mService.registerImeRequestedChangedListener();
1064             }
1065         }
1066 
1067         @Override
onUserSwitching(@ullable TargetUser from, @NonNull TargetUser to)1068         public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
1069             // Called on ActivityManager thread.
1070             synchronized (ImfLock.class) {
1071                 if (mService.mConcurrentMultiUserModeEnabled) {
1072                     // In concurrent multi-user mode, we in general do not rely on the concept of
1073                     // current user.
1074                     return;
1075                 }
1076                 mService.scheduleSwitchUserTaskLocked(to.getUserIdentifier(),
1077                         /* clientToBeReset= */ null);
1078             }
1079         }
1080 
1081         @Override
onBootPhase(int phase)1082         public void onBootPhase(int phase) {
1083             // Called on ActivityManager thread.
1084             // TODO: Dispatch this to a worker thread as needed.
1085             if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
1086                 mService.systemRunning();
1087             }
1088         }
1089 
1090         @Override
onUserCreated(UserInfo user, @Nullable Object token)1091         public void onUserCreated(UserInfo user, @Nullable Object token) {
1092             // Called directly from UserManagerService. Do not block the calling thread.
1093             final int userId = user.id;
1094             AdditionalSubtypeMapRepository.onUserCreated(userId);
1095             initializeUsersAsync(new int[]{ userId });
1096         }
1097 
1098         @Override
onUserRemoved(UserInfo user)1099         public void onUserRemoved(UserInfo user) {
1100             // Called directly from UserManagerService. Do not block the calling thread.
1101             final int userId = user.id;
1102             SecureSettingsWrapper.onUserRemoved(userId);
1103             AdditionalSubtypeMapRepository.remove(userId);
1104             InputMethodSettingsRepository.remove(userId);
1105             mService.mUserDataRepository.remove(userId);
1106             synchronized (ImfLock.class) {
1107                 final int nextOrCurrentUser = mService.mUserSwitchHandlerTask != null
1108                         ? mService.mUserSwitchHandlerTask.mToUserId : mService.mCurrentImeUserId;
1109                 if (!mService.mConcurrentMultiUserModeEnabled && userId == nextOrCurrentUser) {
1110                     // The current user was removed without an ongoing switch, or the user targeted
1111                     // by the ongoing switch was removed. Switch to the current non-profile user
1112                     // to allow starting input on it or one of its profile users later.
1113                     // Note: non-profile users cannot be removed while they are the current user.
1114                     final int currentUserId = mService.mActivityManagerInternal.getCurrentUserId();
1115                     mService.scheduleSwitchUserTaskLocked(currentUserId,
1116                             null /* clientToBeReset */);
1117                 }
1118             }
1119         }
1120 
1121         @Override
onUserUnlocking(@onNull TargetUser user)1122         public void onUserUnlocking(@NonNull TargetUser user) {
1123             // Called on ActivityManager thread. Do not block the calling thread.
1124             final int userId = user.getUserIdentifier();
1125             final var userData = mService.getUserData(userId);
1126             final boolean userUnlocked = true;
1127             userData.mIsUnlockingOrUnlocked.set(userUnlocked);
1128             SecureSettingsWrapper.onUserUnlocking(userId);
1129             final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
1130                     AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
1131                     userUnlocked);
1132             final var newSettings = InputMethodSettings.create(methodMap, userId);
1133             InputMethodSettingsRepository.put(userId, newSettings);
1134             mService.mIoHandler.post(() -> {
1135                 synchronized (ImfLock.class) {
1136                     if (!mService.mSystemReady) {
1137                         return;
1138                     }
1139                     // We need to rebuild IMEs.
1140                     mService.postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */,
1141                             userId);
1142                     mService.updateInputMethodsFromSettingsLocked(true /* enabledChanged */,
1143                             userId);
1144                 }
1145             });
1146         }
1147 
1148         @Override
onUserStarting(TargetUser user)1149         public void onUserStarting(TargetUser user) {
1150             // Called on ActivityManager thread.
1151             final int userId = user.getUserIdentifier();
1152             SecureSettingsWrapper.onUserStarting(userId);
1153             mService.mIoHandler.post(() -> {
1154                 synchronized (ImfLock.class) {
1155                     if (mService.mSystemReady) {
1156                         mService.onUserReadyLocked(userId);
1157                     }
1158                 }
1159             });
1160         }
1161 
1162         @AnyThread
initializeUsersAsync(@serIdInt int[] userIds)1163         private void initializeUsersAsync(@UserIdInt int[] userIds) {
1164             Slog.d(TAG, "Schedule initialization for users=" + Arrays.toString(userIds));
1165             mService.mIoHandler.post(() -> {
1166                 final var service = mService;
1167                 final var context = service.mContext;
1168                 final var userManagerInternal = service.mUserManagerInternal;
1169 
1170                 for (int userId : userIds) {
1171                     Slog.d(TAG, "Start initialization for user=" + userId);
1172                     final var userData = mService.getUserData(userId);
1173 
1174                     AdditionalSubtypeMapRepository.initializeIfNecessary(userId);
1175                     final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
1176                     final var rawMethodMap = queryRawInputMethodServiceMap(context, userId);
1177                     userData.mRawInputMethodMap.set(rawMethodMap);
1178 
1179                     final boolean unlocked = userManagerInternal.isUserUnlockingOrUnlocked(userId);
1180                     userData.mIsUnlockingOrUnlocked.set(unlocked);
1181                     final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
1182                             DirectBootAwareness.AUTO, unlocked);
1183 
1184                     final var settings = InputMethodSettings.create(methodMap, userId);
1185                     InputMethodSettingsRepository.put(userId, settings);
1186 
1187                     final int profileParentId = userManagerInternal.getProfileParentId(userId);
1188                     final boolean value =
1189                             InputMethodDrawsNavBarResourceMonitor.evaluate(context,
1190                                     profileParentId);
1191                     userData.mImeDrawsNavBar.set(value);
1192 
1193                     userData.mBackgroundLoadLatch.countDown();
1194                     Slog.d(TAG, "Complete initialization for user=" + userId);
1195                 }
1196             });
1197         }
1198 
1199         @Override
onUserStopped(@onNull TargetUser user)1200         public void onUserStopped(@NonNull TargetUser user) {
1201             final int userId = user.getUserIdentifier();
1202             // Called on ActivityManager thread.
1203 
1204             // Following operations should be trivial and fast enough, so do not dispatch them to
1205             // the IO thread.
1206             SecureSettingsWrapper.onUserStopped(userId);
1207             final var userData = mService.getUserData(userId);
1208             final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
1209             final var rawMethodMap = userData.mRawInputMethodMap.get();
1210             final boolean userUnlocked = false;  // Stopping a user also locks their storage.
1211             userData.mIsUnlockingOrUnlocked.set(userUnlocked);
1212             final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
1213                     DirectBootAwareness.AUTO, userUnlocked);
1214             InputMethodSettingsRepository.put(userId,
1215                     InputMethodSettings.create(methodMap, userId));
1216         }
1217     }
1218 
1219     @GuardedBy("ImfLock.class")
scheduleSwitchUserTaskLocked(@serIdInt int userId, @Nullable IInputMethodClientInvoker clientToBeReset)1220     void scheduleSwitchUserTaskLocked(@UserIdInt int userId,
1221             @Nullable IInputMethodClientInvoker clientToBeReset) {
1222         if (mUserSwitchHandlerTask != null) {
1223             if (mUserSwitchHandlerTask.mToUserId == userId) {
1224                 mUserSwitchHandlerTask.mClientToBeReset = clientToBeReset;
1225                 return;
1226             }
1227             mIoHandler.removeCallbacks(mUserSwitchHandlerTask);
1228         }
1229         // Hide soft input before user switch task since switch task may block main handler a while
1230         // and delayed the hideCurrentInputLocked().
1231         final var userData = getUserData(userId);
1232         if (Flags.refactorInsetsController()) {
1233             final var statsToken = createStatsTokenForFocusedClient(false /* show */,
1234                     SoftInputShowHideReason.HIDE_SWITCH_USER, userId);
1235             setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
1236         } else {
1237             hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
1238                     SoftInputShowHideReason.HIDE_SWITCH_USER, userId);
1239         }
1240         final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
1241                 clientToBeReset);
1242         mUserSwitchHandlerTask = task;
1243         mIoHandler.post(task);
1244     }
1245 
1246     @VisibleForTesting
InputMethodManagerService( Context context, boolean concurrentMultiUserModeEnabled, @NonNull Looper uiLooper, @NonNull Handler ioHandler, @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting)1247     InputMethodManagerService(
1248             Context context,
1249             boolean concurrentMultiUserModeEnabled,
1250             @NonNull Looper uiLooper,
1251             @NonNull Handler ioHandler,
1252             @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
1253         synchronized (ImfLock.class) {
1254             mConcurrentMultiUserModeEnabled = concurrentMultiUserModeEnabled;
1255             mContext = context;
1256             mRes = context.getResources();
1257 
1258             mHandler = Handler.createAsync(uiLooper, this);
1259             mIoHandler = ioHandler;
1260             SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mIoHandler);
1261             mImeTrackerService = new ImeTrackerService(mHandler);
1262             mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
1263             mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
1264             mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
1265             mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
1266             mImePlatformCompatUtils = new ImePlatformCompatUtils();
1267             mInputMethodDeviceConfigs = new InputMethodDeviceConfigs();
1268             mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
1269 
1270             mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
1271 
1272             mShowOngoingImeSwitcherForPhones = false;
1273 
1274             ProtoLog.init(ImeProtoLogGroup.values());
1275 
1276             mCurrentImeUserId = mActivityManagerInternal.getCurrentUserId();
1277             final IntFunction<InputMethodBindingController>
1278                     bindingControllerFactory = userId -> new InputMethodBindingController(userId,
1279                     InputMethodManagerService.this);
1280             final IntFunction<ImeVisibilityStateComputer> visibilityStateComputerFactory =
1281                     userId -> new ImeVisibilityStateComputer(InputMethodManagerService.this,
1282                             userId);
1283             mUserDataRepository = new UserDataRepository(
1284                     bindingControllerForTesting != null ? bindingControllerForTesting
1285                             : bindingControllerFactory, visibilityStateComputerFactory);
1286 
1287             mMenuController = new InputMethodMenuController(this);
1288             mMenuControllerNew = Flags.imeSwitcherRevamp()
1289                     ? new InputMethodMenuControllerNew() : null;
1290             mVisibilityApplier = new DefaultImeVisibilityApplier(this);
1291 
1292             mClientController = new ClientController(mPackageManagerInternal);
1293             mClientController.addClientControllerCallback(c -> onClientRemoved(c));
1294 
1295             mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
1296                     com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
1297             mNonPreemptibleInputMethods = mRes.getStringArray(
1298                     com.android.internal.R.array.config_nonPreemptibleInputMethods);
1299             Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText();
1300             mHwController = new HandwritingModeController(mContext, uiLooper,
1301                     new InkWindowInitializer(), discardDelegationTextRunnable);
1302             registerDeviceListenerAndCheckStylusSupport();
1303             mInputMethodManagerInternal = new LocalServiceImpl();
1304         }
1305     }
1306 
1307     private final class InkWindowInitializer implements Runnable {
run()1308         public void run() {
1309             synchronized (ImfLock.class) {
1310                 IInputMethodInvoker curMethod = getCurMethodLocked();
1311                 if (curMethod != null) {
1312                     curMethod.initInkWindow();
1313                 }
1314             }
1315         }
1316     }
1317 
1318     @GuardedBy("ImfLock.class")
onUpdateEditorToolTypeLocked(@otionEvent.ToolType int toolType, @UserIdInt int userId)1319     private void onUpdateEditorToolTypeLocked(@MotionEvent.ToolType int toolType,
1320             @UserIdInt int userId) {
1321         final var curMethod = getInputMethodBindingController(userId).getCurMethod();
1322         if (curMethod != null) {
1323             curMethod.updateEditorToolType(toolType);
1324         }
1325     }
1326 
discardHandwritingDelegationText()1327     private void discardHandwritingDelegationText() {
1328         synchronized (ImfLock.class) {
1329             IInputMethodInvoker curMethod = getCurMethodLocked();
1330             if (curMethod != null) {
1331                 curMethod.discardHandwritingDelegationText();
1332             }
1333         }
1334     }
1335 
1336     @GuardedBy("ImfLock.class")
resetDefaultImeLocked(Context context, @UserIdInt int userId)1337     private void resetDefaultImeLocked(Context context, @UserIdInt int userId) {
1338         final var bindingController = getInputMethodBindingController(userId);
1339         // Do not reset the default (current) IME when it is a 3rd-party IME
1340         String selectedMethodId = bindingController.getSelectedMethodId();
1341         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
1342         final InputMethodInfo selectedImi = settings.getMethodMap().get(selectedMethodId);
1343         if (selectedImi != null && !selectedImi.isSystem()) {
1344             return;
1345         }
1346         final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
1347                 context, settings.getEnabledInputMethodList());
1348         if (suitableImes.isEmpty()) {
1349             Slog.i(TAG, "No default found");
1350             return;
1351         }
1352         final InputMethodInfo defIm = suitableImes.get(0);
1353         ProtoLog.v(IMMS_DEBUG, "Default found, using %s", defIm.getId());
1354         setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_INDEX, false, userId);
1355     }
1356 
1357     @NonNull
getPackageManagerForUser(@onNull Context context, @UserIdInt int userId)1358     private static PackageManager getPackageManagerForUser(@NonNull Context context,
1359             @UserIdInt int userId) {
1360         return context.getUserId() == userId
1361                 ? context.getPackageManager()
1362                 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */)
1363                         .getPackageManager();
1364     }
1365 
1366     @GuardedBy("ImfLock.class")
switchUserOnHandlerLocked(@serIdInt int newUserId, IInputMethodClientInvoker clientToBeReset)1367     private void switchUserOnHandlerLocked(@UserIdInt int newUserId,
1368             IInputMethodClientInvoker clientToBeReset) {
1369         final int prevUserId = mCurrentImeUserId;
1370         ProtoLog.v(IMMS_DEBUG, "Switching user stage 1/3. newUserId=%s prevUserId=%s", newUserId,
1371                 prevUserId);
1372 
1373         // Clean up stuff for mCurrentImeUserId, which soon becomes the previous user.
1374 
1375         // TODO(b/338461930): Check if this is still necessary or not.
1376         onUnbindCurrentMethodByReset(prevUserId);
1377 
1378         // Note that in b/197848765 we want to see if we can keep the binding alive for better
1379         // profile switching.
1380         final var bindingController = getInputMethodBindingController(prevUserId);
1381         bindingController.unbindCurrentMethod();
1382 
1383         unbindCurrentClientLocked(UnbindReason.SWITCH_USER, prevUserId);
1384 
1385         // Hereafter we start initializing things for "newUserId".
1386 
1387         final var newUserData = getUserData(newUserId);
1388 
1389         // TODO(b/342027196): Double check if we need to always reset upon user switching.
1390         newUserData.mLastEnabledInputMethodsStr = "";
1391 
1392         mCurrentImeUserId = newUserId;
1393         final String defaultImiId = SecureSettingsWrapper.getString(
1394                 Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
1395 
1396         ProtoLog.v(IMMS_DEBUG, "Switching user stage 2/3. newUserId=%s defaultImiId=%s", newUserId,
1397                 defaultImiId);
1398 
1399         // For secondary users, the list of enabled IMEs may not have been updated since the
1400         // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may
1401         // not be empty even if the IME has been uninstalled by the primary user.
1402         // Even in such cases, IMMS works fine because it will find the most applicable
1403         // IME for that user.
1404         final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
1405 
1406         final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
1407         postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */,
1408                 newUserId);
1409         if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
1410             // This is the first time of the user switch and
1411             // set the current ime to the proper one.
1412             resetDefaultImeLocked(mContext, newUserId);
1413         }
1414         updateFromSettingsLocked(true, newUserId);
1415 
1416         // Special workaround for b/356879517.
1417         // KeyboardLayoutManager still expects onInputMethodSubtypeChangedForKeyboardLayoutMapping
1418         // to be called back upon IME user switching, while we are actively deprecating the concept
1419         // of "current IME user" at b/350386877.
1420         // TODO(b/356879517): Come up with a way to avoid this special handling.
1421         if (newUserData.mSubtypeForKeyboardLayoutMapping != null) {
1422             final var subtypeHandleAndSubtype = newUserData.mSubtypeForKeyboardLayoutMapping;
1423             mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
1424                     newUserId, subtypeHandleAndSubtype.first, subtypeHandleAndSubtype.second);
1425         }
1426 
1427         if (initialUserSwitch) {
1428             InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
1429                     getPackageManagerForUser(mContext, newUserId),
1430                     newSettings.getEnabledInputMethodList());
1431         }
1432 
1433         ProtoLog.v(IMMS_DEBUG, "Switching user stage 3/3. newUserId=%s selectedIme=%s", newUserId,
1434                 newSettings.getSelectedInputMethod());
1435 
1436         if (mIsInteractive && clientToBeReset != null) {
1437             final ClientState cs = mClientController.getClient(clientToBeReset.asBinder());
1438             if (cs == null) {
1439                 // The client is already gone.
1440                 return;
1441             }
1442             cs.mClient.scheduleStartInputIfNecessary(newUserData.mInFullscreenMode);
1443         }
1444     }
1445 
waitForUserInitialization()1446     private void waitForUserInitialization() {
1447         final int[] userIds = mUserManagerInternal.getUserIds();
1448         final long deadlineNanos = SystemClock.elapsedRealtimeNanos()
1449                 + TimeUnit.MILLISECONDS.toNanos(SYSTEM_READY_USER_INIT_TIMEOUT);
1450         boolean interrupted = false;
1451         try {
1452             for (int userId : userIds) {
1453                 final var latch = getUserData(userId).mBackgroundLoadLatch;
1454                 boolean awaitResult;
1455                 while (true) {
1456                     try {
1457                         final long remainingNanos =
1458                                 Math.max(deadlineNanos - SystemClock.elapsedRealtimeNanos(), 0);
1459                         awaitResult = latch.await(remainingNanos, TimeUnit.NANOSECONDS);
1460                         break;
1461                     } catch (InterruptedException ignored) {
1462                         interrupted = true;
1463                     }
1464                 }
1465                 if (!awaitResult) {
1466                     Slog.w(TAG, "Timed out for user#" + userId + " to be initialized");
1467                 }
1468             }
1469         } finally {
1470             if (interrupted) {
1471                 Thread.currentThread().interrupt();
1472             }
1473         }
1474     }
1475 
1476     /**
1477      * TODO(b/32343335): The entire systemRunning() method needs to be revisited.
1478      */
systemRunning()1479     public void systemRunning() {
1480         waitForUserInitialization();
1481 
1482         synchronized (ImfLock.class) {
1483             ProtoLog.v(IMMS_DEBUG, "--- systemReady");
1484             if (!mSystemReady) {
1485                 mSystemReady = true;
1486                 final int currentImeUserId = mCurrentImeUserId;
1487                 mStatusBarManagerInternal =
1488                         LocalServices.getService(StatusBarManagerInternal.class);
1489                 hideStatusBarIconLocked(currentImeUserId);
1490                 final var bindingController = getInputMethodBindingController(currentImeUserId);
1491                 updateSystemUiLocked(bindingController.getImeWindowVis(),
1492                         bindingController.getBackDisposition(), currentImeUserId);
1493                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
1494                         com.android.internal.R.bool.show_ongoing_ime_switcher);
1495                 if (mShowOngoingImeSwitcherForPhones) {
1496                     mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(available -> {
1497                         mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
1498                                 available ? 1 : 0, 0 /* unused */).sendToTarget();
1499                     });
1500                 }
1501 
1502                 mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
1503                 SecureSettingsChangeCallback.register(mHandler, mContext.getContentResolver(),
1504                         new String[] {
1505                                 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
1506                                 Settings.Secure.DEFAULT_INPUT_METHOD,
1507                                 Settings.Secure.ENABLED_INPUT_METHODS,
1508                                 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
1509                                 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
1510                                 Settings.Secure.STYLUS_HANDWRITING_ENABLED,
1511                         }, (key, flags, userId) -> {
1512                             synchronized (ImfLock.class) {
1513                                 onSecureSettingsChangedLocked(key, userId);
1514                             }
1515                         });
1516 
1517                 final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
1518                 broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
1519                 mContext.registerReceiverAsUser(new ImmsBroadcastReceiverForAllUsers(),
1520                         UserHandle.ALL, broadcastFilterForAllUsers, null, null,
1521                         Context.RECEIVER_EXPORTED);
1522 
1523                 AdditionalSubtypeMapRepository.startWriterThread();
1524 
1525                 for (int userId : mUserManagerInternal.getUserIds()) {
1526                     onUserReadyLocked(userId);
1527                 }
1528             }
1529         }
1530     }
1531 
1532     @GuardedBy("ImfLock.class")
onUserReadyLocked(@serIdInt int userId)1533     void onUserReadyLocked(@UserIdInt int userId) {
1534         if (!mUserManagerInternal.isUserRunning(userId)) {
1535             return;
1536         }
1537 
1538         final String defaultImiId = SecureSettingsWrapper.getString(
1539                 Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
1540         final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
1541         final var settings = InputMethodSettingsRepository.get(userId);
1542         postInputMethodSettingUpdatedLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */,
1543                 userId);
1544         updateFromSettingsLocked(true, userId);
1545         InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
1546                 getPackageManagerForUser(mContext, userId), settings.getEnabledInputMethodList());
1547     }
1548 
registerImeRequestedChangedListener()1549     void registerImeRequestedChangedListener() {
1550         mWindowManagerInternal.setOnImeRequestedChangedListener(
1551                 (windowToken, imeVisible, statsToken) -> {
1552                     if (Flags.refactorInsetsController()) {
1553                         if (imeVisible) {
1554                             showCurrentInputInternal(windowToken, statsToken);
1555                         } else {
1556                             hideCurrentInputInternal(windowToken, statsToken);
1557                         }
1558                     }
1559                 });
1560     }
1561 
1562     @BinderThread
1563     @Nullable
1564     @Override
getCurrentInputMethodInfoAsUser(@serIdInt int userId)1565     public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
1566         if (UserHandle.getCallingUserId() != userId) {
1567             mContext.enforceCallingOrSelfPermission(
1568                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
1569         }
1570         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
1571         final String selectedImeId;
1572         if (Flags.consistentGetCurrentInputMethodInfo()) {
1573             final var bindingController = getInputMethodBindingController(userId);
1574             synchronized (ImfLock.class) {
1575                 selectedImeId = bindingController.getSelectedMethodId();
1576             }
1577         } else {
1578             selectedImeId = settings.getSelectedInputMethod();
1579         }
1580         return settings.getMethodMap().get(selectedImeId);
1581     }
1582 
1583     @BinderThread
1584     @NonNull
1585     @Override
getInputMethodList(@serIdInt int userId, @DirectBootAwareness int directBootAwareness)1586     public InputMethodInfoSafeList getInputMethodList(@UserIdInt int userId,
1587             @DirectBootAwareness int directBootAwareness) {
1588         if (UserHandle.getCallingUserId() != userId) {
1589             mContext.enforceCallingOrSelfPermission(
1590                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
1591         }
1592         if (!mUserManagerInternal.exists(userId)) {
1593             return InputMethodInfoSafeList.empty();
1594         }
1595         final int callingUid = Binder.getCallingUid();
1596         final long ident = Binder.clearCallingIdentity();
1597         try {
1598             return InputMethodInfoSafeList.create(getInputMethodListInternal(
1599                     userId, directBootAwareness, callingUid));
1600         } finally {
1601             Binder.restoreCallingIdentity(ident);
1602         }
1603     }
1604 
1605     @BinderThread
1606     @NonNull
1607     @Override
getEnabledInputMethodList(@serIdInt int userId)1608     public InputMethodInfoSafeList getEnabledInputMethodList(@UserIdInt int userId) {
1609         if (UserHandle.getCallingUserId() != userId) {
1610             mContext.enforceCallingOrSelfPermission(
1611                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
1612         }
1613         if (!mUserManagerInternal.exists(userId)) {
1614             return InputMethodInfoSafeList.empty();
1615         }
1616         final int callingUid = Binder.getCallingUid();
1617         final long ident = Binder.clearCallingIdentity();
1618         try {
1619             return InputMethodInfoSafeList.create(
1620                     getEnabledInputMethodListInternal(userId, callingUid));
1621         } finally {
1622             Binder.restoreCallingIdentity(ident);
1623         }
1624     }
1625 
1626     @BinderThread
1627     @NonNull
1628     @Override
getInputMethodListLegacy(@serIdInt int userId, @DirectBootAwareness int directBootAwareness)1629     public List<InputMethodInfo> getInputMethodListLegacy(@UserIdInt int userId,
1630             @DirectBootAwareness int directBootAwareness) {
1631         if (UserHandle.getCallingUserId() != userId) {
1632             mContext.enforceCallingOrSelfPermission(
1633                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
1634         }
1635         if (!mUserManagerInternal.exists(userId)) {
1636             return Collections.emptyList();
1637         }
1638         final int callingUid = Binder.getCallingUid();
1639         final long ident = Binder.clearCallingIdentity();
1640         try {
1641             return getInputMethodListInternal(userId, directBootAwareness, callingUid);
1642         } finally {
1643             Binder.restoreCallingIdentity(ident);
1644         }
1645     }
1646 
1647     @BinderThread
1648     @NonNull
1649     @Override
getEnabledInputMethodListLegacy(@serIdInt int userId)1650     public List<InputMethodInfo> getEnabledInputMethodListLegacy(@UserIdInt int userId) {
1651         if (UserHandle.getCallingUserId() != userId) {
1652             mContext.enforceCallingOrSelfPermission(
1653                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
1654         }
1655         if (!mUserManagerInternal.exists(userId)) {
1656             return Collections.emptyList();
1657         }
1658         final int callingUid = Binder.getCallingUid();
1659         final long ident = Binder.clearCallingIdentity();
1660         try {
1661             return getEnabledInputMethodListInternal(userId, callingUid);
1662         } finally {
1663             Binder.restoreCallingIdentity(ident);
1664         }
1665     }
1666 
1667     @Override
isStylusHandwritingAvailableAsUser( @serIdInt int userId, boolean connectionless)1668     public boolean isStylusHandwritingAvailableAsUser(
1669             @UserIdInt int userId, boolean connectionless) {
1670         if (UserHandle.getCallingUserId() != userId) {
1671             mContext.enforceCallingOrSelfPermission(
1672                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
1673         }
1674 
1675         synchronized (ImfLock.class) {
1676             if (!isStylusHandwritingEnabled(mContext, userId)) {
1677                 return false;
1678             }
1679 
1680             // Check if selected IME of current user supports handwriting.
1681             if (userId == mCurrentImeUserId) {
1682                 final var bindingController = getInputMethodBindingController(userId);
1683                 return bindingController.supportsStylusHandwriting()
1684                         && (!connectionless
1685                         || bindingController.supportsConnectionlessStylusHandwriting());
1686             }
1687             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
1688             final InputMethodInfo imi = settings.getMethodMap().get(
1689                     settings.getSelectedInputMethod());
1690             return imi != null && imi.supportsStylusHandwriting()
1691                     && (!connectionless || imi.supportsConnectionlessStylusHandwriting());
1692         }
1693     }
1694 
isStylusHandwritingEnabled( @onNull Context context, @UserIdInt int userId)1695     private boolean isStylusHandwritingEnabled(
1696             @NonNull Context context, @UserIdInt int userId) {
1697         // If user is a profile, use preference of it`s parent profile.
1698         final int profileParentUserId = mUserManagerInternal.getProfileParentId(userId);
1699         if (Settings.Secure.getIntForUser(context.getContentResolver(),
1700                 Settings.Secure.STYLUS_HANDWRITING_ENABLED,
1701                 Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE, profileParentUserId) == 0) {
1702             return false;
1703         }
1704         return true;
1705     }
1706 
getInputMethodListInternal(@serIdInt int userId, @DirectBootAwareness int directBootAwareness, int callingUid)1707     private List<InputMethodInfo> getInputMethodListInternal(@UserIdInt int userId,
1708             @DirectBootAwareness int directBootAwareness, int callingUid) {
1709         final var userData = getUserData(userId);
1710         final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
1711                 AdditionalSubtypeMapRepository.get(userId), directBootAwareness,
1712                 userData.mIsUnlockingOrUnlocked.get());
1713         final var settings = InputMethodSettings.create(methodMap, userId);
1714         // Create a copy.
1715         final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList());
1716         // filter caller's access to input methods
1717         methodList.removeIf(imi ->
1718                 !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
1719         return methodList;
1720     }
1721 
getEnabledInputMethodListInternal(@serIdInt int userId, int callingUid)1722     private List<InputMethodInfo> getEnabledInputMethodListInternal(@UserIdInt int userId,
1723             int callingUid) {
1724         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
1725         final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList();
1726         // filter caller's access to input methods
1727         methodList.removeIf(imi ->
1728                 !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
1729         return methodList;
1730     }
1731 
1732     /**
1733      * Gets enabled subtypes of the specified {@link InputMethodInfo}.
1734      *
1735      * @param imiId                           if null, returns enabled subtypes for the current
1736      *                                        {@link InputMethodInfo}
1737      * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled
1738      *                                        subtypes
1739      * @param userId                          the user ID to be queried about
1740      */
1741     @Override
getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId)1742     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
1743             boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
1744         if (UserHandle.getCallingUserId() != userId) {
1745             mContext.enforceCallingOrSelfPermission(
1746                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
1747         }
1748 
1749         final int callingUid = Binder.getCallingUid();
1750         final long ident = Binder.clearCallingIdentity();
1751         try {
1752             return getEnabledInputMethodSubtypeListInternal(imiId,
1753                     allowsImplicitlyEnabledSubtypes, userId, callingUid);
1754         } finally {
1755             Binder.restoreCallingIdentity(ident);
1756         }
1757     }
1758 
getEnabledInputMethodSubtypeListInternal(String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid)1759     private List<InputMethodSubtype> getEnabledInputMethodSubtypeListInternal(String imiId,
1760             boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
1761         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
1762         final InputMethodInfo imi = settings.getMethodMap().get(imiId);
1763         if (imi == null) {
1764             return Collections.emptyList();
1765         }
1766         if (!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)) {
1767             return Collections.emptyList();
1768         }
1769         return settings.getEnabledInputMethodSubtypeList(
1770                 imi, allowsImplicitlyEnabledSubtypes);
1771     }
1772 
1773     /**
1774      * Called by each application process as a preparation to start interacting with
1775      * {@link InputMethodManagerService}.
1776      *
1777      * <p>As a general principle, IPCs from the application process that take
1778      * {@link IInputMethodClient} will be rejected without this step.</p>
1779      *
1780      * @param client                  {@link android.os.Binder} proxy that is associated with the
1781      *                                singleton instance of
1782      *                                {@link android.view.inputmethod.InputMethodManager} that runs
1783      *                                on the client process
1784      * @param fallbackInputConnection communication channel for the fallback {@link InputConnection}
1785      * @param selfReportedDisplayId   self-reported display ID to which the client is associated.
1786      *                                Whether the client is still allowed to access to this display
1787      *                                or not needs to be evaluated every time the client interacts
1788      *                                with the display
1789      */
1790     @Override
addClient(@onNull IInputMethodClient client, @NonNull IRemoteInputConnection fallbackInputConnection, int selfReportedDisplayId)1791     public void addClient(@NonNull IInputMethodClient client,
1792             @NonNull IRemoteInputConnection fallbackInputConnection, int selfReportedDisplayId) {
1793         Objects.requireNonNull(client, "client must not be null");
1794         Objects.requireNonNull(fallbackInputConnection, "fallbackInputConnection must not be null");
1795         // Here there are two scenarios where this method is called:
1796         // A. IMM is being instantiated in a different process and this is an IPC from that process
1797         // B. IMM is being instantiated in the same process but Binder.clearCallingIdentity() is
1798         //    called in the caller side if necessary.
1799         // In either case the following UID/PID should be the ones where InputMethodManager is
1800         // actually running.
1801         final int callerUid = Binder.getCallingUid();
1802         final int callerPid = Binder.getCallingPid();
1803         final var clientInvoker = IInputMethodClientInvoker.create(client, mHandler);
1804         synchronized (ImfLock.class) {
1805             mClientController.addClient(clientInvoker, fallbackInputConnection,
1806                     selfReportedDisplayId, callerUid, callerPid);
1807         }
1808     }
1809 
1810     @GuardedBy("ImfLock.class")
onClientRemoved(@onNull ClientState client)1811     private void onClientRemoved(@NonNull ClientState client) {
1812         clearClientSessionLocked(client);
1813         clearClientSessionForAccessibilityLocked(client);
1814         // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
1815         @SuppressWarnings("GuardedBy") Consumer<UserData> clientRemovedForUser =
1816                 userData -> onClientRemovedInternalLocked(client, userData);
1817         mUserDataRepository.forAllUserData(clientRemovedForUser);
1818     }
1819 
1820     /**
1821      * Hide the IME if the removed user is the current user.
1822      */
1823     // TODO(b/325515685): Move this method to InputMethodBindingController
1824     @GuardedBy("ImfLock.class")
onClientRemovedInternalLocked(@onNull ClientState client, @NonNull UserData userData)1825     private void onClientRemovedInternalLocked(@NonNull ClientState client,
1826             @NonNull UserData userData) {
1827         final int userId = userData.mUserId;
1828         if (userData.mCurClient == client) {
1829             hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
1830                     SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
1831             if (userData.mBoundToMethod) {
1832                 userData.mBoundToMethod = false;
1833                 final var userBindingController = userData.mBindingController;
1834                 IInputMethodInvoker curMethod = userBindingController.getCurMethod();
1835                 if (curMethod != null) {
1836                     // When we unbind input, we are unbinding the client, so we always
1837                     // unbind ime and a11y together.
1838                     curMethod.unbindInput();
1839                     AccessibilityManagerInternal.get().unbindInput();
1840                 }
1841             }
1842             userData.mBoundToAccessibility = false;
1843             userData.mCurClient = null;
1844             if (userData.mImeBindingState.mFocusedWindowClient == client) {
1845                 userData.mImeBindingState = ImeBindingState.newEmptyState();
1846             }
1847         }
1848     }
1849 
1850     @Nullable
1851     @GuardedBy("ImfLock.class")
1852     @Override
getClientStateLocked(IInputMethodClient client)1853     public ClientState getClientStateLocked(IInputMethodClient client) {
1854         return mClientController.getClient(client.asBinder());
1855     }
1856 
1857     @GuardedBy("ImfLock.class")
unbindCurrentClientLocked(@nbindReason int unbindClientReason, @UserIdInt int userId)1858     void unbindCurrentClientLocked(@UnbindReason int unbindClientReason, @UserIdInt int userId) {
1859         final var userData = getUserData(userId);
1860         if (userData.mCurClient != null) {
1861             ProtoLog.v(IMMS_DEBUG, "unbindCurrentInputLocked: client=%s",
1862                     userData.mCurClient.mClient.asBinder());
1863             final var bindingController = userData.mBindingController;
1864             if (userData.mBoundToMethod) {
1865                 userData.mBoundToMethod = false;
1866                 IInputMethodInvoker curMethod = bindingController.getCurMethod();
1867                 if (curMethod != null) {
1868                     curMethod.unbindInput();
1869                 }
1870             }
1871             userData.mBoundToAccessibility = false;
1872 
1873             // Since we set active false to current client and set mCurClient to null, let's unbind
1874             // all accessibility too. That means, when input method get disconnected (including
1875             // switching ime), we also unbind accessibility
1876             userData.mCurClient.mClient.setActive(false /* active */, false /* fullscreen */);
1877 
1878             userData.mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(),
1879                     unbindClientReason);
1880             userData.mCurClient.mSessionRequested = false;
1881             userData.mCurClient.mSessionRequestedForAccessibility = false;
1882             userData.mCurClient = null;
1883             ImeTracker.forLogging().onFailed(userData.mCurStatsToken,
1884                     ImeTracker.PHASE_SERVER_WAIT_IME);
1885             userData.mCurStatsToken = null;
1886             // TODO: Make mMenuController multi-user aware
1887             if (Flags.imeSwitcherRevamp()) {
1888                 mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
1889             } else {
1890                 mMenuController.hideInputMethodMenuLocked(userId);
1891             }
1892         }
1893     }
1894 
1895     /**
1896      * TODO(b/338404383) Remove
1897      * Called when {@link #resetCurrentMethodAndClientLocked(int, int)} invoked for clean-up states
1898      * before unbinding the current method.
1899      */
1900     @GuardedBy("ImfLock.class")
onUnbindCurrentMethodByReset(@serIdInt int userId)1901     void onUnbindCurrentMethodByReset(@UserIdInt int userId) {
1902         final var userData = getUserData(userId);
1903         final var visibilityStateComputer = userData.mVisibilityStateComputer;
1904         final ImeTargetWindowState winState = visibilityStateComputer.getWindowStateOrNull(
1905                 userData.mImeBindingState.mFocusedWindow);
1906         if (winState != null && !winState.isRequestedImeVisible()
1907                 && !visibilityStateComputer.isInputShown()) {
1908             // Normally, the focus window will apply the IME visibility state to
1909             // WindowManager when the IME has applied it. But it would be too late when
1910             // switching IMEs in between different users. (Since the focused IME will
1911             // first unbind the service to switch to bind the next user of the IME
1912             // service, that wouldn't make the attached IME token validity check in time)
1913             // As a result, we have to notify WM to apply IME visibility before clearing the
1914             // binding states in the first place.
1915             final var statsToken = createStatsTokenForFocusedClient(false /* show */,
1916                     SoftInputShowHideReason.UNBIND_CURRENT_METHOD, userId);
1917             mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow,
1918                     statsToken, STATE_HIDE_IME, SoftInputShowHideReason.NOT_SET /* ignore reason */,
1919                     userId);
1920         }
1921     }
1922 
1923     @GuardedBy("ImfLock.class")
isShowRequestedForCurrentWindow(@serIdInt int userId)1924     private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) {
1925         final var userData = getUserData(userId);
1926         final var visibilityStateComputer = userData.mVisibilityStateComputer;
1927         final ImeTargetWindowState state = visibilityStateComputer.getWindowStateOrNull(
1928                 userData.mImeBindingState.mFocusedWindow);
1929         return state != null && state.isRequestedImeVisible();
1930     }
1931 
1932     @GuardedBy("ImfLock.class")
1933     @NonNull
attachNewInputLocked(@tartInputReason int startInputReason, boolean initial, @UserIdInt int userId)1934     InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial,
1935             @UserIdInt int userId) {
1936         final var userData = getUserData(userId);
1937         final var bindingController = userData.mBindingController;
1938         if (!userData.mBoundToMethod) {
1939             bindingController.getCurMethod().bindInput(userData.mCurClient.mBinding);
1940             userData.mBoundToMethod = true;
1941         }
1942 
1943         final boolean restarting = !initial;
1944         final Binder startInputToken = new Binder();
1945         final StartInputInfo info = new StartInputInfo(userId,
1946                 bindingController.getCurToken(), bindingController.getCurTokenDisplayId(),
1947                 bindingController.getCurId(), startInputReason,
1948                 restarting, UserHandle.getUserId(userData.mCurClient.mUid),
1949                 userData.mCurClient.mSelfReportedDisplayId,
1950                 userData.mImeBindingState.mFocusedWindow, userData.mCurEditorInfo,
1951                 userData.mImeBindingState.mFocusedWindowSoftInputMode,
1952                 bindingController.getSequenceNumber());
1953         mImeTargetWindowMap.put(startInputToken, userData.mImeBindingState.mFocusedWindow);
1954         mStartInputHistory.addEntry(info);
1955 
1956         // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user
1957         // implicit visibility (e.g. IME[user=10] -> App[user=0]) thus we do this only for the
1958         // same-user scenarios.
1959         // That said ignoring cross-user scenario will never affect IMEs that do not have
1960         // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
1961         if (userId == UserHandle.getUserId(userData.mCurClient.mUid)) {
1962             mPackageManagerInternal.grantImplicitAccess(userId, null /* intent */,
1963                     UserHandle.getAppId(bindingController.getCurMethodUid()),
1964                     userData.mCurClient.mUid, true /* direct */);
1965         }
1966 
1967         @InputMethodNavButtonFlags final int navButtonFlags =
1968                 getInputMethodNavButtonFlagsLocked(userData);
1969         final SessionState session = userData.mCurClient.mCurSession;
1970         setEnabledSessionLocked(session, userData);
1971         session.mMethod.startInput(startInputToken, userData.mCurInputConnection,
1972                 userData.mCurEditorInfo, restarting, navButtonFlags, userData.mCurImeDispatcher);
1973         if (Flags.refactorInsetsController()) {
1974             if (isShowRequestedForCurrentWindow(userId)
1975                     && userData.mImeBindingState.mFocusedWindow != null) {
1976                 // Re-use current statsToken, if it exists.
1977                 final var statsToken = userData.mCurStatsToken != null ? userData.mCurStatsToken
1978                         : createStatsTokenForFocusedClient(true /* show */,
1979                                 SoftInputShowHideReason.ATTACH_NEW_INPUT, userId);
1980                 userData.mCurStatsToken = null;
1981                 showCurrentInputInternal(userData.mImeBindingState.mFocusedWindow, statsToken);
1982             }
1983         } else {
1984             if (isShowRequestedForCurrentWindow(userId)) {
1985                 ProtoLog.v(IMMS_DEBUG, "Attach new input asks to show input");
1986                 // Re-use current statsToken, if it exists.
1987                 final var statsToken = userData.mCurStatsToken != null ? userData.mCurStatsToken
1988                     : createStatsTokenForFocusedClient(true /* show */,
1989                             SoftInputShowHideReason.ATTACH_NEW_INPUT, userId);
1990                 userData.mCurStatsToken = null;
1991                 final var visibilityStateComputer = userData.mVisibilityStateComputer;
1992                 showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, statsToken,
1993                         visibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN,
1994                         null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT,
1995                         userId);
1996             }
1997         }
1998 
1999         final var curId = bindingController.getCurId();
2000         final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(userId)
2001                 .getMethodMap().get(curId);
2002         final boolean suppressesSpellChecker =
2003                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
2004         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
2005                 createAccessibilityInputMethodSessions(
2006                         userData.mCurClient.mAccessibilitySessions);
2007         if (bindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
2008             mHwController.setInkWindowInitializer(new InkWindowInitializer());
2009         }
2010         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
2011                 session.mSession, accessibilityInputMethodSessions,
2012                 (session.mChannel != null ? session.mChannel.dup() : null),
2013                 curId, bindingController.getSequenceNumber(), suppressesSpellChecker);
2014     }
2015 
2016     @GuardedBy("ImfLock.class")
attachNewAccessibilityLocked(@tartInputReason int startInputReason, boolean initial, @UserIdInt int userId)2017     private void attachNewAccessibilityLocked(@StartInputReason int startInputReason,
2018             boolean initial, @UserIdInt int userId) {
2019         final var userData = getUserData(userId);
2020 
2021         if (!userData.mBoundToAccessibility) {
2022             AccessibilityManagerInternal.get().bindInput();
2023             userData.mBoundToAccessibility = true;
2024         }
2025 
2026         // TODO(b/187453053): grantImplicitAccess to accessibility services access? if so, need to
2027         //  record accessibility services uid.
2028 
2029         // We don't start input when session for a11y is created. We start input when
2030         // input method start input, a11y manager service is always on.
2031         if (startInputReason != StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY) {
2032             setEnabledSessionForAccessibilityLocked(userData.mCurClient.mAccessibilitySessions,
2033                     userData);
2034             AccessibilityManagerInternal.get().startInput(
2035                     userData.mCurRemoteAccessibilityInputConnection,
2036                     userData.mCurEditorInfo, !initial /* restarting */);
2037         }
2038     }
2039 
2040     @NonNull
createAccessibilityInputMethodSessions( @onNull SparseArray<AccessibilitySessionState> accessibilitySessions)2041     private SparseArray<IAccessibilityInputMethodSession> createAccessibilityInputMethodSessions(
2042             @NonNull SparseArray<AccessibilitySessionState> accessibilitySessions) {
2043         final var accessibilityInputMethodSessions =
2044                 new SparseArray<IAccessibilityInputMethodSession>();
2045         for (int i = 0; i < accessibilitySessions.size(); i++) {
2046             accessibilityInputMethodSessions.append(accessibilitySessions.keyAt(i),
2047                     accessibilitySessions.valueAt(i).mSession);
2048         }
2049         return accessibilityInputMethodSessions;
2050     }
2051 
2052     /**
2053      * Called by {@link #startInputOrWindowGainedFocusInternalLocked} to bind/unbind/attach the
2054      * selected InputMethod to the given focused IME client.
2055      *
2056      * Note that this should be called after validating if the IME client has IME focus.
2057      *
2058      * @see WindowManagerInternal#hasInputMethodClientFocus(IBinder, int, int, int)
2059      */
2060     @GuardedBy("ImfLock.class")
2061     @NonNull
startInputUncheckedLocked(@onNull ClientState cs, IRemoteInputConnection inputConnection, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, int unverifiedTargetSdkVersion, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull InputMethodBindingController bindingController)2062     private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
2063             IRemoteInputConnection inputConnection,
2064             @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
2065             @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags,
2066             @StartInputReason int startInputReason,
2067             int unverifiedTargetSdkVersion,
2068             @NonNull ImeOnBackInvokedDispatcher imeDispatcher,
2069             @NonNull InputMethodBindingController bindingController) {
2070 
2071         final int userId = bindingController.getUserId();
2072         final var userData = getUserData(userId);
2073         final var visibilityStateComputer = userData.mVisibilityStateComputer;
2074 
2075         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
2076         // session & other conditions.
2077         ImeTargetWindowState winState = visibilityStateComputer.getWindowStateOrNull(
2078                 userData.mImeBindingState.mFocusedWindow);
2079         if (winState == null) {
2080             return InputBindResult.NOT_IME_TARGET_WINDOW;
2081         }
2082         final int csDisplayId = cs.mSelfReportedDisplayId;
2083         bindingController.setDisplayIdToShowIme(
2084                 visibilityStateComputer.computeImeDisplayId(winState, csDisplayId));
2085 
2086         // Potentially override the selected input method if the new display belongs to a virtual
2087         // device with a custom IME.
2088         String selectedMethodId = bindingController.getSelectedMethodId();
2089         final String deviceMethodId = computeCurrentDeviceMethodIdLocked(
2090                 bindingController.getUserId(), selectedMethodId);
2091         if (deviceMethodId == null) {
2092             visibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
2093         } else if (!Objects.equals(deviceMethodId, selectedMethodId)) {
2094             setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_INDEX,
2095                     bindingController.getDeviceIdToShowIme(), userId);
2096             selectedMethodId = deviceMethodId;
2097         }
2098 
2099         if (visibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
2100             hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
2101                     SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId);
2102             return InputBindResult.NO_IME;
2103         }
2104 
2105         // If no method is currently selected, do nothing.
2106         if (selectedMethodId == null) {
2107             return InputBindResult.NO_IME;
2108         }
2109 
2110         if (userData.mCurClient != cs) {
2111             prepareClientSwitchLocked(cs, userId);
2112         }
2113 
2114         final boolean connectionWasActive = userData.mCurInputConnection != null;
2115 
2116         // Bump up the sequence for this client and attach it.
2117         bindingController.advanceSequenceNumber();
2118 
2119         userData.mCurClient = cs;
2120         userData.mCurInputConnection = inputConnection;
2121         userData.mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection;
2122         userData.mCurImeDispatcher = imeDispatcher;
2123         // Override the locale hints if the app is running on a virtual device.
2124         if (mVdmInternal == null) {
2125             mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
2126         }
2127         if (mVdmInternal != null && editorInfo.hintLocales == null) {
2128             LocaleList hintsFromVirtualDevice = mVdmInternal.getPreferredLocaleListForUid(cs.mUid);
2129             if (hintsFromVirtualDevice != null) {
2130                 editorInfo.hintLocales = hintsFromVirtualDevice;
2131             }
2132         }
2133         userData.mCurEditorInfo = editorInfo;
2134 
2135         // Notify input manager if the connection state changes.
2136         final boolean connectionIsActive = userData.mCurInputConnection != null;
2137         if (connectionIsActive != connectionWasActive) {
2138             mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
2139         }
2140 
2141         // If configured, we want to avoid starting up the IME if it is not supposed to be showing
2142         if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
2143                 unverifiedTargetSdkVersion, userId)) {
2144             ProtoLog.v(IMMS_DEBUG, "Avoiding IME startup and unbinding current input method.");
2145             bindingController.invalidateAutofillSession();
2146             bindingController.unbindCurrentMethod();
2147             return InputBindResult.NO_EDITOR;
2148         }
2149 
2150         // Check if the input method is changing.
2151         // We expect the caller has already verified that the client is allowed to access this
2152         // display ID.
2153         final String curId = bindingController.getCurId();
2154         final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
2155         if (curId != null && curId.equals(bindingController.getSelectedMethodId())
2156                 && displayIdToShowIme == bindingController.getCurTokenDisplayId()) {
2157             if (cs.mCurSession != null) {
2158                 // Fast case: if we are already connected to the input method,
2159                 // then just return it.
2160                 // This doesn't mean a11y sessions are there. When a11y service is
2161                 // enabled while this client is switched out, this client doesn't have the session.
2162                 // A11yManagerService will only request missing sessions (will not request existing
2163                 // sessions again). Note when an a11y service is disabled, it will clear its
2164                 // session from all clients, so we don't need to worry about disabled a11y services.
2165                 cs.mSessionRequestedForAccessibility = false;
2166                 requestClientSessionForAccessibilityLocked(cs);
2167                 // we can always attach to accessibility because AccessibilityManagerService is
2168                 // always on.
2169                 attachNewAccessibilityLocked(startInputReason,
2170                         (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, userId);
2171                 return attachNewInputLocked(startInputReason,
2172                         (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, userId);
2173             }
2174 
2175             InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs, userId);
2176             if (bindResult != null) {
2177                 return bindResult;
2178             }
2179         }
2180 
2181         bindingController.unbindCurrentMethod();
2182         return bindingController.bindCurrentMethod();
2183     }
2184 
2185     /**
2186      * Update the current deviceId and return the relevant imeId for this device.
2187      *
2188      * <p>1. If the device changes to virtual and its custom IME is not available, then disable
2189      * IME.</p>
2190      * <p>2. If the device changes to virtual with valid custom IME, then return the custom IME. If
2191      * the old device was default, then store the current imeId so it can be restored.</p>
2192      * <p>3. If the device changes to default, restore the default device IME.</p>
2193      * <p>4. Otherwise keep the current imeId.</p>
2194      */
2195     @GuardedBy("ImfLock.class")
computeCurrentDeviceMethodIdLocked(@serIdInt int userId, String currentMethodId)2196     private String computeCurrentDeviceMethodIdLocked(@UserIdInt int userId,
2197             String currentMethodId) {
2198         if (mVdmInternal == null) {
2199             mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
2200         }
2201         if (mVdmInternal == null) {
2202             return currentMethodId;
2203         }
2204 
2205         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
2206         final var bindingController = getInputMethodBindingController(userId);
2207         final int oldDeviceId = bindingController.getDeviceIdToShowIme();
2208         final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
2209         int newDeviceId = mVdmInternal.getDeviceIdForDisplayId(displayIdToShowIme);
2210         if (newDeviceId != DEVICE_ID_DEFAULT) {
2211             // Only show custom IME on trusted displays.
2212             if (mDisplayManagerInternal == null) {
2213                 mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
2214             }
2215             int displayFlags = mDisplayManagerInternal.getDisplayInfo(displayIdToShowIme).flags;
2216             if ((displayFlags & Display.FLAG_TRUSTED) != Display.FLAG_TRUSTED) {
2217                 // If the display is not trusted, fallback to the default device IME.
2218                 newDeviceId = DEVICE_ID_DEFAULT;
2219             }
2220         }
2221         bindingController.setDeviceIdToShowIme(newDeviceId);
2222         if (newDeviceId == DEVICE_ID_DEFAULT) {
2223             if (oldDeviceId == DEVICE_ID_DEFAULT) {
2224                 return currentMethodId;
2225             }
2226             final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod();
2227             ProtoLog.v(IMMS_DEBUG, "Restoring default device input method: %s",
2228                     defaultDeviceMethodId);
2229             settings.putSelectedDefaultDeviceInputMethod(null);
2230             return defaultDeviceMethodId;
2231         }
2232 
2233         final String deviceMethodId = mVirtualDeviceMethodMap.get(newDeviceId, currentMethodId);
2234         if (Objects.equals(deviceMethodId, currentMethodId)) {
2235             return currentMethodId;
2236         } else if (!settings.getMethodMap().containsKey(deviceMethodId)) {
2237             ProtoLog.v(IMMS_DEBUG,
2238                     "Disabling IME on virtual device with id %s because its custom input method "
2239                             + "is not available: %s",
2240                     newDeviceId, deviceMethodId);
2241             return null;
2242         }
2243 
2244         if (oldDeviceId == DEVICE_ID_DEFAULT) {
2245             ProtoLog.v(IMMS_DEBUG, "Storing default device input method %s", currentMethodId);
2246             settings.putSelectedDefaultDeviceInputMethod(currentMethodId);
2247         }
2248         ProtoLog.v(IMMS_DEBUG,
2249                 "Switching current input method from %s to device-specific one %s because the "
2250                         + "current display %s belongs to device with id %s",
2251                 currentMethodId, deviceMethodId, displayIdToShowIme, newDeviceId);
2252         return deviceMethodId;
2253     }
2254 
2255     @GuardedBy("ImfLock.class")
shouldPreventImeStartupLocked( @onNull String selectedMethodId, @StartInputFlags int startInputFlags, int unverifiedTargetSdkVersion, @UserIdInt int userId)2256     private boolean shouldPreventImeStartupLocked(
2257             @NonNull String selectedMethodId,
2258             @StartInputFlags int startInputFlags,
2259             int unverifiedTargetSdkVersion,
2260             @UserIdInt int userId) {
2261         // Fast-path for the majority of cases
2262         if (!mPreventImeStartupUnlessTextEditor) {
2263             return false;
2264         }
2265         if (isShowRequestedForCurrentWindow(userId)) {
2266             return false;
2267         }
2268         if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
2269             return false;
2270         }
2271         final InputMethodInfo imi = InputMethodSettingsRepository.get(userId)
2272                 .getMethodMap().get(selectedMethodId);
2273         if (imi == null) {
2274             return false;
2275         }
2276         if (ArrayUtils.contains(mNonPreemptibleInputMethods, imi.getPackageName())) {
2277             return false;
2278         }
2279         return true;
2280     }
2281 
2282     @GuardedBy("ImfLock.class")
prepareClientSwitchLocked(ClientState cs, @UserIdInt int userId)2283     private void prepareClientSwitchLocked(ClientState cs, @UserIdInt int userId) {
2284         // If the client is changing, we need to switch over to the new
2285         // one.
2286         unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT, userId);
2287         // If the screen is on, inform the new client it is active
2288         if (mIsInteractive) {
2289             cs.mClient.setActive(true /* active */, false /* fullscreen */);
2290         }
2291     }
2292 
2293     @GuardedBy("ImfLock.class")
2294     @Nullable
tryReuseConnectionLocked( @onNull InputMethodBindingController bindingController, @NonNull ClientState cs, @UserIdInt int userId)2295     private InputBindResult tryReuseConnectionLocked(
2296             @NonNull InputMethodBindingController bindingController, @NonNull ClientState cs,
2297             @UserIdInt int userId) {
2298         if (bindingController.hasMainConnection()) {
2299             if (bindingController.getCurMethod() != null) {
2300                 if (!Flags.useZeroJankProxy()) {
2301                     // Return to client, and we will get back with it when
2302                     // we have had a session made for it.
2303                     requestClientSessionLocked(cs, userId);
2304                     requestClientSessionForAccessibilityLocked(cs);
2305                 }
2306                 return new InputBindResult(
2307                         InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
2308                         null, null, null,
2309                         bindingController.getCurId(),
2310                         bindingController.getSequenceNumber(), false);
2311             } else {
2312                 final long lastBindTime = bindingController.getLastBindTime();
2313                 long bindingDuration = SystemClock.uptimeMillis() - lastBindTime;
2314                 if (bindingDuration < TIME_TO_RECONNECT) {
2315                     // In this case we have connected to the service, but
2316                     // don't yet have its interface.  If it hasn't been too
2317                     // long since we did the connection, we'll return to
2318                     // the client and wait to get the service interface so
2319                     // we can report back.  If it has been too long, we want
2320                     // to fall through so we can try a disconnect/reconnect
2321                     // to see if we can get back in touch with the service.
2322                     return new InputBindResult(
2323                             InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
2324                             null, null, null,
2325                             bindingController.getCurId(),
2326                             bindingController.getSequenceNumber(), false);
2327                 } else {
2328                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
2329                             bindingController.getSelectedMethodId(), bindingDuration, 0);
2330                 }
2331             }
2332         }
2333         return null;
2334     }
2335 
2336     @FunctionalInterface
2337     interface ImeDisplayValidator {
2338         @DisplayImePolicy
getDisplayImePolicy(int displayId)2339         int getDisplayImePolicy(int displayId);
2340     }
2341 
2342     /**
2343      * Find the display where the IME should be shown.
2344      *
2345      * @param displayId the ID of the display where the IME client target is
2346      * @param checker   instance of {@link ImeDisplayValidator} which is used for
2347      *                  checking display config to adjust the final target display
2348      * @return the ID of the display where the IME should be shown or
2349      * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of
2350      * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}
2351      */
computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker)2352     static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
2353         return computeImeDisplayIdForTargetInner(displayId, checker, FALLBACK_DISPLAY_ID);
2354     }
2355 
2356     /**
2357      * Find the display where the IME should be shown for a visible background user.
2358      *
2359      * @param displayId the ID of the display where the IME client target is
2360      * @param userId the ID of the user who own the IME
2361      * @param checker   instance of {@link ImeDisplayValidator} which is used for
2362      *                  checking display config to adjust the final target display
2363      * @return the ID of the display where the IME should be shown or
2364      * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of
2365      * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}
2366      */
computeImeDisplayIdForVisibleBackgroundUserOnAutomotive( int displayId, @UserIdInt int userId, @NonNull ImeDisplayValidator checker)2367     int computeImeDisplayIdForVisibleBackgroundUserOnAutomotive(
2368             int displayId, @UserIdInt int userId, @NonNull ImeDisplayValidator checker) {
2369         // Visible background user can be assigned to a secondary display, not the default display.
2370         // The main display assigned to the user will be used as the fallback display.
2371         final int mainDisplayId = mUserManagerInternal.getMainDisplayAssignedToUser(userId);
2372         return computeImeDisplayIdForTargetInner(displayId, checker, mainDisplayId);
2373     }
2374 
computeImeDisplayIdForTargetInner( int displayId, @NonNull ImeDisplayValidator checker, int fallbackDisplayId)2375     private static int computeImeDisplayIdForTargetInner(
2376             int displayId, @NonNull ImeDisplayValidator checker, int fallbackDisplayId) {
2377         if (displayId == fallbackDisplayId || displayId == INVALID_DISPLAY) {
2378             return fallbackDisplayId;
2379         }
2380 
2381         // Show IME window on fallback display when the display doesn't support system decorations
2382         // or the display is virtual and isn't owned by system for security concern.
2383         final int result = checker.getDisplayImePolicy(displayId);
2384         if (result == DISPLAY_IME_POLICY_LOCAL) {
2385             return displayId;
2386         } else if (result == DISPLAY_IME_POLICY_HIDE) {
2387             return INVALID_DISPLAY;
2388         }
2389         return fallbackDisplayId;
2390     }
2391 
2392     @GuardedBy("ImfLock.class")
initializeImeLocked(@onNull IInputMethodInvoker inputMethod, @NonNull IBinder token, @NonNull InputMethodBindingController bindingController)2393     void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
2394             @NonNull InputMethodBindingController bindingController) {
2395         ProtoLog.v(IMMS_DEBUG, "Sending attach of token: %s for display: %s", token,
2396                 bindingController.getCurTokenDisplayId());
2397         final int userId = bindingController.getUserId();
2398         final var userData = getUserData(userId);
2399         inputMethod.initializeInternal(token,
2400                 new InputMethodPrivilegedOperationsImpl(this, token, userData),
2401                 getInputMethodNavButtonFlagsLocked(userData));
2402     }
2403 
2404     @AnyThread
scheduleResetStylusHandwriting()2405     void scheduleResetStylusHandwriting() {
2406         mHandler.obtainMessage(MSG_RESET_HANDWRITING).sendToTarget();
2407     }
2408 
2409     @AnyThread
schedulePrepareStylusHandwritingDelegation(@serIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName)2410     void schedulePrepareStylusHandwritingDelegation(@UserIdInt int userId,
2411             @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
2412         mHandler.obtainMessage(
2413                 MSG_PREPARE_HANDWRITING_DELEGATION, userId, 0 /* unused */,
2414                 new Pair<>(delegatePackageName, delegatorPackageName)).sendToTarget();
2415     }
2416 
2417     @AnyThread
scheduleRemoveStylusHandwritingWindow()2418     void scheduleRemoveStylusHandwritingWindow() {
2419         mHandler.obtainMessage(MSG_REMOVE_HANDWRITING_WINDOW).sendToTarget();
2420     }
2421 
2422     @AnyThread
scheduleNotifyImeUidToAudioService(int uid)2423     void scheduleNotifyImeUidToAudioService(int uid) {
2424         mHandler.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
2425         mHandler.obtainMessage(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid, 0 /* unused */)
2426                 .sendToTarget();
2427     }
2428 
2429     @BinderThread
onSessionCreated(IInputMethodInvoker method, IInputMethodSession session, InputChannel channel, @UserIdInt int userId)2430     void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session,
2431             InputChannel channel, @UserIdInt int userId) {
2432         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated");
2433         try {
2434             synchronized (ImfLock.class) {
2435                 if (mUserSwitchHandlerTask != null) {
2436                     // We have a pending user-switching task so it's better to just ignore this
2437                     // session.
2438                     channel.dispose();
2439                     return;
2440                 }
2441                 final var userData = getUserData(userId);
2442                 final var bindingController = userData.mBindingController;
2443                 IInputMethodInvoker curMethod = bindingController.getCurMethod();
2444                 if (curMethod != null && method != null
2445                         && curMethod.asBinder() == method.asBinder()) {
2446                     if (userData.mCurClient != null) {
2447                         clearClientSessionLocked(userData.mCurClient);
2448                         userData.mCurClient.mCurSession = new SessionState(
2449                                 userData.mCurClient, method, session, channel, userId);
2450                         InputBindResult res = attachNewInputLocked(
2451                                 StartInputReason.SESSION_CREATED_BY_IME, true, userId);
2452                         attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, true,
2453                                 userId);
2454                         if (res.method != null) {
2455                             userData.mCurClient.mClient.onBindMethod(res);
2456                         }
2457                         return;
2458                     }
2459                 }
2460             }
2461 
2462             // Session abandoned.  Close its associated input channel.
2463             channel.dispose();
2464         } finally {
2465             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
2466         }
2467     }
2468 
2469     @GuardedBy("ImfLock.class")
resetSystemUiLocked(InputMethodBindingController bindingController)2470     void resetSystemUiLocked(InputMethodBindingController bindingController) {
2471         // Set IME window status as invisible when unbinding current method.
2472         final int imeWindowVis = 0;
2473         final int backDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
2474         bindingController.setImeWindowVis(imeWindowVis);
2475         bindingController.setBackDisposition(backDisposition);
2476         updateSystemUiLocked(imeWindowVis, backDisposition, bindingController.getUserId());
2477     }
2478 
2479     @GuardedBy("ImfLock.class")
resetCurrentMethodAndClientLocked(@nbindReason int unbindClientReason, @UserIdInt int userId)2480     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason,
2481             @UserIdInt int userId) {
2482         final var bindingController = getInputMethodBindingController(userId);
2483         bindingController.setSelectedMethodId(null);
2484 
2485         // Callback before clean-up binding states.
2486         // TODO(b/338461930): Check if this is still necessary or not.
2487         onUnbindCurrentMethodByReset(userId);
2488         bindingController.unbindCurrentMethod();
2489         unbindCurrentClientLocked(unbindClientReason, userId);
2490     }
2491 
2492     @GuardedBy("ImfLock.class")
reRequestCurrentClientSessionLocked(@serIdInt int userId)2493     void reRequestCurrentClientSessionLocked(@UserIdInt int userId) {
2494         final var userData = getUserData(userId);
2495         if (userData.mCurClient != null) {
2496             clearClientSessionLocked(userData.mCurClient);
2497             clearClientSessionForAccessibilityLocked(userData.mCurClient);
2498             requestClientSessionLocked(userData.mCurClient, userId);
2499             requestClientSessionForAccessibilityLocked(userData.mCurClient);
2500         }
2501     }
2502 
2503     @GuardedBy("ImfLock.class")
requestClientSessionLocked(ClientState cs, @UserIdInt int userId)2504     void requestClientSessionLocked(ClientState cs, @UserIdInt int userId) {
2505         if (!cs.mSessionRequested) {
2506             ProtoLog.v(IMMS_DEBUG, "Creating new session for client %s", cs);
2507             final InputChannel serverChannel;
2508             final InputChannel clientChannel;
2509             {
2510                 final InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
2511                 serverChannel = channels[0];
2512                 clientChannel = channels[1];
2513             }
2514 
2515             cs.mSessionRequested = true;
2516 
2517             final var bindingController = getInputMethodBindingController(userId);
2518             final IInputMethodInvoker curMethod = bindingController.getCurMethod();
2519             final IInputMethodSessionCallback.Stub callback =
2520                     new IInputMethodSessionCallback.Stub() {
2521                         @Override
2522                         public void sessionCreated(IInputMethodSession session) {
2523                             final long ident = Binder.clearCallingIdentity();
2524                             try {
2525                                 onSessionCreated(curMethod, session, serverChannel, userId);
2526                             } finally {
2527                                 Binder.restoreCallingIdentity(ident);
2528                             }
2529                         }
2530                     };
2531 
2532             try {
2533                 curMethod.createSession(clientChannel, callback);
2534             } finally {
2535                 // Dispose the channel because the remote proxy will get its own copy when
2536                 // unparceled.
2537                 if (clientChannel != null) {
2538                     clientChannel.dispose();
2539                 }
2540             }
2541         }
2542     }
2543 
2544     @GuardedBy("ImfLock.class")
requestClientSessionForAccessibilityLocked(ClientState cs)2545     void requestClientSessionForAccessibilityLocked(ClientState cs) {
2546         if (!cs.mSessionRequestedForAccessibility) {
2547             ProtoLog.v(IMMS_DEBUG, "Creating new accessibility sessions for client %s", cs);
2548             cs.mSessionRequestedForAccessibility = true;
2549             ArraySet<Integer> ignoreSet = new ArraySet<>();
2550             for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) {
2551                 ignoreSet.add(cs.mAccessibilitySessions.keyAt(i));
2552             }
2553             AccessibilityManagerInternal.get().createImeSession(ignoreSet);
2554         }
2555     }
2556 
2557     @GuardedBy("ImfLock.class")
clearClientSessionLocked(@onNull ClientState cs)2558     void clearClientSessionLocked(@NonNull ClientState cs) {
2559         finishSessionLocked(cs.mCurSession);
2560         cs.mCurSession = null;
2561         cs.mSessionRequested = false;
2562     }
2563 
2564     @GuardedBy("ImfLock.class")
clearClientSessionForAccessibilityLocked(@onNull ClientState cs)2565     void clearClientSessionForAccessibilityLocked(@NonNull ClientState cs) {
2566         for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) {
2567             finishSessionForAccessibilityLocked(cs.mAccessibilitySessions.valueAt(i));
2568         }
2569         cs.mAccessibilitySessions.clear();
2570         cs.mSessionRequestedForAccessibility = false;
2571     }
2572 
2573     @GuardedBy("ImfLock.class")
clearClientSessionForAccessibilityLocked(@onNull ClientState cs, int id)2574     void clearClientSessionForAccessibilityLocked(@NonNull ClientState cs, int id) {
2575         AccessibilitySessionState session = cs.mAccessibilitySessions.get(id);
2576         if (session != null) {
2577             finishSessionForAccessibilityLocked(session);
2578             cs.mAccessibilitySessions.remove(id);
2579         }
2580     }
2581 
2582     @GuardedBy("ImfLock.class")
finishSessionLocked(@ullable SessionState sessionState)2583     private void finishSessionLocked(@Nullable SessionState sessionState) {
2584         if (sessionState != null) {
2585             if (sessionState.mSession != null) {
2586                 try {
2587                     sessionState.mSession.finishSession();
2588                 } catch (RemoteException e) {
2589                     Slog.w(TAG, "Session failed to close due to remote exception", e);
2590                     final int userId = sessionState.mUserId;
2591                     final var bindingController = getInputMethodBindingController(userId);
2592                     updateSystemUiLocked(0 /* vis */, bindingController.getBackDisposition(),
2593                             userId);
2594                 }
2595                 sessionState.mSession = null;
2596             }
2597             if (sessionState.mChannel != null) {
2598                 sessionState.mChannel.dispose();
2599                 sessionState.mChannel = null;
2600             }
2601         }
2602     }
2603 
2604     @GuardedBy("ImfLock.class")
finishSessionForAccessibilityLocked(AccessibilitySessionState sessionState)2605     private void finishSessionForAccessibilityLocked(AccessibilitySessionState sessionState) {
2606         if (sessionState != null) {
2607             if (sessionState.mSession != null) {
2608                 try {
2609                     sessionState.mSession.finishSession();
2610                 } catch (RemoteException e) {
2611                     Slog.w(TAG, "Session failed to close due to remote exception", e);
2612                 }
2613                 sessionState.mSession = null;
2614             }
2615         }
2616     }
2617 
2618     @GuardedBy("ImfLock.class")
clearClientSessionsLocked(@onNull InputMethodBindingController bindingController)2619     void clearClientSessionsLocked(@NonNull InputMethodBindingController bindingController) {
2620         final int userId = bindingController.getUserId();
2621         final var userData = getUserData(userId);
2622         if (bindingController.getCurMethod() != null) {
2623             // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
2624             @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c -> {
2625                 // TODO(b/305849394): Figure out what we should do for single user IME mode.
2626                 final boolean shouldClearClientSession =
2627                         !mConcurrentMultiUserModeEnabled
2628                                 || UserHandle.getUserId(c.mUid) == userId;
2629                 if (shouldClearClientSession) {
2630                     clearClientSessionLocked(c);
2631                     clearClientSessionForAccessibilityLocked(c);
2632                 }
2633             };
2634             mClientController.forAllClients(clearClientSession);
2635 
2636             finishSessionLocked(userData.mEnabledSession);
2637             for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) {
2638                 finishSessionForAccessibilityLocked(
2639                         userData.mEnabledAccessibilitySessions.valueAt(i));
2640             }
2641             userData.mEnabledSession = null;
2642             userData.mEnabledAccessibilitySessions.clear();
2643             scheduleNotifyImeUidToAudioService(Process.INVALID_UID);
2644         }
2645         hideStatusBarIconLocked(userId);
2646         getUserData(userId).mInFullscreenMode = false;
2647         mWindowManagerInternal.setDismissImeOnBackKeyPressed(false);
2648         scheduleResetStylusHandwriting();
2649     }
2650 
2651     @BinderThread
2652     @GuardedBy("ImfLock.class")
updateStatusIconLocked(String packageName, @DrawableRes int iconId, @NonNull UserData userData)2653     private void updateStatusIconLocked(String packageName, @DrawableRes int iconId,
2654             @NonNull UserData userData) {
2655         final int userId = userData.mUserId;
2656         // To minimize app compat risk, ignore background users' request for single-user mode.
2657         // TODO(b/357178609): generalize the logic and remove this special rule.
2658         if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
2659             return;
2660         }
2661         if (iconId == 0) {
2662             ProtoLog.v(IMMS_DEBUG, "hide the small icon for the input method");
2663             hideStatusBarIconLocked(userId);
2664         } else if (packageName != null) {
2665             ProtoLog.v(IMMS_DEBUG, "show a small icon for the input method");
2666             final PackageManager userAwarePackageManager =
2667                     getPackageManagerForUser(mContext, userId);
2668             ApplicationInfo applicationInfo = null;
2669             try {
2670                 applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
2671                         PackageManager.ApplicationInfoFlags.of(0));
2672             } catch (PackageManager.NameNotFoundException e) {
2673             }
2674             final CharSequence contentDescription = applicationInfo != null
2675                     ? userAwarePackageManager.getApplicationLabel(applicationInfo)
2676                     : null;
2677             if (mStatusBarManagerInternal != null) {
2678                 mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0,
2679                         contentDescription != null
2680                                 ? contentDescription.toString() : null);
2681                 mStatusBarManagerInternal.setIconVisibility(mSlotIme, true);
2682             }
2683         }
2684     }
2685 
2686     @GuardedBy("ImfLock.class")
hideStatusBarIconLocked(@serIdInt int userId)2687     private void hideStatusBarIconLocked(@UserIdInt int userId) {
2688         // To minimize app compat risk, ignore background users' request for single-user mode.
2689         // TODO(b/357178609): generalize the logic and remove this special rule.
2690         if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
2691             return;
2692         }
2693         if (mStatusBarManagerInternal != null) {
2694             mStatusBarManagerInternal.setIconVisibility(mSlotIme, false);
2695         }
2696     }
2697 
2698     @GuardedBy("ImfLock.class")
2699     @InputMethodNavButtonFlags
getInputMethodNavButtonFlagsLocked(@onNull UserData userData)2700     private int getInputMethodNavButtonFlagsLocked(@NonNull UserData userData) {
2701         final int userId = userData.mUserId;
2702         final var bindingController = userData.mBindingController;
2703         // Whether the current display has a navigation bar. When this is false (e.g. emulator),
2704         // the IME should not draw the IME navigation bar.
2705         final int tokenDisplayId = bindingController.getCurTokenDisplayId();
2706         final boolean hasNavigationBar = mWindowManagerInternal
2707                 .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
2708                         ? tokenDisplayId : DEFAULT_DISPLAY);
2709         final boolean canImeDrawsImeNavBar = userData.mImeDrawsNavBar.get() && hasNavigationBar;
2710         final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
2711                 InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId);
2712         return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
2713                 | (shouldShowImeSwitcherWhenImeIsShown
2714                 ? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0);
2715     }
2716 
2717     @GuardedBy("ImfLock.class")
shouldShowImeSwitcherLocked(@meWindowVisibility int visibility, @UserIdInt int userId)2718     private boolean shouldShowImeSwitcherLocked(@ImeWindowVisibility int visibility,
2719             @UserIdInt int userId) {
2720         if (!mShowOngoingImeSwitcherForPhones) return false;
2721         // When the IME switcher dialog is shown, the IME switcher button should be hidden.
2722         // TODO(b/305849394): Make mMenuController multi-user aware.
2723         final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
2724                 ? mMenuControllerNew.isShowing()
2725                 : mMenuController.getSwitchingDialogLocked() != null;
2726         if (switcherMenuShowing) {
2727             return false;
2728         }
2729         // When we are switching IMEs, the IME switcher button should be hidden.
2730         final var bindingController = getInputMethodBindingController(userId);
2731         if (!Objects.equals(bindingController.getCurId(),
2732                 bindingController.getSelectedMethodId())) {
2733             return false;
2734         }
2735         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
2736                 && mWindowManagerInternal.isKeyguardSecure(userId)) {
2737             return false;
2738         }
2739         if ((visibility & InputMethodService.IME_ACTIVE) == 0) {
2740             return false;
2741         }
2742         if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) {
2743             // When physical keyboard is attached, we show the ime switcher (or notification if
2744             // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
2745             // exists in the IME switcher dialog.  Might be OK to remove this condition once
2746             // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live.
2747             return true;
2748         } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
2749             return false;
2750         }
2751 
2752         return hasMultipleSubtypesForSwitcher(false /* nonAuxOnly */, userId);
2753     }
2754 
2755     /**
2756      * Checks whether there at least two subtypes that should be shown for the IME switcher menu,
2757      * across all enabled IMEs for the given user.
2758      *
2759      * @param nonAuxOnly whether to check only for non auxiliary subtypes.
2760      * @param userId     the id of the user for which to check the number of subtypes.
2761      */
hasMultipleSubtypesForSwitcher(boolean nonAuxOnly, @UserIdInt int userId)2762     private static boolean hasMultipleSubtypesForSwitcher(boolean nonAuxOnly,
2763             @UserIdInt int userId) {
2764         final var settings = InputMethodSettingsRepository.get(userId);
2765         List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter(
2766                 InputMethodInfo::shouldShowInInputMethodPicker);
2767         final int numImes = imes.size();
2768         if (numImes > 2) return true;
2769         if (numImes < 1) return false;
2770         int nonAuxCount = 0;
2771         int auxCount = 0;
2772         InputMethodSubtype nonAuxSubtype = null;
2773         InputMethodSubtype auxSubtype = null;
2774         for (int i = 0; i < numImes; ++i) {
2775             final InputMethodInfo imi = imes.get(i);
2776             final List<InputMethodSubtype> subtypes =
2777                     settings.getEnabledInputMethodSubtypeList(imi, true);
2778             final int subtypeCount = subtypes.size();
2779             if (subtypeCount == 0) {
2780                 ++nonAuxCount;
2781             } else {
2782                 for (int j = 0; j < subtypeCount; ++j) {
2783                     final InputMethodSubtype subtype = subtypes.get(j);
2784                     if (!subtype.isAuxiliary()) {
2785                         ++nonAuxCount;
2786                         nonAuxSubtype = subtype;
2787                     } else {
2788                         ++auxCount;
2789                         auxSubtype = subtype;
2790                     }
2791                 }
2792             }
2793         }
2794         if (Flags.imeSwitcherRevamp() && nonAuxOnly) {
2795             return nonAuxCount > 1;
2796         } else if (nonAuxCount > 1 || auxCount > 1) {
2797             return true;
2798         } else if (nonAuxCount == 1 && auxCount == 1) {
2799             if (nonAuxSubtype != null && auxSubtype != null
2800                     && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
2801                     || auxSubtype.overridesImplicitlyEnabledSubtype()
2802                     || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
2803                     && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
2804                 return false;
2805             }
2806             return true;
2807         }
2808         return false;
2809     }
2810 
2811     @BinderThread
2812     @GuardedBy("ImfLock.class")
2813     @SuppressWarnings("deprecation")
setImeWindowStatusLocked(@meWindowVisibility int vis, @BackDispositionMode int backDisposition, @NonNull UserData userData)2814     private void setImeWindowStatusLocked(@ImeWindowVisibility int vis,
2815             @BackDispositionMode int backDisposition, @NonNull UserData userData) {
2816         final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId();
2817 
2818         final int userId = userData.mUserId;
2819         final var bindingController = userData.mBindingController;
2820         // Skip update IME status when current token display is not same as focused display.
2821         // Note that we still need to update IME status when focusing external display
2822         // that does not support system decoration and fallback to show IME on default
2823         // display since it is intentional behavior.
2824         final int tokenDisplayId = bindingController.getCurTokenDisplayId();
2825         if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
2826             return;
2827         }
2828         bindingController.setImeWindowVis(vis);
2829         bindingController.setBackDisposition(backDisposition);
2830         updateSystemUiLocked(vis, backDisposition, userId);
2831 
2832         final boolean dismissImeOnBackKeyPressed;
2833         switch (backDisposition) {
2834             case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
2835                 dismissImeOnBackKeyPressed = true;
2836                 break;
2837             case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
2838                 dismissImeOnBackKeyPressed = false;
2839                 break;
2840             default:
2841             case InputMethodService.BACK_DISPOSITION_DEFAULT:
2842                 dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0);
2843                 break;
2844         }
2845         mWindowManagerInternal.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed);
2846     }
2847 
2848     @BinderThread
2849     @GuardedBy("ImfLock.class")
reportStartInputLocked(IBinder startInputToken, @NonNull UserData userData)2850     private void reportStartInputLocked(IBinder startInputToken, @NonNull UserData userData) {
2851         final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
2852         if (targetWindow != null) {
2853             mWindowManagerInternal.updateInputMethodTargetWindow(targetWindow);
2854         }
2855         final var visibilityStateComputer = userData.mVisibilityStateComputer;
2856         visibilityStateComputer.setLastImeTargetWindow(targetWindow);
2857     }
2858 
2859     @GuardedBy("ImfLock.class")
updateImeWindowStatusLocked(boolean disableImeIcon, int displayId)2860     private void updateImeWindowStatusLocked(boolean disableImeIcon, int displayId) {
2861         final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
2862         if (disableImeIcon) {
2863             final var bindingController = getInputMethodBindingController(userId);
2864             updateSystemUiLocked(0 /* vis */, bindingController.getBackDisposition(), userId);
2865         } else {
2866             updateSystemUiLocked(userId);
2867         }
2868     }
2869 
2870     // Caution! This method is called in this class. Handle multi-user carefully
2871     @GuardedBy("ImfLock.class")
updateSystemUiLocked(@serIdInt int userId)2872     void updateSystemUiLocked(@UserIdInt int userId) {
2873         final var bindingController = getInputMethodBindingController(userId);
2874         updateSystemUiLocked(bindingController.getImeWindowVis(),
2875                 bindingController.getBackDisposition(), userId);
2876     }
2877 
2878     @GuardedBy("ImfLock.class")
updateSystemUiLocked(@meWindowVisibility int vis, @BackDispositionMode int backDisposition, @UserIdInt int userId)2879     private void updateSystemUiLocked(@ImeWindowVisibility int vis,
2880             @BackDispositionMode int backDisposition, @UserIdInt int userId) {
2881         // To minimize app compat risk, ignore background users' request for single-user mode.
2882         // TODO(b/357178609): generalize the logic and remove this special rule.
2883         if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
2884             return;
2885         }
2886         final var userData = getUserData(userId);
2887         final var bindingController = userData.mBindingController;
2888         final var curToken = bindingController.getCurToken();
2889         if (curToken == null) {
2890             return;
2891         }
2892         final int curTokenDisplayId = bindingController.getCurTokenDisplayId();
2893         ProtoLog.v(IMMS_DEBUG, "IME window vis: %s active: %s visible: %s displayId: %s", vis,
2894                 (vis & InputMethodService.IME_ACTIVE), (vis & InputMethodService.IME_VISIBLE),
2895                 curTokenDisplayId);
2896         final IBinder focusedWindowToken = userData.mImeBindingState.mFocusedWindow;
2897         final Boolean windowPerceptible = focusedWindowToken != null
2898                 ? mFocusedWindowPerceptible.get(focusedWindowToken) : null;
2899 
2900         // TODO: Move this clearing calling identity block to setImeWindowStatusLocked after making
2901         //  sure all updateSystemUi happens on system privilege.
2902         final long ident = Binder.clearCallingIdentity();
2903         try {
2904             if (windowPerceptible != null && !windowPerceptible) {
2905                 vis &= ~InputMethodService.IME_VISIBLE;
2906             }
2907             final var curId = bindingController.getCurId();
2908             // TODO(b/305849394): Make mMenuController multi-user aware.
2909             final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
2910                     ? mMenuControllerNew.isShowing()
2911                     : mMenuController.getSwitchingDialogLocked() != null;
2912             if (switcherMenuShowing
2913                     || !Objects.equals(curId, bindingController.getSelectedMethodId())) {
2914                 // When the IME switcher dialog is shown, or we are switching IMEs,
2915                 // the back button should be in the default state (as if the IME is not shown).
2916                 backDisposition = InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING;
2917             }
2918             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis, userId);
2919             if (mStatusBarManagerInternal != null) {
2920                 mStatusBarManagerInternal.setImeWindowStatus(curTokenDisplayId, vis,
2921                         backDisposition, needsToShowImeSwitcher);
2922             }
2923         } finally {
2924             Binder.restoreCallingIdentity(ident);
2925         }
2926     }
2927 
2928     @GuardedBy("ImfLock.class")
updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId)2929     void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
2930         updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
2931         if (!Flags.imeSwitcherRevamp()) {
2932             mMenuController.updateKeyboardFromSettingsLocked(userId);
2933         }
2934     }
2935 
2936     @GuardedBy("ImfLock.class")
updateInputMethodsFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId)2937     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
2938         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
2939         if (enabledMayChange) {
2940             final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
2941                     userId);
2942 
2943             List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
2944             for (int i = 0; i < enabled.size(); i++) {
2945                 // We allow the user to select "disabled until used" apps, so if they
2946                 // are enabling one of those here we now need to make it enabled.
2947                 InputMethodInfo imm = enabled.get(i);
2948                 ApplicationInfo ai = null;
2949                 try {
2950                     ai = userAwarePackageManager.getApplicationInfo(imm.getPackageName(),
2951                             PackageManager.ApplicationInfoFlags.of(
2952                                     PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS));
2953                 } catch (PackageManager.NameNotFoundException ignored) {
2954                 }
2955                 if (ai != null && ai.enabledSetting
2956                         == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
2957                     ProtoLog.v(IMMS_DEBUG, "Update state(%s): DISABLED_UNTIL_USED -> DEFAULT",
2958                             imm.getId());
2959                     userAwarePackageManager.setApplicationEnabledSetting(imm.getPackageName(),
2960                             PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
2961                             PackageManager.DONT_KILL_APP);
2962                 }
2963             }
2964         }
2965 
2966         final var userData = getUserData(userId);
2967         final var bindingController = userData.mBindingController;
2968         if (bindingController.getDeviceIdToShowIme() == DEVICE_ID_DEFAULT) {
2969             String ime = SecureSettingsWrapper.getString(
2970                     Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
2971             String defaultDeviceIme = SecureSettingsWrapper.getString(
2972                     Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, userId);
2973             if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
2974                 ProtoLog.v(IMMS_DEBUG,
2975                         "Current input method %s differs from the stored default device input "
2976                                 + "method for user %s - restoring %s",
2977                         ime, userId, defaultDeviceIme);
2978                 SecureSettingsWrapper.putString(
2979                         Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme, userId);
2980                 SecureSettingsWrapper.putString(
2981                         Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, userId);
2982             }
2983         }
2984 
2985         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
2986         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
2987         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
2988         // enabled.
2989         String id = settings.getSelectedInputMethod();
2990         // There is no input method selected, try to choose new applicable input method.
2991         if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked(userId)) {
2992             id = settings.getSelectedInputMethod();
2993         }
2994         if (!TextUtils.isEmpty(id)) {
2995             try {
2996                 setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeIndex(id), userId);
2997             } catch (IllegalArgumentException e) {
2998                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
2999                 resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED, userId);
3000             }
3001         } else {
3002             // There is no longer an input method set, so stop any current one.
3003             resetCurrentMethodAndClientLocked(UnbindReason.NO_IME, userId);
3004         }
3005 
3006         userData.mSwitchingController.resetCircularListLocked(mContext, settings);
3007         userData.mHardwareKeyboardShortcutController.update(settings);
3008         sendOnNavButtonFlagsChangedLocked(userData);
3009     }
3010 
3011     @GuardedBy("ImfLock.class")
notifyInputMethodSubtypeChangedLocked(@serIdInt int userId, @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype)3012     private void notifyInputMethodSubtypeChangedLocked(@UserIdInt int userId,
3013             @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
3014         final InputMethodSubtype normalizedSubtype =
3015                 subtype != null && subtype.isSuitableForPhysicalKeyboardLayoutMapping()
3016                         ? subtype : null;
3017         final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null
3018                 ? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null;
3019 
3020         final var userData = getUserData(userId);
3021 
3022         // A workaround for b/356879517. KeyboardLayoutManager has relied on an implementation
3023         // detail that IMMS triggers this callback only for the current IME user.
3024         // TODO(b/357663774): Figure out how to better handle this scenario.
3025         userData.mSubtypeForKeyboardLayoutMapping =
3026                 Pair.create(newSubtypeHandle, normalizedSubtype);
3027         if (userId != mCurrentImeUserId) {
3028             return;
3029         }
3030         mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
3031                 userId, newSubtypeHandle, normalizedSubtype);
3032     }
3033 
3034     @GuardedBy("ImfLock.class")
setInputMethodLocked(String id, int subtypeIndex, @UserIdInt int userId)3035     void setInputMethodLocked(String id, int subtypeIndex, @UserIdInt int userId) {
3036         setInputMethodLocked(id, subtypeIndex, DEVICE_ID_DEFAULT, userId);
3037     }
3038 
3039     @GuardedBy("ImfLock.class")
setInputMethodLocked(String id, int subtypeIndex, int deviceId, @UserIdInt int userId)3040     void setInputMethodLocked(String id, int subtypeIndex, int deviceId, @UserIdInt int userId) {
3041         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
3042         InputMethodInfo info = settings.getMethodMap().get(id);
3043         if (info == null) {
3044             throw getExceptionForUnknownImeId(id);
3045         }
3046 
3047         final var bindingController = getInputMethodBindingController(userId);
3048         // See if we need to notify a subtype change within the same IME.
3049         if (id.equals(bindingController.getSelectedMethodId())) {
3050             final int subtypeCount = info.getSubtypeCount();
3051             if (subtypeCount <= 0) {
3052                 notifyInputMethodSubtypeChangedLocked(userId, info, null);
3053                 return;
3054             }
3055             final InputMethodSubtype oldSubtype = bindingController.getCurrentSubtype();
3056             final InputMethodSubtype newSubtype;
3057             if (subtypeIndex >= 0 && subtypeIndex < subtypeCount) {
3058                 newSubtype = info.getSubtypeAt(subtypeIndex);
3059             } else {
3060                 // If subtype is null, try to find the most applicable one from
3061                 // getCurrentInputMethodSubtype.
3062                 subtypeIndex = NOT_A_SUBTYPE_INDEX;
3063                 // TODO(b/347083680): The method below has questionable behaviors.
3064                 newSubtype = bindingController.getCurrentInputMethodSubtype();
3065                 if (newSubtype != null) {
3066                     for (int i = 0; i < subtypeCount; ++i) {
3067                         if (Objects.equals(newSubtype, info.getSubtypeAt(i))) {
3068                             subtypeIndex = i;
3069                             break;
3070                         }
3071                     }
3072                 }
3073             }
3074             if (!Objects.equals(newSubtype, oldSubtype)) {
3075                 setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, true, userId);
3076                 IInputMethodInvoker curMethod = bindingController.getCurMethod();
3077                 if (curMethod != null) {
3078                     updateSystemUiLocked(bindingController.getImeWindowVis(),
3079                             bindingController.getBackDisposition(), userId);
3080                     curMethod.changeInputMethodSubtype(newSubtype);
3081                 }
3082             }
3083             return;
3084         }
3085 
3086         // Changing to a different IME.
3087         if (bindingController.getDeviceIdToShowIme() != DEVICE_ID_DEFAULT
3088                 && deviceId == DEVICE_ID_DEFAULT) {
3089             // This change should only be applicable to the default device but the current input
3090             // method is a custom one specific to a virtual device. So only update the settings
3091             // entry used to restore the default device input method once we want to show the IME
3092             // back on the default device.
3093             settings.putSelectedDefaultDeviceInputMethod(id);
3094             return;
3095         }
3096         IInputMethodInvoker curMethod = bindingController.getCurMethod();
3097         if (curMethod != null) {
3098             curMethod.removeStylusHandwritingWindow();
3099         }
3100         final long ident = Binder.clearCallingIdentity();
3101         try {
3102             setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, false, userId);
3103             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
3104             // because mCurMethodId is stored as a history in
3105             // setSelectedInputMethodAndSubtypeLocked().
3106             bindingController.setSelectedMethodId(id);
3107 
3108             if (mActivityManagerInternal.isSystemReady()) {
3109                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
3110                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
3111                 intent.putExtra("input_method_id", id);
3112                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
3113             }
3114             bindingController.unbindCurrentMethod();
3115             unbindCurrentClientLocked(UnbindReason.SWITCH_IME, userId);
3116         } finally {
3117             Binder.restoreCallingIdentity(ident);
3118         }
3119     }
3120 
3121     @GuardedBy("ImfLock.class")
sendResultReceiverFailureLocked(@ullable ResultReceiver resultReceiver, @UserIdInt int userId)3122     private void sendResultReceiverFailureLocked(@Nullable ResultReceiver resultReceiver,
3123             @UserIdInt int userId) {
3124         if (resultReceiver == null) {
3125             return;
3126         }
3127         final var userData = getUserData(userId);
3128         final var visibilityStateComputer = userData.mVisibilityStateComputer;
3129         final boolean isInputShown = visibilityStateComputer.isInputShown();
3130         resultReceiver.send(isInputShown
3131                 ? InputMethodManager.RESULT_UNCHANGED_SHOWN
3132                 : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
3133     }
3134 
3135     @Override
showSoftInput(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, int lastClickToolType, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async)3136     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
3137             @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
3138             int lastClickToolType, ResultReceiver resultReceiver,
3139             @SoftInputShowHideReason int reason, boolean async) {
3140         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
3141         ImeTracing.getInstance().triggerManagerServiceDump(
3142                 "InputMethodManagerService#showSoftInput", mDumper);
3143         synchronized (ImfLock.class) {
3144             final int uid = Binder.getCallingUid();
3145             final int callingUserId = UserHandle.getUserId(uid);
3146             final int userId = resolveImeUserIdLocked(callingUserId, client);
3147             final boolean result = showSoftInputLocked(client, windowToken, statsToken, flags,
3148                     lastClickToolType, resultReceiver, reason, uid, userId);
3149             // When ZeroJankProxy is enabled, the app has already received "true" as the return
3150             // value, and expect "resultReceiver" to be notified later. See b/327751155.
3151             if (!result && Flags.useZeroJankProxy()) {
3152                 sendResultReceiverFailureLocked(resultReceiver, userId);
3153             }
3154             return result;  // ignored when ZeroJankProxy is enabled.
3155         }
3156     }
3157 
3158     @GuardedBy("ImfLock.class")
showSoftInputLocked(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, int lastClickToolType, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, int uid, @UserIdInt int userId)3159     private boolean showSoftInputLocked(IInputMethodClient client, IBinder windowToken,
3160             @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
3161             int lastClickToolType, ResultReceiver resultReceiver,
3162             @SoftInputShowHideReason int reason, int uid, @UserIdInt int userId) {
3163         if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken,
3164                 userId)) {
3165             ImeTracker.forLogging().onFailed(
3166                     statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
3167             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3168             return false;
3169         }
3170         final var userData = getUserData(userId);
3171         final long ident = Binder.clearCallingIdentity();
3172         try {
3173             ProtoLog.v(IMMS_DEBUG, "Client requesting input be shown");
3174             if (Flags.refactorInsetsController()) {
3175                 final var visibilityStateComputer = userData.mVisibilityStateComputer;
3176                 boolean wasVisible = visibilityStateComputer.isInputShown();
3177                 if (setImeVisibilityOnFocusedWindowClient(false, userData, statsToken)) {
3178                     if (resultReceiver != null) {
3179                         resultReceiver.send(
3180                                 wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
3181                                         : InputMethodManager.RESULT_SHOWN, null);
3182                     }
3183                     return true;
3184                 }
3185                 return false;
3186             } else {
3187                 return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType,
3188                         resultReceiver, reason, userId);
3189             }
3190         } finally {
3191             Binder.restoreCallingIdentity(ident);
3192             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3193         }
3194     }
3195 
3196     // TODO(b/353463205) check callers to see if we can make statsToken @NonNull
showCurrentInputInternal(IBinder windowToken, @NonNull ImeTracker.Token statsToken)3197     boolean showCurrentInputInternal(IBinder windowToken, @NonNull ImeTracker.Token statsToken) {
3198         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showCurrentInputInternal");
3199         ImeTracing.getInstance().triggerManagerServiceDump(
3200                 "InputMethodManagerService#showSoftInput", mDumper);
3201         synchronized (ImfLock.class) {
3202             final int userId = resolveImeUserIdFromWindowLocked(windowToken);
3203             final long ident = Binder.clearCallingIdentity();
3204             try {
3205                 ProtoLog.v(IMMS_DEBUG, "Client requesting input be shown");
3206                 return showCurrentInputLocked(windowToken, statsToken, 0 /* flags */,
3207                         0 /* lastClickTooType */, null /* resultReceiver */,
3208                         SoftInputShowHideReason.SHOW_SOFT_INPUT, userId);
3209             } finally {
3210                 Binder.restoreCallingIdentity(ident);
3211                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3212             }
3213         }
3214     }
3215 
3216     // TODO(b/353463205) check callers to see if we can make statsToken @NonNull
hideCurrentInputInternal(IBinder windowToken, @NonNull ImeTracker.Token statsToken)3217     boolean hideCurrentInputInternal(IBinder windowToken, @NonNull ImeTracker.Token statsToken) {
3218         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideCurrentInputInternal");
3219         ImeTracing.getInstance().triggerManagerServiceDump(
3220                 "InputMethodManagerService#hideSoftInput", mDumper);
3221         synchronized (ImfLock.class) {
3222             final int userId = resolveImeUserIdFromWindowLocked(windowToken);
3223             final long ident = Binder.clearCallingIdentity();
3224             try {
3225                 ProtoLog.v(IMMS_DEBUG, "Client requesting input be hidden");
3226                 return hideCurrentInputLocked(windowToken, statsToken, 0 /* flags */,
3227                         null /* resultReceiver */, SoftInputShowHideReason.HIDE_SOFT_INPUT,
3228                         userId);
3229             } finally {
3230                 Binder.restoreCallingIdentity(ident);
3231                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3232             }
3233         }
3234     }
3235 
3236     @BinderThread
3237     @Override
startStylusHandwriting(IInputMethodClient client)3238     public void startStylusHandwriting(IInputMethodClient client) {
3239         startStylusHandwriting(client, false /* usesDelegation */);
3240     }
3241 
3242     @BinderThread
3243     @Override
startConnectionlessStylusHandwriting(IInputMethodClient client, int userId, @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName, @Nullable String delegatorPackageName, @NonNull IConnectionlessHandwritingCallback callback)3244     public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
3245             @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
3246             @Nullable String delegatorPackageName,
3247             @NonNull IConnectionlessHandwritingCallback callback) {
3248         synchronized (ImfLock.class) {
3249             final var bindingController = getInputMethodBindingController(userId);
3250             if (!bindingController.supportsConnectionlessStylusHandwriting()) {
3251                 Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME.");
3252                 try {
3253                     callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
3254                 } catch (RemoteException e) {
3255                     Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED", e);
3256                     e.rethrowAsRuntimeException();
3257                 }
3258                 return;
3259             }
3260         }
3261 
3262         IConnectionlessHandwritingCallback immsCallback = callback;
3263         boolean isForDelegation = delegatePackageName != null && delegatorPackageName != null;
3264         if (isForDelegation) {
3265             synchronized (ImfLock.class) {
3266                 if (!mClientController.verifyClientAndPackageMatch(client, delegatorPackageName)) {
3267                     Slog.w(TAG, "startConnectionlessStylusHandwriting() fail");
3268                     try {
3269                         callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
3270                     } catch (RemoteException e) {
3271                         Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e);
3272                         e.rethrowAsRuntimeException();
3273                     }
3274                     throw new IllegalArgumentException("Delegator doesn't match UID");
3275                 }
3276             }
3277             immsCallback = new IConnectionlessHandwritingCallback.Stub() {
3278                 @Override
3279                 public void onResult(CharSequence text) throws RemoteException {
3280                     synchronized (ImfLock.class) {
3281                         mHwController.prepareStylusHandwritingDelegation(
3282                                 userId, delegatePackageName, delegatorPackageName,
3283                                 /* connectionless= */ true);
3284                     }
3285                     callback.onResult(text);
3286                 }
3287 
3288                 @Override
3289                 public void onError(int errorCode) throws RemoteException {
3290                     callback.onError(errorCode);
3291                 }
3292             };
3293         }
3294 
3295         if (!startStylusHandwriting(
3296                 client, false, immsCallback, cursorAnchorInfo, isForDelegation)) {
3297             try {
3298                 callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
3299             } catch (RemoteException e) {
3300                 Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e);
3301                 e.rethrowAsRuntimeException();
3302             }
3303         }
3304     }
3305 
startStylusHandwriting(IInputMethodClient client, boolean acceptingDelegation)3306     private void startStylusHandwriting(IInputMethodClient client, boolean acceptingDelegation) {
3307         startStylusHandwriting(client, acceptingDelegation, null, null, false);
3308     }
3309 
startStylusHandwriting(IInputMethodClient client, boolean acceptingDelegation, IConnectionlessHandwritingCallback connectionlessCallback, CursorAnchorInfo cursorAnchorInfo, boolean isConnectionlessForDelegation)3310     private boolean startStylusHandwriting(IInputMethodClient client, boolean acceptingDelegation,
3311             IConnectionlessHandwritingCallback connectionlessCallback,
3312             CursorAnchorInfo cursorAnchorInfo, boolean isConnectionlessForDelegation) {
3313         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting");
3314         try {
3315             ImeTracing.getInstance().triggerManagerServiceDump(
3316                     "InputMethodManagerService#startStylusHandwriting", mDumper);
3317             final int uid = Binder.getCallingUid();
3318             final int callingUserId = UserHandle.getUserId(uid);
3319             synchronized (ImfLock.class) {
3320                 final int userId = resolveImeUserIdLocked(callingUserId);
3321                 if (!acceptingDelegation) {
3322                     mHwController.clearPendingHandwritingDelegation();
3323                 }
3324                 if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
3325                         null /* statsToken */, userId)) {
3326                     return false;
3327                 }
3328                 if (!hasSupportedStylusLocked()) {
3329                     Slog.w(TAG, "No supported Stylus hardware found on device. Ignoring"
3330                             + " startStylusHandwriting()");
3331                     return false;
3332                 }
3333                 final long ident = Binder.clearCallingIdentity();
3334                 try {
3335                     final var bindingController = getInputMethodBindingController(userId);
3336                     if (!bindingController.supportsStylusHandwriting()) {
3337                         Slog.w(TAG,
3338                                 "Stylus HW unsupported by IME. Ignoring startStylusHandwriting()");
3339                         return false;
3340                     }
3341 
3342                     final OptionalInt requestId = mHwController.getCurrentRequestId();
3343                     if (!requestId.isPresent()) {
3344                         Slog.e(TAG, "Stylus handwriting was not initialized.");
3345                         return false;
3346                     }
3347                     if (!mHwController.isStylusGestureOngoing()) {
3348                         Slog.e(TAG,
3349                                 "There is no ongoing stylus gesture to start stylus handwriting.");
3350                         return false;
3351                     }
3352                     if (mHwController.hasOngoingStylusHandwritingSession()) {
3353                         // prevent duplicate calls to startStylusHandwriting().
3354                         Slog.e(TAG,
3355                                 "Stylus handwriting session is already ongoing."
3356                                         + " Ignoring startStylusHandwriting().");
3357                         return false;
3358                     }
3359                     ProtoLog.v(IMMS_DEBUG, "Client requesting Stylus Handwriting to be started");
3360                     final IInputMethodInvoker curMethod = bindingController.getCurMethod();
3361                     if (curMethod != null) {
3362                         curMethod.canStartStylusHandwriting(requestId.getAsInt(),
3363                                 connectionlessCallback, cursorAnchorInfo,
3364                                 isConnectionlessForDelegation);
3365                         return true;
3366                     }
3367                 } finally {
3368                     Binder.restoreCallingIdentity(ident);
3369                 }
3370             }
3371         } finally {
3372             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3373         }
3374         return false;
3375     }
3376 
3377     @Override
prepareStylusHandwritingDelegation( @onNull IInputMethodClient client, @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName)3378     public void prepareStylusHandwritingDelegation(
3379             @NonNull IInputMethodClient client,
3380             @UserIdInt int userId,
3381             @NonNull String delegatePackageName,
3382             @NonNull String delegatorPackageName) {
3383         if (!isStylusHandwritingEnabled(mContext, userId)) {
3384             Slog.w(TAG, "Can not prepare stylus handwriting delegation. Stylus handwriting"
3385                     + " pref is disabled for user: " + userId);
3386             return;
3387         }
3388         synchronized (ImfLock.class) {
3389             if (!mClientController.verifyClientAndPackageMatch(client,
3390                     delegatorPackageName)) {
3391                 Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
3392                 throw new IllegalArgumentException("Delegator doesn't match Uid");
3393             }
3394         }
3395         schedulePrepareStylusHandwritingDelegation(
3396                 userId, delegatePackageName, delegatorPackageName);
3397     }
3398 
3399     @Override
acceptStylusHandwritingDelegationAsync( @onNull IInputMethodClient client, @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName, @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)3400     public void acceptStylusHandwritingDelegationAsync(
3401             @NonNull IInputMethodClient client,
3402             @UserIdInt int userId,
3403             @NonNull String delegatePackageName,
3404             @NonNull String delegatorPackageName,
3405             @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) {
3406         boolean result = acceptStylusHandwritingDelegation(
3407                 client, userId, delegatePackageName, delegatorPackageName, flags);
3408         try {
3409             callback.onResult(result);
3410         } catch (RemoteException e) {
3411             Slog.e(TAG, "Failed to report result=" + result, e);
3412             e.rethrowAsRuntimeException();
3413         }
3414     }
3415 
3416     @Override
acceptStylusHandwritingDelegation( @onNull IInputMethodClient client, @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName, @InputMethodManager.HandwritingDelegateFlags int flags)3417     public boolean acceptStylusHandwritingDelegation(
3418             @NonNull IInputMethodClient client,
3419             @UserIdInt int userId,
3420             @NonNull String delegatePackageName,
3421             @NonNull String delegatorPackageName,
3422             @InputMethodManager.HandwritingDelegateFlags int flags) {
3423         if (!isStylusHandwritingEnabled(mContext, userId)) {
3424             Slog.w(TAG, "Can not accept stylus handwriting delegation. Stylus handwriting"
3425                     + " pref is disabled for user: " + userId);
3426             return false;
3427         }
3428         if (!verifyDelegator(client, delegatePackageName, delegatorPackageName, flags)) {
3429             return false;
3430         }
3431         synchronized (ImfLock.class) {
3432             final var bindingController = getInputMethodBindingController(userId);
3433             if (mHwController.isDelegationUsingConnectionlessFlow()) {
3434                 final IInputMethodInvoker curMethod = bindingController.getCurMethod();
3435                 if (curMethod == null) {
3436                     return false;
3437                 }
3438                 curMethod.commitHandwritingDelegationTextIfAvailable();
3439                 mHwController.clearPendingHandwritingDelegation();
3440             } else {
3441                 startStylusHandwriting(client, true /* acceptingDelegation */);
3442             }
3443         }
3444         return true;
3445     }
3446 
verifyDelegator( @onNull IInputMethodClient client, @NonNull String delegatePackageName, @NonNull String delegatorPackageName, @InputMethodManager.HandwritingDelegateFlags int flags)3447     private boolean verifyDelegator(
3448             @NonNull IInputMethodClient client,
3449             @NonNull String delegatePackageName,
3450             @NonNull String delegatorPackageName,
3451             @InputMethodManager.HandwritingDelegateFlags int flags) {
3452         synchronized (ImfLock.class) {
3453             if (!mClientController.verifyClientAndPackageMatch(client, delegatePackageName)) {
3454                 Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
3455                         + " startStylusHandwriting");
3456                 return false;
3457             }
3458             boolean homeDelegatorAllowed =
3459                     (flags & InputMethodManager.HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED)
3460                             != 0;
3461             if (!delegatorPackageName.equals(mHwController.getDelegatorPackageName())
3462                     && !(homeDelegatorAllowed
3463                             && mHwController.isDelegatorFromDefaultHomePackage())) {
3464                 Slog.w(TAG,
3465                         "Delegator package does not match. Ignoring startStylusHandwriting");
3466                 return false;
3467             }
3468             if (!delegatePackageName.equals(mHwController.getDelegatePackageName())) {
3469                 Slog.w(TAG,
3470                         "Delegate package does not match. Ignoring startStylusHandwriting");
3471                 return false;
3472             }
3473         }
3474         return true;
3475     }
3476 
3477     @BinderThread
3478     @Override
reportPerceptibleAsync(@onNull IBinder windowToken, boolean perceptible)3479     public void reportPerceptibleAsync(@NonNull IBinder windowToken, boolean perceptible) {
3480         Binder.withCleanCallingIdentity(() -> {
3481             Objects.requireNonNull(windowToken, "windowToken must not be null");
3482             synchronized (ImfLock.class) {
3483                 Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken);
3484                 final int userId = resolveImeUserIdFromWindowLocked(windowToken);
3485                 final var userData = getUserData(userId);
3486                 if (userData.mImeBindingState.mFocusedWindow != windowToken
3487                         || (windowPerceptible != null && windowPerceptible == perceptible)) {
3488                     return;
3489                 }
3490                 mFocusedWindowPerceptible.put(windowToken, perceptible);
3491                 updateSystemUiLocked(userId);
3492             }
3493         });
3494     }
3495 
3496     @GuardedBy("ImfLock.class")
showCurrentInputLocked(IBinder windowToken, @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, @UserIdInt int userId)3497     private boolean showCurrentInputLocked(IBinder windowToken,
3498             @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
3499             @UserIdInt int userId) {
3500         final var statsToken = createStatsTokenForFocusedClient(true /* show */, reason, userId);
3501         return showCurrentInputLocked(windowToken, statsToken, flags,
3502                 MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason, userId);
3503     }
3504 
3505     @GuardedBy("ImfLock.class")
showCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, @MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, @UserIdInt int userId)3506     boolean showCurrentInputLocked(IBinder windowToken,
3507             @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
3508             @MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver,
3509             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
3510         final var userData = getUserData(userId);
3511         final var visibilityStateComputer = userData.mVisibilityStateComputer;
3512         if (!visibilityStateComputer.onImeShowFlags(statsToken, flags)) {
3513             return false;
3514         }
3515 
3516         if (!mSystemReady) {
3517             ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
3518             return false;
3519         }
3520         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
3521 
3522         visibilityStateComputer.requestImeVisibility(windowToken, true);
3523 
3524         // Ensure binding the connection when IME is going to show.
3525         final var bindingController = userData.mBindingController;
3526         bindingController.setCurrentMethodVisible();
3527         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
3528         ImeTracker.forLogging().onCancelled(userData.mCurStatsToken,
3529                 ImeTracker.PHASE_SERVER_WAIT_IME);
3530         final boolean readyToDispatchToIme;
3531         if (Flags.deferShowSoftInputUntilSessionCreation()) {
3532             readyToDispatchToIme =
3533                     curMethod != null && userData.mCurClient != null
3534                             && userData.mCurClient.mCurSession != null;
3535         } else {
3536             readyToDispatchToIme = curMethod != null;
3537         }
3538         if (readyToDispatchToIme) {
3539             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
3540             userData.mCurStatsToken = null;
3541 
3542             if (Flags.useHandwritingListenerForTooltype()) {
3543                 maybeReportToolType(userId);
3544             } else if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
3545                 onUpdateEditorToolTypeLocked(lastClickToolType, userId);
3546             }
3547             mVisibilityApplier.performShowIme(windowToken, statsToken,
3548                     visibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
3549                     resultReceiver, reason, userId);
3550             visibilityStateComputer.setInputShown(true);
3551             return true;
3552         } else {
3553             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
3554             userData.mCurStatsToken = statsToken;
3555         }
3556         return false;
3557     }
3558 
3559     @GuardedBy("ImfLock.class")
maybeReportToolType(@serIdInt int userId)3560     private void maybeReportToolType(@UserIdInt int userId) {
3561         // TODO(b/356638981): This needs to be compatible with visible background users.
3562         int lastDeviceId = mInputManagerInternal.getLastUsedInputDeviceId();
3563         final InputManager im = mContext.getSystemService(InputManager.class);
3564         if (im == null) {
3565             return;
3566         }
3567         InputDevice device = im.getInputDevice(lastDeviceId);
3568         if (device == null) {
3569             return;
3570         }
3571         int toolType;
3572         if (isStylusDevice(device)) {
3573             toolType = MotionEvent.TOOL_TYPE_STYLUS;
3574         } else if (isFingerDevice(device)) {
3575             toolType = MotionEvent.TOOL_TYPE_FINGER;
3576         } else {
3577             // other toolTypes are irrelevant and reported as unknown.
3578             toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
3579         }
3580         onUpdateEditorToolTypeLocked(toolType, userId);
3581     }
3582 
3583     @Override
hideSoftInput(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async)3584     public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
3585             @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
3586             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async) {
3587         ImeTracing.getInstance().triggerManagerServiceDump(
3588                 "InputMethodManagerService#hideSoftInput", mDumper);
3589         synchronized (ImfLock.class) {
3590             final int uid = Binder.getCallingUid();
3591             final int callingUserId = UserHandle.getUserId(uid);
3592             final int userId = resolveImeUserIdLocked(callingUserId, client);
3593             final boolean result = hideSoftInputLocked(client, windowToken, statsToken, flags,
3594                     resultReceiver, reason, uid, userId);
3595             // When ZeroJankProxy is enabled, the app has already received "true" as the return
3596             // value, and expect "resultReceiver" to be notified later. See b/327751155.
3597             if (!result && Flags.useZeroJankProxy()) {
3598                 sendResultReceiverFailureLocked(resultReceiver, userId);
3599             }
3600             return result;  // ignored when ZeroJankProxy is enabled.
3601         }
3602     }
3603 
3604     @GuardedBy("ImfLock.class")
hideSoftInputLocked(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, int uid, @UserIdInt int userId)3605     private boolean hideSoftInputLocked(IInputMethodClient client, IBinder windowToken,
3606             @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
3607             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason,
3608             int uid, @UserIdInt int userId) {
3609         final var userData = getUserData(userId);
3610         final var visibilityStateComputer = userData.mVisibilityStateComputer;
3611         if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) {
3612             if (visibilityStateComputer.isInputShown()) {
3613                 ImeTracker.forLogging().onFailed(
3614                         statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
3615             } else {
3616                 ImeTracker.forLogging().onCancelled(statsToken,
3617                         ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
3618             }
3619             return false;
3620         }
3621         final long ident = Binder.clearCallingIdentity();
3622         try {
3623             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
3624             ProtoLog.v(IMMS_DEBUG, "Client requesting input be hidden");
3625             if (Flags.refactorInsetsController()) {
3626                 boolean wasVisible = visibilityStateComputer.isInputShown();
3627                 // TODO add windowToken to interface
3628                 if (setImeVisibilityOnFocusedWindowClient(false, userData, statsToken)) {
3629                     if (resultReceiver != null) {
3630                         resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN
3631                                 : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
3632                     }
3633                     return true;
3634                 }
3635                 return false;
3636             } else {
3637                 return InputMethodManagerService.this.hideCurrentInputLocked(
3638                         windowToken, statsToken, flags, resultReceiver, reason, userId);
3639             }
3640         } finally {
3641             Binder.restoreCallingIdentity(ident);
3642             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3643         }
3644     }
3645 
3646     @Override
3647     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
hideSoftInputFromServerForTest()3648     public void hideSoftInputFromServerForTest() {
3649         final int callingUserId = UserHandle.getCallingUserId();
3650         synchronized (ImfLock.class) {
3651             final int userId = resolveImeUserIdLocked(callingUserId);
3652             final var userData = getUserData(userId);
3653             hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
3654                     SoftInputShowHideReason.HIDE_SOFT_INPUT, userId);
3655         }
3656     }
3657 
3658     @GuardedBy("ImfLock.class")
hideCurrentInputLocked(IBinder windowToken, @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, @UserIdInt int userId)3659     private boolean hideCurrentInputLocked(IBinder windowToken,
3660             @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
3661             @UserIdInt int userId) {
3662         final var statsToken = createStatsTokenForFocusedClient(false /* show */, reason, userId);
3663         return hideCurrentInputLocked(windowToken, statsToken, flags, null /* resultReceiver */,
3664                 reason, userId);
3665     }
3666 
3667     @GuardedBy("ImfLock.class")
hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, @UserIdInt int userId)3668     boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
3669             @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver,
3670             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
3671         final var userData = getUserData(userId);
3672         final var bindingController = userData.mBindingController;
3673         final var visibilityStateComputer = userData.mVisibilityStateComputer;
3674         if (!visibilityStateComputer.canHideIme(statsToken, flags)) {
3675             return false;
3676         }
3677 
3678         // There is a chance that IMM#hideSoftInput() is called in a transient state where
3679         // IMMS#InputShown is already updated to be true whereas the user's ImeWindowVis is still
3680         // waiting to be updated with the new value sent from IME process.  Even in such a transient
3681         // state historically we have accepted an incoming call of IMM#hideSoftInput() from the
3682         // application process as a valid request, and have even promised such a behavior with CTS
3683         // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
3684         // IMMS#InputShown indicates that the software keyboard is shown.
3685         // TODO(b/246309664): Clean up IMMS#mImeWindowVis
3686         IInputMethodInvoker curMethod = bindingController.getCurMethod();
3687         final boolean shouldHideSoftInput = curMethod != null
3688                 && (visibilityStateComputer.isInputShown()
3689                 || (bindingController.getImeWindowVis() & InputMethodService.IME_ACTIVE) != 0);
3690 
3691         visibilityStateComputer.requestImeVisibility(windowToken, false);
3692         if (shouldHideSoftInput) {
3693             // The IME will report its visible state again after the following message finally
3694             // delivered to the IME process as an IPC.  Hence the inconsistency between
3695             // IMMS#mInputShown and the user's ImeWindowVis should be resolved spontaneously in
3696             // the final state.
3697             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
3698             mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason,
3699                     userId);
3700         } else {
3701             ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
3702         }
3703         bindingController.setCurrentMethodNotVisible();
3704         visibilityStateComputer.clearImeShowFlags();
3705         // Cancel existing statsToken for show IME as we got a hide request.
3706         ImeTracker.forLogging().onCancelled(userData.mCurStatsToken,
3707                 ImeTracker.PHASE_SERVER_WAIT_IME);
3708         userData.mCurStatsToken = null;
3709         return shouldHideSoftInput;
3710     }
3711 
isImeClientFocused(IBinder windowToken, ClientState cs)3712     private boolean isImeClientFocused(IBinder windowToken, ClientState cs) {
3713         final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
3714                 windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
3715         return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS;
3716     }
3717 
3718     //TODO(b/293640003): merge with startInputOrWindowGainedFocus once Flags.useZeroJankProxy()
3719     // is enabled.
3720     @Override
startInputOrWindowGainedFocusAsync( @tartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection, IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @UserIdInt int userId, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible, int startInputSeq, boolean useAsyncShowHideMethod)3721     public void startInputOrWindowGainedFocusAsync(
3722             @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
3723             @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
3724             int windowFlags, @Nullable EditorInfo editorInfo,
3725             IRemoteInputConnection inputConnection,
3726             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
3727             int unverifiedTargetSdkVersion, @UserIdInt int userId,
3728             @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible,
3729             int startInputSeq, boolean useAsyncShowHideMethod) {
3730         // implemented by ZeroJankProxy
3731     }
3732 
3733     @NonNull
3734     @Override
startInputOrWindowGainedFocus( @tartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection, IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @UserIdInt int userId, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible)3735     public InputBindResult startInputOrWindowGainedFocus(
3736             @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
3737             @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
3738             int windowFlags, @Nullable EditorInfo editorInfo,
3739             IRemoteInputConnection inputConnection,
3740             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
3741             int unverifiedTargetSdkVersion, @UserIdInt int userId,
3742             @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) {
3743         if (UserHandle.getCallingUserId() != userId) {
3744             mContext.enforceCallingOrSelfPermission(
3745                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
3746 
3747             if (editorInfo == null || editorInfo.targetInputMethodUser == null
3748                     || editorInfo.targetInputMethodUser.getIdentifier() != userId) {
3749                 throw new InvalidParameterException("EditorInfo#targetInputMethodUser must also be "
3750                         + "specified for cross-user startInputOrWindowGainedFocus()");
3751             }
3752         }
3753         if (windowToken == null) {
3754             Slog.e(TAG, "windowToken cannot be null.");
3755             return InputBindResult.NULL;
3756         }
3757         // The user represented by userId, must be running.
3758         if (!mUserManagerInternal.isUserRunning(userId)) {
3759             // There is a chance that we hit here because of race condition. Let's just
3760             // return an error code instead of crashing the caller process, which at
3761             // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
3762             // important process.
3763             Slog.w(TAG, "User #" + userId + " is not running.");
3764             return InputBindResult.INVALID_USER;
3765         }
3766         final var userData = getUserData(userId);
3767         try {
3768             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
3769                     "IMMS.startInputOrWindowGainedFocus");
3770             ImeTracing.getInstance().triggerManagerServiceDump(
3771                     "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
3772             final InputBindResult result;
3773             synchronized (ImfLock.class) {
3774                 final var bindingController = userData.mBindingController;
3775                 // If the system is not yet ready, we shouldn't be running third party code.
3776                 if (!mSystemReady) {
3777                     return new InputBindResult(
3778                             InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
3779                             null /* method */, null /* accessibilitySessions */, null /* channel */,
3780                             bindingController.getSelectedMethodId(),
3781                             bindingController.getSequenceNumber(),
3782                             false /* isInputMethodSuppressingSpellChecker */);
3783                 }
3784                 final ClientState cs = mClientController.getClient(client.asBinder());
3785                 if (cs == null) {
3786                     throw new IllegalArgumentException("Unknown client " + client.asBinder());
3787                 }
3788                 final long ident = Binder.clearCallingIdentity();
3789                 try {
3790                     // Verify if IMMS is in the process of switching user.
3791                     if (!mConcurrentMultiUserModeEnabled && mUserSwitchHandlerTask != null) {
3792                         // There is already an on-going pending user switch task.
3793                         final int nextUserId = mUserSwitchHandlerTask.mToUserId;
3794                         if (userId == nextUserId) {
3795                             scheduleSwitchUserTaskLocked(userId, cs.mClient);
3796                             return InputBindResult.USER_SWITCHING;
3797                         }
3798                         final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
3799                                 mCurrentImeUserId, false /* enabledOnly */);
3800                         for (int profileId : profileIdsWithDisabled) {
3801                             if (profileId == userId) {
3802                                 scheduleSwitchUserTaskLocked(userId, cs.mClient);
3803                                 return InputBindResult.USER_SWITCHING;
3804                             }
3805                         }
3806                         return InputBindResult.INVALID_USER;
3807                     }
3808 
3809                     // Ensure that caller's focused window and display parameters are allowd to
3810                     // display input method.
3811                     final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
3812                             windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId);
3813                     switch (imeClientFocus) {
3814                         case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
3815                             Slog.e(TAG,
3816                                     "startInputOrWindowGainedFocusInternal: display ID mismatch.");
3817                             return InputBindResult.DISPLAY_ID_MISMATCH;
3818                         case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
3819                             // Check with the window manager to make sure this client actually
3820                             // has a window with focus.  If not, reject.  This is thread safe
3821                             // because if the focus changes some time before or after, the
3822                             // next client receiving focus that has any interest in input will
3823                             // be calling through here after that change happens.
3824                             ProtoLog.v(IMMS_DEBUG,
3825                                     "Focus gain on non-focused client %s (uid=%s pid=%s)",
3826                                     cs.mClient, cs.mUid, cs.mPid);
3827                             return InputBindResult.NOT_IME_TARGET_WINDOW;
3828                         case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
3829                             return InputBindResult.INVALID_DISPLAY_ID;
3830                     }
3831 
3832                     // In case mShowForced flag affects the next client to keep IME visible, when
3833                     // the current client is leaving due to the next focused client, we clear
3834                     // mShowForced flag when the next client's targetSdkVersion is T or higher.
3835                     final boolean shouldClearFlag =
3836                             mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
3837                     final var visibilityStateComputer = userData.mVisibilityStateComputer;
3838                     final boolean showForced = visibilityStateComputer.mShowForced;
3839                     if (userData.mImeBindingState.mFocusedWindow != windowToken
3840                             && showForced && shouldClearFlag) {
3841                         visibilityStateComputer.mShowForced = false;
3842                     }
3843 
3844                     // Verify if caller is a background user.
3845                     if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
3846                         if (ArrayUtils.contains(
3847                                 mUserManagerInternal.getProfileIds(mCurrentImeUserId, false),
3848                                 userId)) {
3849                             // cross-profile access is always allowed here to allow
3850                             // profile-switching.
3851                             scheduleSwitchUserTaskLocked(userId, cs.mClient);
3852                             return InputBindResult.USER_SWITCHING;
3853                         }
3854                         Slog.w(TAG, "A background user is requesting window. Hiding IME.");
3855                         Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
3856                                 + " a background user, use EditorInfo.targetInputMethodUser with"
3857                                 + " INTERACT_ACROSS_USERS_FULL permission.");
3858                         hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
3859                                 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER, userId);
3860                         return InputBindResult.INVALID_USER;
3861                     }
3862 
3863                     if (editorInfo != null && !InputMethodUtils.checkIfPackageBelongsToUid(
3864                             mPackageManagerInternal, cs.mUid, editorInfo.packageName)) {
3865                         Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
3866                                 + " uid=" + cs.mUid + " package=" + editorInfo.packageName);
3867                         return InputBindResult.INVALID_PACKAGE_NAME;
3868                     }
3869 
3870                     result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
3871                             client, windowToken, startInputFlags, softInputMode, windowFlags,
3872                             editorInfo, inputConnection, remoteAccessibilityInputConnection,
3873                             unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs,
3874                             imeRequestedVisible);
3875                 } finally {
3876                     Binder.restoreCallingIdentity(ident);
3877                 }
3878             }
3879             if (result == null) {
3880                 // This must never happen, but just in case.
3881                 Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
3882                         + InputMethodDebug.startInputReasonToString(startInputReason)
3883                         + " windowFlags=#" + Integer.toHexString(windowFlags)
3884                         + " editorInfo=" + editorInfo);
3885                 return InputBindResult.NULL;
3886             }
3887 
3888             return result;
3889         } finally {
3890             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
3891         }
3892     }
3893 
3894     @GuardedBy("ImfLock.class")
3895     @NonNull
startInputOrWindowGainedFocusInternalLocked( @tartInputReason int startInputReason, IInputMethodClient client, @NonNull IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo, IRemoteInputConnection inputContext, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @NonNull InputMethodBindingController bindingController, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs, boolean imeRequestedVisible)3896     private InputBindResult startInputOrWindowGainedFocusInternalLocked(
3897             @StartInputReason int startInputReason, IInputMethodClient client,
3898             @NonNull IBinder windowToken, @StartInputFlags int startInputFlags,
3899             @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo,
3900             IRemoteInputConnection inputContext,
3901             @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
3902             int unverifiedTargetSdkVersion, @NonNull InputMethodBindingController bindingController,
3903             @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs,
3904             boolean imeRequestedVisible) {
3905         ProtoLog.v(IMMS_DEBUG, "startInputOrWindowGainedFocusInternalLocked: reason=%s"
3906                     + " client=%s"
3907                     + " inputContext=%s"
3908                     + " editorInfo=%s"
3909                     + " startInputFlags=%s"
3910                     + " softInputMode=%s"
3911                     + " windowFlags=#%s"
3912                     + " unverifiedTargetSdkVersion=%s"
3913                     + " bindingController=%s"
3914                     + " imeDispatcher=%s"
3915                     + " cs=%s"
3916                     + " imeRequestedVisible=%s",
3917                 InputMethodDebug.startInputReasonToString(startInputReason), client.asBinder(),
3918                 inputContext, editorInfo, InputMethodDebug.startInputFlagsToString(startInputFlags),
3919                 InputMethodDebug.softInputModeToString(softInputMode),
3920                 Integer.toHexString(windowFlags), unverifiedTargetSdkVersion, bindingController,
3921                 imeDispatcher, cs, imeRequestedVisible);
3922 
3923         final int userId = bindingController.getUserId();
3924         final var userData = getUserData(userId);
3925         final boolean sameWindowFocused = userData.mImeBindingState.mFocusedWindow == windowToken;
3926         final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
3927         final boolean startInputByWinGainedFocus =
3928                 (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
3929         final int toolType = editorInfo != null
3930                 ? editorInfo.getInitialToolType() : MotionEvent.TOOL_TYPE_UNKNOWN;
3931 
3932         // Init the focused window state (e.g. whether the editor has focused or IME focus has
3933         // changed from another window).
3934         final ImeTargetWindowState windowState = new ImeTargetWindowState(
3935                 softInputMode, windowFlags, !sameWindowFocused, isTextEditor,
3936                 startInputByWinGainedFocus, toolType);
3937         final var visibilityStateComputer = userData.mVisibilityStateComputer;
3938         visibilityStateComputer.setWindowState(windowToken, windowState);
3939 
3940         if (sameWindowFocused && isTextEditor) {
3941             ProtoLog.v(IMMS_DEBUG,
3942                     "Window already focused, ignoring focus gain of: %s editorInfo=%s, token=%s, "
3943                             + "startInputReason=%s",
3944                     client, editorInfo, windowToken,
3945                     InputMethodDebug.startInputReasonToString(startInputReason));
3946             if (editorInfo != null) {
3947                 return startInputUncheckedLocked(cs, inputContext,
3948                         remoteAccessibilityInputConnection, editorInfo, startInputFlags,
3949                         startInputReason, unverifiedTargetSdkVersion, imeDispatcher,
3950                         bindingController);
3951             }
3952             return new InputBindResult(
3953                     InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
3954                     null, null, null, null, -1, false);
3955         }
3956 
3957         userData.mImeBindingState = new ImeBindingState(bindingController.getUserId(), windowToken,
3958                 softInputMode, cs, editorInfo);
3959         mFocusedWindowPerceptible.put(windowToken, true);
3960 
3961         // We want to start input before showing the IME, but after closing
3962         // it.  We want to do this after closing it to help the IME disappear
3963         // more quickly (not get stuck behind it initializing itself for the
3964         // new focused input, even if its window wants to hide the IME).
3965         boolean didStart = false;
3966         InputBindResult res = null;
3967 
3968         final ImeVisibilityResult imeVisRes = visibilityStateComputer.computeState(windowState,
3969                 isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags),
3970                 imeRequestedVisible);
3971         if (imeVisRes != null) {
3972             boolean isShow = false;
3973             switch (imeVisRes.getReason()) {
3974                 case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY:
3975                 case SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV:
3976                 case SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV:
3977                 case SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE:
3978                     isShow = true;
3979 
3980                     if (editorInfo != null) {
3981                         res = startInputUncheckedLocked(cs, inputContext,
3982                                 remoteAccessibilityInputConnection, editorInfo, startInputFlags,
3983                                 startInputReason, unverifiedTargetSdkVersion,
3984                                 imeDispatcher, bindingController);
3985                         didStart = true;
3986                     }
3987                     break;
3988             }
3989             final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason(),
3990                     userId);
3991             mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow,
3992                     statsToken, imeVisRes.getState(), imeVisRes.getReason(), userId);
3993             if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) {
3994                 // If focused display changed, we should unbind current method
3995                 // to make app window in previous display relayout after Ime
3996                 // window token removed.
3997                 // Note that we can trust client's display ID as long as it matches
3998                 // to the display ID obtained from the window.
3999                 if (cs.mSelfReportedDisplayId != bindingController.getCurTokenDisplayId()) {
4000                     bindingController.unbindCurrentMethod();
4001                 }
4002             }
4003         }
4004         if (!didStart) {
4005             if (editorInfo != null) {
4006                 res = startInputUncheckedLocked(cs, inputContext,
4007                         remoteAccessibilityInputConnection, editorInfo, startInputFlags,
4008                         startInputReason, unverifiedTargetSdkVersion,
4009                         imeDispatcher, bindingController);
4010             } else {
4011                 res = InputBindResult.NULL_EDITOR_INFO;
4012             }
4013         }
4014         return res;
4015     }
4016 
4017     @GuardedBy("ImfLock.class")
canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName, @Nullable ImeTracker.Token statsToken, @UserIdInt int userId)4018     private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName,
4019             @Nullable ImeTracker.Token statsToken, @UserIdInt int userId) {
4020         final var userData = getUserData(userId);
4021         if (userData.mCurClient == null || client == null
4022                 || userData.mCurClient.mClient.asBinder() != client.asBinder()) {
4023             // We need to check if this is the current client with
4024             // focus in the window manager, to allow this call to
4025             // be made before input is started in it.
4026             final ClientState cs = mClientController.getClient(client.asBinder());
4027             if (cs == null) {
4028                 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
4029                 throw new IllegalArgumentException("unknown client " + client.asBinder());
4030             }
4031             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
4032             if (!isImeClientFocused(userData.mImeBindingState.mFocusedWindow, cs)) {
4033                 Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
4034                 return false;
4035             }
4036         }
4037         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
4038         return true;
4039     }
4040 
4041     @GuardedBy("ImfLock.class")
canShowInputMethodPickerLocked(IInputMethodClient client, @UserIdInt int userId)4042     private boolean canShowInputMethodPickerLocked(IInputMethodClient client,
4043             @UserIdInt int userId) {
4044         final int uid = Binder.getCallingUid();
4045         final var userData = getUserData(userId);
4046         if (userData.mImeBindingState.mFocusedWindowClient != null && client != null
4047                 && userData.mImeBindingState.mFocusedWindowClient.mClient.asBinder()
4048                 == client.asBinder()) {
4049             return true;
4050         }
4051         if (userId != UserHandle.getUserId(uid)) {
4052             return false;
4053         }
4054         final var curIntent = getInputMethodBindingController(userId).getCurIntent();
4055         if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid(
4056                 mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) {
4057             return true;
4058         }
4059         return false;
4060     }
4061 
4062     @Override
showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode)4063     public void showInputMethodPickerFromClient(IInputMethodClient client,
4064             int auxiliarySubtypeMode) {
4065         if (mConcurrentMultiUserModeEnabled) {
4066             Slog.w(TAG, "showInputMethodPickerFromClient is not enabled on automotive");
4067             return;
4068         }
4069         final int callingUserId = UserHandle.getCallingUserId();
4070         synchronized (ImfLock.class) {
4071             final int userId = resolveImeUserIdLocked(callingUserId);
4072             if (!canShowInputMethodPickerLocked(client, userId)) {
4073                 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
4074                         + Binder.getCallingUid() + ": " + client);
4075                 return;
4076             }
4077             final var userData = getUserData(userId);
4078             // Always call subtype picker, because subtype picker is a superset of input method
4079             // picker.
4080             final int displayId = (userData.mCurClient != null)
4081                     ? userData.mCurClient.mSelfReportedDisplayId : DEFAULT_DISPLAY;
4082             mHandler.post(() -> {
4083                 synchronized (ImfLock.class) {
4084                     showInputMethodPickerLocked(auxiliarySubtypeMode, displayId, userId);
4085                 }
4086             });
4087         }
4088     }
4089 
4090     @IInputMethodManagerImpl.PermissionVerified(allOf = {
4091             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
4092             Manifest.permission.WRITE_SECURE_SETTINGS})
4093     @Override
showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId)4094     public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
4095         // Always call subtype picker, because subtype picker is a superset of input method
4096         // picker.
4097         mHandler.post(() -> {
4098             synchronized (ImfLock.class) {
4099                 final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
4100                 showInputMethodPickerLocked(auxiliarySubtypeMode, displayId, userId);
4101             }
4102         });
4103     }
4104 
4105     /**
4106      * A test API for CTS to make sure that the input method menu is showing.
4107      */
4108     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
isInputMethodPickerShownForTest()4109     public boolean isInputMethodPickerShownForTest() {
4110         synchronized (ImfLock.class) {
4111             return Flags.imeSwitcherRevamp()
4112                     ? mMenuControllerNew.isShowing()
4113                     : mMenuController.isisInputMethodPickerShownForTestLocked();
4114         }
4115     }
4116 
4117     @IInputMethodManagerImpl.PermissionVerified(allOf = {
4118             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
4119             Manifest.permission.WRITE_SECURE_SETTINGS})
4120     @Override
onImeSwitchButtonClickFromSystem(int displayId)4121     public void onImeSwitchButtonClickFromSystem(int displayId) {
4122         synchronized (ImfLock.class) {
4123             final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
4124             final var userData = getUserData(userId);
4125 
4126             onImeSwitchButtonClickLocked(displayId, userData);
4127         }
4128     }
4129 
4130     /**
4131      * Handles a click on the IME switch button. Depending on the number of enabled IME subtypes,
4132      * this will either switch to the next IME/subtype, or show the input method picker dialog.
4133      *
4134      * @param displayId The ID of the display where the input method picker dialog should be shown.
4135      * @param userData  The data of the user for which to switch IMEs or show the picker dialog.
4136      */
4137     @BinderThread
4138     @GuardedBy("ImfLock.class")
onImeSwitchButtonClickLocked(int displayId, @NonNull UserData userData)4139     private void onImeSwitchButtonClickLocked(int displayId, @NonNull UserData userData) {
4140         final int userId = userData.mUserId;
4141         if (hasMultipleSubtypesForSwitcher(true /* nonAuxOnly */, userId)) {
4142             switchToNextInputMethodLocked(false /* onlyCurrentIme */, userData);
4143         } else {
4144             showInputMethodPickerFromSystem(
4145                     InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, displayId);
4146         }
4147     }
4148 
4149     /**
4150      * A test API for CTS to check whether the IME Switcher button should be shown when the IME
4151      * is shown.
4152      */
4153     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
shouldShowImeSwitcherButtonForTest()4154     public boolean shouldShowImeSwitcherButtonForTest() {
4155         final int callingUserId = UserHandle.getCallingUserId();
4156         synchronized (ImfLock.class) {
4157             final int userId = resolveImeUserIdLocked(callingUserId);
4158             return shouldShowImeSwitcherLocked(
4159                     InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId);
4160         }
4161     }
4162 
4163     @NonNull
getExceptionForUnknownImeId( @ullable String imeId)4164     private static IllegalArgumentException getExceptionForUnknownImeId(
4165             @Nullable String imeId) {
4166         return new IllegalArgumentException("Unknown id: " + imeId);
4167     }
4168 
4169     @BinderThread
4170     @GuardedBy("ImfLock.class")
setInputMethodAndSubtypeLocked(String id, @Nullable InputMethodSubtype subtype, @NonNull UserData userData)4171     private void setInputMethodAndSubtypeLocked(String id, @Nullable InputMethodSubtype subtype,
4172             @NonNull UserData userData) {
4173         final int callingUid = Binder.getCallingUid();
4174         final int userId = userData.mUserId;
4175         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
4176         final InputMethodInfo imi = settings.getMethodMap().get(id);
4177         if (imi == null || !canCallerAccessInputMethod(
4178                 imi.getPackageName(), callingUid, userId, settings)) {
4179             throw getExceptionForUnknownImeId(id);
4180         }
4181         final int subtypeIndex = subtype != null
4182                 ? SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtype.hashCode())
4183                 : NOT_A_SUBTYPE_INDEX;
4184         setInputMethodWithSubtypeIndexLocked(id, subtypeIndex, userId);
4185     }
4186 
4187     @BinderThread
4188     @GuardedBy("ImfLock.class")
switchToPreviousInputMethodLocked(@onNull UserData userData)4189     private boolean switchToPreviousInputMethodLocked(@NonNull UserData userData) {
4190         final int userId = userData.mUserId;
4191         final var bindingController = userData.mBindingController;
4192         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
4193         final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
4194         final InputMethodInfo lastImi;
4195         if (lastIme != null) {
4196             lastImi = settings.getMethodMap().get(lastIme.first);
4197         } else {
4198             lastImi = null;
4199         }
4200         final var currentSubtype = bindingController.getCurrentSubtype();
4201         String targetLastImiId = null;
4202         int subtypeIndex = NOT_A_SUBTYPE_INDEX;
4203         if (lastIme != null && lastImi != null) {
4204             final boolean imiIdIsSame = lastImi.getId().equals(
4205                     bindingController.getSelectedMethodId());
4206             final int lastSubtypeHash = Integer.parseInt(lastIme.second);
4207             final int currentSubtypeHash = currentSubtype == null ? NOT_A_SUBTYPE_INDEX
4208                     : currentSubtype.hashCode();
4209             // If the last IME is the same as the current IME and the last subtype is not
4210             // defined, there is no need to switch to the last IME.
4211             if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
4212                 targetLastImiId = lastIme.first;
4213                 subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(lastImi, lastSubtypeHash);
4214             }
4215         }
4216 
4217         if (TextUtils.isEmpty(targetLastImiId)
4218                 && !InputMethodUtils.canAddToLastInputMethod(currentSubtype)) {
4219             // This is a safety net. If the currentSubtype can't be added to the history
4220             // and the framework couldn't find the last ime, we will make the last ime be
4221             // the most applicable enabled keyboard subtype of the system imes.
4222             final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
4223             final int enabledCount = enabled.size();
4224             final String locale;
4225             if (currentSubtype != null
4226                     && !TextUtils.isEmpty(currentSubtype.getLocale())) {
4227                 locale = currentSubtype.getLocale();
4228             } else {
4229                 locale = SystemLocaleWrapper.get(userId).get(0).toString();
4230             }
4231             for (int i = 0; i < enabledCount; ++i) {
4232                 final InputMethodInfo imi = enabled.get(i);
4233                 if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
4234                     InputMethodSubtype keyboardSubtype =
4235                             SubtypeUtils.findLastResortApplicableSubtype(
4236                                     SubtypeUtils.getSubtypes(imi),
4237                                     SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
4238                     if (keyboardSubtype != null) {
4239                         targetLastImiId = imi.getId();
4240                         subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi,
4241                                 keyboardSubtype.hashCode());
4242                         if (keyboardSubtype.getLocale().equals(locale)) {
4243                             break;
4244                         }
4245                     }
4246                 }
4247             }
4248         }
4249 
4250         if (!TextUtils.isEmpty(targetLastImiId)) {
4251             ProtoLog.v(IMMS_DEBUG, "Switch to: %s, %s, from: %s, %s", lastImi.getId(),
4252                     lastIme.second, bindingController.getSelectedMethodId(), subtypeIndex);
4253             setInputMethodWithSubtypeIndexLocked(targetLastImiId, subtypeIndex, userId);
4254             return true;
4255         } else {
4256             return false;
4257         }
4258     }
4259 
4260     @BinderThread
4261     @GuardedBy("ImfLock.class")
switchToNextInputMethodLocked(boolean onlyCurrentIme, @NonNull UserData userData)4262     private boolean switchToNextInputMethodLocked(boolean onlyCurrentIme,
4263             @NonNull UserData userData) {
4264         final var bindingController = userData.mBindingController;
4265         final var currentImi = bindingController.getSelectedMethod();
4266         if (currentImi == null) {
4267             return false;
4268         }
4269         final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
4270                 .getNextInputMethodLocked(onlyCurrentIme, currentImi,
4271                         bindingController.getCurrentSubtype(),
4272                         MODE_AUTO, true /* forward */);
4273         if (nextSubtype == null) {
4274             return false;
4275         }
4276         setInputMethodWithSubtypeIndexLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeIndex,
4277                 userData.mUserId);
4278         return true;
4279     }
4280 
4281     @BinderThread
4282     @GuardedBy("ImfLock.class")
shouldOfferSwitchingToNextInputMethodLocked(@onNull UserData userData)4283     private boolean shouldOfferSwitchingToNextInputMethodLocked(@NonNull UserData userData) {
4284         final var bindingController = userData.mBindingController;
4285         final var currentImi = bindingController.getSelectedMethod();
4286         if (currentImi == null) {
4287             return false;
4288         }
4289         final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
4290                 .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
4291                         bindingController.getCurrentSubtype(),
4292                         MODE_AUTO, true /* forward */);
4293         return nextSubtype != null;
4294     }
4295 
4296     @Override
getLastInputMethodSubtype(@serIdInt int userId)4297     public InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
4298         if (UserHandle.getCallingUserId() != userId) {
4299             mContext.enforceCallingOrSelfPermission(
4300                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
4301         }
4302         synchronized (ImfLock.class) {
4303             return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
4304         }
4305     }
4306 
4307     @Override
setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes, @UserIdInt int userId)4308     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
4309             @UserIdInt int userId) {
4310         if (UserHandle.getCallingUserId() != userId) {
4311             mContext.enforceCallingOrSelfPermission(
4312                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
4313         }
4314         final int callingUid = Binder.getCallingUid();
4315 
4316         // By this IPC call, only a process which shares the same uid with the IME can add
4317         // additional input method subtypes to the IME.
4318         if (TextUtils.isEmpty(imiId) || subtypes == null) return;
4319         final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>();
4320         for (InputMethodSubtype subtype : subtypes) {
4321             if (!toBeAdded.contains(subtype)) {
4322                 toBeAdded.add(subtype);
4323             } else {
4324                 Slog.w(TAG, "Duplicated subtype definition found: "
4325                         + subtype.getLocale() + ", " + subtype.getMode());
4326             }
4327         }
4328         final var userData = getUserData(userId);
4329         synchronized (ImfLock.class) {
4330             if (!mSystemReady) {
4331                 return;
4332             }
4333 
4334             final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
4335             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
4336             final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
4337                     imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
4338             if (additionalSubtypeMap != newAdditionalSubtypeMap) {
4339                 AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
4340                         settings.getMethodMap());
4341                 final long ident = Binder.clearCallingIdentity();
4342                 try {
4343                     final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
4344                             AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
4345                             userData.mIsUnlockingOrUnlocked.get());
4346                     final var newSettings = InputMethodSettings.create(methodMap, userId);
4347                     InputMethodSettingsRepository.put(userId, newSettings);
4348                     postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
4349                 } finally {
4350                     Binder.restoreCallingIdentity(ident);
4351                 }
4352             }
4353         }
4354     }
4355 
4356     @Override
setExplicitlyEnabledInputMethodSubtypes(String imeId, @NonNull int[] subtypeHashCodes, @UserIdInt int userId)4357     public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
4358             @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
4359         if (UserHandle.getCallingUserId() != userId) {
4360             mContext.enforceCallingOrSelfPermission(
4361                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
4362         }
4363         final int callingUid = Binder.getCallingUid();
4364         final ComponentName imeComponentName =
4365                 imeId != null ? ComponentName.unflattenFromString(imeId) : null;
4366         if (imeComponentName == null || !InputMethodUtils.checkIfPackageBelongsToUid(
4367                 mPackageManagerInternal, callingUid, imeComponentName.getPackageName())) {
4368             throw new SecurityException("Calling UID=" + callingUid + " does not belong to imeId="
4369                     + imeId);
4370         }
4371         Objects.requireNonNull(subtypeHashCodes, "subtypeHashCodes must not be null");
4372 
4373         final long ident = Binder.clearCallingIdentity();
4374         try {
4375             synchronized (ImfLock.class) {
4376                 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
4377                 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
4378                     return;
4379                 }
4380                 // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening.
4381                 final var userData = getUserData(userId);
4382                 userData.mLastEnabledInputMethodsStr = settings.getEnabledInputMethodsStr();
4383                 updateInputMethodsFromSettingsLocked(false /* enabledChanged */, userId);
4384             }
4385         } finally {
4386             Binder.restoreCallingIdentity(ident);
4387         }
4388     }
4389 
4390     /**
4391      * This is kept due to {@code @UnsupportedAppUsage} in
4392      * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in
4393      * {@link InputMethodService#onCreate()}.
4394      * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight(int)}
4395      *
4396      * @deprecated TODO(b/113914148): Check if we can remove this
4397      */
4398     @Override
4399     @Deprecated
getInputMethodWindowVisibleHeight(@onNull IInputMethodClient client)4400     public int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) {
4401         final int callingUid = Binder.getCallingUid();
4402         final int callingUserId = UserHandle.getCallingUserId();
4403         return Binder.withCleanCallingIdentity(() -> {
4404             final int curTokenDisplayId;
4405             synchronized (ImfLock.class) {
4406                 final int userId = resolveImeUserIdLocked(callingUserId);
4407                 if (!canInteractWithImeLocked(callingUid, client,
4408                         "getInputMethodWindowVisibleHeight", null /* statsToken */, userId)) {
4409                     return 0;
4410                 }
4411                 final var bindingController = getInputMethodBindingController(userId);
4412                 // This should probably use the caller's display id, but because this is unsupported
4413                 // and maintained only for compatibility, there's no point in fixing it.
4414                 curTokenDisplayId = bindingController.getCurTokenDisplayId();
4415             }
4416             return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId);
4417         });
4418     }
4419 
4420     @IInputMethodManagerImpl.PermissionVerified(allOf = {
4421             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
4422             Manifest.permission.INTERNAL_SYSTEM_WINDOW})
4423     @Override
removeImeSurface(int displayId)4424     public void removeImeSurface(int displayId) {
4425         mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
4426     }
4427 
4428     @Override
removeImeSurfaceFromWindowAsync(IBinder windowToken)4429     public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
4430         // No permission check, because we'll only execute the request if the calling window is
4431         // also the current IME client.
4432         mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
4433     }
4434 
registerDeviceListenerAndCheckStylusSupport()4435     private void registerDeviceListenerAndCheckStylusSupport() {
4436         final InputManager im = mContext.getSystemService(InputManager.class);
4437         final IntArray stylusIds = getStylusInputDeviceIds(im);
4438         if (stylusIds.size() > 0) {
4439             synchronized (ImfLock.class) {
4440                 mStylusIds = new IntArray();
4441                 mStylusIds.addAll(stylusIds);
4442             }
4443         }
4444         im.registerInputDeviceListener(new InputManager.InputDeviceListener() {
4445             @Override
4446             public void onInputDeviceAdded(int deviceId) {
4447                 InputDevice device = im.getInputDevice(deviceId);
4448                 if (device != null && isStylusDevice(device)) {
4449                     add(deviceId);
4450                 }
4451             }
4452 
4453             @Override
4454             public void onInputDeviceRemoved(int deviceId) {
4455                 remove(deviceId);
4456             }
4457 
4458             @Override
4459             public void onInputDeviceChanged(int deviceId) {
4460                 InputDevice device = im.getInputDevice(deviceId);
4461                 if (device == null) {
4462                     return;
4463                 }
4464                 if (isStylusDevice(device)) {
4465                     add(deviceId);
4466                 } else {
4467                     remove(deviceId);
4468                 }
4469             }
4470 
4471             private void add(int deviceId) {
4472                 synchronized (ImfLock.class) {
4473                     addStylusDeviceIdLocked(deviceId);
4474                 }
4475             }
4476 
4477             private void remove(int deviceId) {
4478                 synchronized (ImfLock.class) {
4479                     removeStylusDeviceIdLocked(deviceId);
4480                 }
4481             }
4482         }, mHandler);
4483     }
4484 
4485     @GuardedBy("ImfLock.class")
addStylusDeviceIdLocked(int deviceId)4486     private void addStylusDeviceIdLocked(int deviceId) {
4487         if (mStylusIds == null) {
4488             mStylusIds = new IntArray();
4489         } else if (mStylusIds.indexOf(deviceId) != -1) {
4490             return;
4491         }
4492         Slog.d(TAG, "New Stylus deviceId" + deviceId + " added.");
4493         mStylusIds.add(deviceId);
4494         // a new Stylus is detected. If IME supports handwriting, and we don't have
4495         // handwriting initialized, lets do it now.
4496         final var bindingController = getInputMethodBindingController(mCurrentImeUserId);
4497         if (!mHwController.getCurrentRequestId().isPresent()
4498                 && bindingController.supportsStylusHandwriting()) {
4499             scheduleResetStylusHandwriting();
4500         }
4501     }
4502 
removeStylusDeviceIdLocked(int deviceId)4503     private void removeStylusDeviceIdLocked(int deviceId) {
4504         if (mStylusIds == null || mStylusIds.size() == 0) {
4505             return;
4506         }
4507         int index;
4508         if ((index = mStylusIds.indexOf(deviceId)) != -1) {
4509             mStylusIds.remove(index);
4510             Slog.d(TAG, "Stylus deviceId: " + deviceId + " removed.");
4511         }
4512         if (mStylusIds.size() == 0) {
4513             // no more supported stylus(es) in system.
4514             mHwController.reset();
4515             scheduleRemoveStylusHandwritingWindow();
4516         }
4517     }
4518 
isStylusDevice(InputDevice inputDevice)4519     private static boolean isStylusDevice(InputDevice inputDevice) {
4520         return inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)
4521                 || inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS);
4522     }
4523 
isFingerDevice(InputDevice inputDevice)4524     private static boolean isFingerDevice(InputDevice inputDevice) {
4525         return inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN);
4526     }
4527 
4528     @GuardedBy("ImfLock.class")
hasSupportedStylusLocked()4529     private boolean hasSupportedStylusLocked() {
4530         return mStylusIds != null && mStylusIds.size() != 0;
4531     }
4532 
4533     /**
4534      * Helper method that adds a virtual stylus id for next handwriting session test if
4535      * a stylus deviceId is not already registered on device.
4536      */
4537     @BinderThread
4538     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
4539     @Override
addVirtualStylusIdForTestSession(IInputMethodClient client)4540     public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
4541         final int uid = Binder.getCallingUid();
4542         final int callingUserId = UserHandle.getUserId(uid);
4543         synchronized (ImfLock.class) {
4544             final int userId = resolveImeUserIdLocked(callingUserId);
4545             if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession",
4546                     null /* statsToken */, userId)) {
4547                 return;
4548             }
4549             final long ident = Binder.clearCallingIdentity();
4550             try {
4551                 ProtoLog.v(IMMS_DEBUG, "Adding virtual stylus id for session");
4552                 addStylusDeviceIdLocked(VIRTUAL_STYLUS_ID_FOR_TEST);
4553             } finally {
4554                 Binder.restoreCallingIdentity(ident);
4555             }
4556         }
4557     }
4558 
4559     /**
4560      * Helper method to set a stylus idle-timeout after which handwriting {@code InkWindow}
4561      * will be removed.
4562      *
4563      * @param timeout to set in milliseconds. To reset to default, use a value <= zero
4564      */
4565     @BinderThread
4566     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
4567     @Override
setStylusWindowIdleTimeoutForTest( IInputMethodClient client, @DurationMillisLong long timeout)4568     public void setStylusWindowIdleTimeoutForTest(
4569             IInputMethodClient client, @DurationMillisLong long timeout) {
4570         final int uid = Binder.getCallingUid();
4571         final int callingUserId = UserHandle.getUserId(uid);
4572         synchronized (ImfLock.class) {
4573             final int userId = resolveImeUserIdLocked(callingUserId);
4574             if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest",
4575                     null /* statsToken */, userId)) {
4576                 return;
4577             }
4578             final long ident = Binder.clearCallingIdentity();
4579             try {
4580                 ProtoLog.v(IMMS_DEBUG, "Setting stylus window idle timeout");
4581                 getCurMethodLocked().setStylusWindowIdleTimeoutForTest(timeout);
4582             } finally {
4583                 Binder.restoreCallingIdentity(ident);
4584             }
4585         }
4586     }
4587 
4588     @GuardedBy("ImfLock.class")
removeVirtualStylusIdForTestSessionLocked()4589     private void removeVirtualStylusIdForTestSessionLocked() {
4590         removeStylusDeviceIdLocked(VIRTUAL_STYLUS_ID_FOR_TEST);
4591     }
4592 
getStylusInputDeviceIds(InputManager im)4593     private static IntArray getStylusInputDeviceIds(InputManager im) {
4594         IntArray stylusIds = new IntArray();
4595         for (int id : im.getInputDeviceIds()) {
4596             InputDevice device = im.getInputDevice(id);
4597             if (device != null && device.isEnabled() && isStylusDevice(device)) {
4598                 stylusIds.add(id);
4599             }
4600         }
4601 
4602         return stylusIds;
4603     }
4604 
4605     /**
4606      * Starting point for dumping the IME tracing information in proto format.
4607      *
4608      * @param protoDump dump information from the IME client side
4609      */
4610     @BinderThread
4611     @Override
startProtoDump(byte[] protoDump, int source, String where)4612     public void startProtoDump(byte[] protoDump, int source, String where) {
4613         if (protoDump == null && source != ImeTracing.IME_TRACING_FROM_IMMS) {
4614             // Dump not triggered from IMMS, but no proto information provided.
4615             return;
4616         }
4617         ImeTracing tracingInstance = ImeTracing.getInstance();
4618         if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
4619             return;
4620         }
4621 
4622         ProtoOutputStream proto = new ProtoOutputStream();
4623         switch (source) {
4624             case ImeTracing.IME_TRACING_FROM_CLIENT:
4625                 final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
4626                 proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
4627                         SystemClock.elapsedRealtimeNanos());
4628                 proto.write(InputMethodClientsTraceProto.WHERE, where);
4629                 proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
4630                 proto.end(client_token);
4631                 break;
4632             case ImeTracing.IME_TRACING_FROM_IMS:
4633                 final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
4634                 proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
4635                         SystemClock.elapsedRealtimeNanos());
4636                 proto.write(InputMethodServiceTraceProto.WHERE, where);
4637                 proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
4638                 proto.end(service_token);
4639                 break;
4640             case ImeTracing.IME_TRACING_FROM_IMMS:
4641                 final long managerservice_token =
4642                         proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
4643                 proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
4644                         SystemClock.elapsedRealtimeNanos());
4645                 proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
4646                 dumpDebug(proto,
4647                         InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
4648                 proto.end(managerservice_token);
4649                 break;
4650             default:
4651                 // Dump triggered by a source not recognised.
4652                 return;
4653         }
4654         tracingInstance.addToBuffer(proto, source);
4655     }
4656 
4657     @BinderThread
4658     @Override
isImeTraceEnabled()4659     public boolean isImeTraceEnabled() {
4660         return ImeTracing.getInstance().isEnabled();
4661     }
4662 
4663     @BinderThread
4664     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
4665     @Override
startImeTrace()4666     public void startImeTrace() {
4667         ImeTracing.getInstance().startTrace(null /* printwriter */);
4668         synchronized (ImfLock.class) {
4669             mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(true /* enabled */));
4670         }
4671     }
4672 
4673     @BinderThread
4674     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
4675     @Override
stopImeTrace()4676     public void stopImeTrace() {
4677         ImeTracing.getInstance().stopTrace(null /* printwriter */);
4678         synchronized (ImfLock.class) {
4679             mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(false /* enabled */));
4680         }
4681     }
4682 
4683     // TODO(b/356239178): Make dump proto multi-user aware.
dumpDebug(ProtoOutputStream proto, long fieldId)4684     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
4685         synchronized (ImfLock.class) {
4686             final int userId = mCurrentImeUserId;
4687             final var userData = getUserData(userId);
4688             final var bindingController = userData.mBindingController;
4689             final var visibilityStateComputer = userData.mVisibilityStateComputer;
4690             final long token = proto.start(fieldId);
4691             proto.write(CUR_METHOD_ID, bindingController.getSelectedMethodId());
4692             proto.write(CUR_SEQ, bindingController.getSequenceNumber());
4693             proto.write(CUR_CLIENT, Objects.toString(userData.mCurClient));
4694             userData.mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
4695             proto.write(LAST_IME_TARGET_WINDOW_NAME, mWindowManagerInternal.getWindowName(
4696                     visibilityStateComputer.getLastImeTargetWindow()));
4697             proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
4698                     userData.mImeBindingState.mFocusedWindowSoftInputMode));
4699             if (userData.mCurEditorInfo != null) {
4700                 userData.mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
4701             }
4702             proto.write(CUR_ID, bindingController.getCurId());
4703             visibilityStateComputer.dumpDebug(proto, fieldId);
4704             proto.write(IN_FULLSCREEN_MODE, userData.mInFullscreenMode);
4705             proto.write(CUR_TOKEN, Objects.toString(bindingController.getCurToken()));
4706             proto.write(CUR_TOKEN_DISPLAY_ID, bindingController.getCurTokenDisplayId());
4707             proto.write(SYSTEM_READY, mSystemReady);
4708             proto.write(HAVE_CONNECTION, bindingController.hasMainConnection());
4709             proto.write(BOUND_TO_METHOD, userData.mBoundToMethod);
4710             proto.write(IS_INTERACTIVE, mIsInteractive);
4711             proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
4712             proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
4713             if (!Flags.imeSwitcherRevamp()) {
4714                 proto.write(SHOW_IME_WITH_HARD_KEYBOARD,
4715                         mMenuController.getShowImeWithHardKeyboard());
4716             }
4717             proto.write(CONCURRENT_MULTI_USER_MODE_ENABLED, mConcurrentMultiUserModeEnabled);
4718             proto.end(token);
4719         }
4720     }
4721 
4722     @BinderThread
4723     @GuardedBy("ImfLock.class")
notifyUserActionLocked(@onNull UserData userData)4724     private void notifyUserActionLocked(@NonNull UserData userData) {
4725         ProtoLog.v(IMMS_DEBUG, "Got the notification of a user action.");
4726         final var bindingController = userData.mBindingController;
4727         final InputMethodInfo imi = bindingController.getSelectedMethod();
4728         if (imi != null) {
4729             userData.mSwitchingController.onUserActionLocked(imi,
4730                     bindingController.getCurrentSubtype());
4731         }
4732     }
4733 
4734     @BinderThread
4735     @GuardedBy("ImfLock.class")
applyImeVisibilityLocked(IBinder windowToken, boolean setVisible, @NonNull ImeTracker.Token statsToken, @NonNull UserData userData)4736     private void applyImeVisibilityLocked(IBinder windowToken, boolean setVisible,
4737             @NonNull ImeTracker.Token statsToken, @NonNull UserData userData) {
4738         try {
4739             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibilityLocked");
4740             final int userId = userData.mUserId;
4741             final var visibilityStateComputer = userData.mVisibilityStateComputer;
4742             final IBinder requestToken = visibilityStateComputer.getWindowTokenFrom(
4743                     windowToken, userId);
4744             mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
4745                     setVisible ? STATE_SHOW_IME : STATE_HIDE_IME,
4746                     SoftInputShowHideReason.NOT_SET /* ignore reason */, userId);
4747         } finally {
4748             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
4749         }
4750     }
4751 
4752     @BinderThread
4753     @GuardedBy("ImfLock.class")
resetStylusHandwritingLocked(int requestId)4754     private void resetStylusHandwritingLocked(int requestId) {
4755         final OptionalInt curRequest = mHwController.getCurrentRequestId();
4756         if (!curRequest.isPresent() || curRequest.getAsInt() != requestId) {
4757             Slog.w(TAG, "IME requested to finish handwriting with a mismatched requestId: "
4758                     + requestId);
4759         }
4760         removeVirtualStylusIdForTestSessionLocked();
4761         scheduleResetStylusHandwriting();
4762     }
4763 
4764     @GuardedBy("ImfLock.class")
setInputMethodWithSubtypeIndexLocked(String id, int subtypeIndex, @UserIdInt int userId)4765     private void setInputMethodWithSubtypeIndexLocked(String id, int subtypeIndex,
4766             @UserIdInt int userId) {
4767         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
4768         if (settings.getMethodMap().get(id) != null
4769                 && settings.getEnabledInputMethodListWithFilter(
4770                         (info) -> info.getId().equals(id)).isEmpty()) {
4771             throw new IllegalStateException("Requested IME is not enabled: " + id);
4772         }
4773         final long ident = Binder.clearCallingIdentity();
4774         try {
4775             setInputMethodLocked(id, subtypeIndex, userId);
4776         } finally {
4777             Binder.restoreCallingIdentity(ident);
4778         }
4779     }
4780 
4781     /**
4782      * Called right after {@link IInputMethod#showSoftInput} or {@link IInputMethod#hideSoftInput}.
4783      */
4784     @GuardedBy("ImfLock.class")
onShowHideSoftInputRequested(boolean show, IBinder requestImeToken, @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken, @UserIdInt int userId)4785     void onShowHideSoftInputRequested(boolean show, IBinder requestImeToken,
4786             @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken,
4787             @UserIdInt int userId) {
4788         final var userData = getUserData(userId);
4789         final var visibilityStateComputer = userData.mVisibilityStateComputer;
4790         final IBinder requestToken = visibilityStateComputer.getWindowTokenFrom(requestImeToken,
4791                 userId);
4792         final var bindingController = userData.mBindingController;
4793         final WindowManagerInternal.ImeTargetInfo info =
4794                 mWindowManagerInternal.onToggleImeRequested(
4795                         show, userData.mImeBindingState.mFocusedWindow, requestToken,
4796                         bindingController.getCurTokenDisplayId());
4797         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
4798                 userData.mImeBindingState.mFocusedWindowClient,
4799                 userData.mImeBindingState.mFocusedWindowEditorInfo,
4800                 info.focusedWindowName, userData.mImeBindingState.mFocusedWindowSoftInputMode,
4801                 reason, userData.mInFullscreenMode, info.requestWindowName,
4802                 info.imeControlTargetName, info.imeLayerTargetName, info.imeSurfaceParentName,
4803                 userId));
4804 
4805         if (statsToken != null) {
4806             mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
4807         }
4808     }
4809 
4810     @BinderThread
4811     @GuardedBy("ImfLock.class")
hideMySoftInputLocked(@onNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, @NonNull UserData userData)4812     private void hideMySoftInputLocked(@NonNull ImeTracker.Token statsToken,
4813             @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
4814             @NonNull UserData userData) {
4815         final int userId = userData.mUserId;
4816         if (Flags.refactorInsetsController()) {
4817             userData.mCurClient.mClient.setImeVisibility(false, statsToken);
4818             // TODO we will loose the flags here
4819             setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
4820         } else {
4821             final var visibilityStateComputer = userData.mVisibilityStateComputer;
4822             hideCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
4823                     statsToken, flags, null /* resultReceiver */, reason, userId);
4824         }
4825     }
4826 
4827     @BinderThread
4828     @GuardedBy("ImfLock.class")
showMySoftInputLocked(@onNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, @NonNull UserData userData)4829     private void showMySoftInputLocked(@NonNull ImeTracker.Token statsToken,
4830             @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
4831             @NonNull UserData userData) {
4832         final int userId = userData.mUserId;
4833         if (Flags.refactorInsetsController()) {
4834             userData.mCurClient.mClient.setImeVisibility(true, statsToken);
4835             setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
4836         } else {
4837             final var visibilityStateComputer = userData.mVisibilityStateComputer;
4838             showCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
4839                     statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN,
4840                     null /* resultReceiver */, reason, userId);
4841         }
4842     }
4843 
4844     @GuardedBy("ImfLock.class")
4845     @VisibleForTesting
getVisibilityApplierLocked()4846     DefaultImeVisibilityApplier getVisibilityApplierLocked() {
4847         return mVisibilityApplier;
4848     }
4849 
4850     @GuardedBy("ImfLock.class")
onApplyImeVisibilityFromComputerLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result, @UserIdInt int userId)4851     void onApplyImeVisibilityFromComputerLocked(IBinder windowToken,
4852             @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result,
4853             @UserIdInt int userId) {
4854         mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(),
4855                 result.getReason(), userId);
4856     }
4857 
4858     @GuardedBy("ImfLock.class")
setEnabledSessionLocked(SessionState session, @NonNull UserData userData)4859     void setEnabledSessionLocked(SessionState session, @NonNull UserData userData) {
4860         if (userData.mEnabledSession != session) {
4861             if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) {
4862                 ProtoLog.v(IMMS_DEBUG, "Disabling: " + userData.mEnabledSession);
4863                 userData.mEnabledSession.mMethod.setSessionEnabled(
4864                         userData.mEnabledSession.mSession, false);
4865             }
4866             userData.mEnabledSession = session;
4867             if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) {
4868                 ProtoLog.v(IMMS_DEBUG, "Enabling: " + userData.mEnabledSession);
4869                 userData.mEnabledSession.mMethod.setSessionEnabled(
4870                         userData.mEnabledSession.mSession, true);
4871             }
4872         }
4873     }
4874 
4875     @GuardedBy("ImfLock.class")
setEnabledSessionForAccessibilityLocked( @onNull SparseArray<AccessibilitySessionState> accessibilitySessions, @NonNull UserData userData)4876     void setEnabledSessionForAccessibilityLocked(
4877             @NonNull SparseArray<AccessibilitySessionState> accessibilitySessions,
4878             @NonNull UserData userData) {
4879         // mEnabledAccessibilitySessions could the same object as accessibilitySessions.
4880         SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
4881         for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) {
4882             if (!accessibilitySessions.contains(userData.mEnabledAccessibilitySessions.keyAt(i))) {
4883                 AccessibilitySessionState sessionState =
4884                         userData.mEnabledAccessibilitySessions.valueAt(i);
4885                 if (sessionState != null) {
4886                     disabledSessions.append(userData.mEnabledAccessibilitySessions.keyAt(i),
4887                             sessionState.mSession);
4888                 }
4889             }
4890         }
4891         if (disabledSessions.size() > 0) {
4892             AccessibilityManagerInternal.get().setImeSessionEnabled(disabledSessions,
4893                     false);
4894         }
4895         SparseArray<IAccessibilityInputMethodSession> enabledSessions = new SparseArray<>();
4896         for (int i = 0; i < accessibilitySessions.size(); i++) {
4897             if (!userData.mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) {
4898                 AccessibilitySessionState sessionState = accessibilitySessions.valueAt(i);
4899                 if (sessionState != null) {
4900                     enabledSessions.append(accessibilitySessions.keyAt(i), sessionState.mSession);
4901                 }
4902             }
4903         }
4904         if (enabledSessions.size() > 0) {
4905             AccessibilityManagerInternal.get().setImeSessionEnabled(enabledSessions,
4906                     true);
4907         }
4908         userData.mEnabledAccessibilitySessions = accessibilitySessions;
4909     }
4910 
4911     @GuardedBy("ImfLock.class")
showInputMethodPickerLocked(int auxiliarySubtypeMode, int displayId, @UserIdInt int userId)4912     private void showInputMethodPickerLocked(int auxiliarySubtypeMode, int displayId,
4913             @UserIdInt int userId) {
4914         final boolean showAuxSubtypes;
4915         switch (auxiliarySubtypeMode) {
4916             // This is undocumented so far, but IMM#showInputMethodPicker() has been
4917             // implemented so that auxiliary subtypes will be excluded when the soft
4918             // keyboard is invisible.
4919             case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO -> {
4920                 final var userData = getUserData(userId);
4921                 final var visibilityStateComputer = userData.mVisibilityStateComputer;
4922                 showAuxSubtypes = visibilityStateComputer.isInputShown();
4923             }
4924             case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES ->
4925                     showAuxSubtypes = true;
4926             case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES ->
4927                     showAuxSubtypes = false;
4928             default -> {
4929                 Slog.e(TAG, "Unknown subtype picker mode=" + auxiliarySubtypeMode);
4930                 return;
4931             }
4932         }
4933         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
4934         final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
4935                 && mWindowManagerInternal.isKeyguardSecure(userId);
4936         final String lastInputMethodId = settings.getSelectedInputMethod();
4937         final int lastInputMethodSubtypeIndex =
4938                 settings.getSelectedInputMethodSubtypeIndex(lastInputMethodId);
4939 
4940         final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
4941                 .getSortedInputMethodAndSubtypeList(
4942                         showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
4943                         mContext, settings);
4944         if (imList.isEmpty()) {
4945             Slog.w(TAG, "Show switching menu failed, imList is empty,"
4946                     + " showAuxSubtypes: " + showAuxSubtypes
4947                     + " isScreenLocked: " + isScreenLocked
4948                     + " userId: " + userId);
4949             return;
4950         }
4951 
4952         if (Flags.imeSwitcherRevamp()) {
4953             ProtoLog.v(IMMS_DEBUG, "Show IME switcher menu,"
4954                             + " showAuxSubtypes=%s"
4955                             + " displayId=%s"
4956                             + " preferredInputMethodId=%s"
4957                             + " preferredInputMethodSubtypeIndex=%s",
4958                     showAuxSubtypes, displayId, lastInputMethodId, lastInputMethodSubtypeIndex);
4959 
4960             int selectedSubtypeIndex = lastInputMethodSubtypeIndex;
4961             if (selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX) {
4962                 // TODO(b/351124299): Check if this fallback logic is still necessary.
4963                 final var bindingController = getInputMethodBindingController(userId);
4964                 final var curSubtype = bindingController.getCurrentInputMethodSubtype();
4965                 if (curSubtype != null) {
4966                     final var curMethodId = bindingController.getSelectedMethodId();
4967                     final var curImi = settings.getMethodMap().get(curMethodId);
4968                     selectedSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(
4969                             curImi, curSubtype.hashCode());
4970                 }
4971             }
4972 
4973             mMenuControllerNew.show(imList, lastInputMethodId, selectedSubtypeIndex, isScreenLocked,
4974                     displayId, userId);
4975         } else {
4976             mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
4977                     lastInputMethodId, lastInputMethodSubtypeIndex, imList, userId);
4978         }
4979     }
4980 
4981     @SuppressWarnings("unchecked")
4982     @UiThread
4983     @Override
handleMessage(Message msg)4984     public boolean handleMessage(Message msg) {
4985         switch (msg.what) {
4986             case MSG_HIDE_INPUT_METHOD: {
4987                 @SoftInputShowHideReason final int reason = msg.arg1;
4988                 final int originatingDisplayId = msg.arg2;
4989                 synchronized (ImfLock.class) {
4990                     final int userId = resolveImeUserIdFromDisplayIdLocked(originatingDisplayId);
4991                     final var userData = getUserData(userId);
4992                     if (Flags.refactorInsetsController()) {
4993                         setImeVisibilityOnFocusedWindowClient(false, userData,
4994                                 null /* TODO(b/353463205) check statsToken */);
4995                     } else {
4996 
4997                         hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
4998                                 0 /* flags */, reason, userId);
4999                     }
5000                 }
5001                 return true;
5002             }
5003             case MSG_REMOVE_IME_SURFACE: {
5004                 synchronized (ImfLock.class) {
5005                     // TODO(b/305849394): Needs to figure out what to do where for background users.
5006                     final int userId = mCurrentImeUserId;
5007                     final var userData = getUserData(userId);
5008                     try {
5009                         if (userData.mEnabledSession != null
5010                                 && userData.mEnabledSession.mSession != null
5011                                 && !isShowRequestedForCurrentWindow(userId)) {
5012                             userData.mEnabledSession.mSession.removeImeSurface();
5013                         }
5014                     } catch (RemoteException e) {
5015                     }
5016                 }
5017                 return true;
5018             }
5019             case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: {
5020                 IBinder windowToken = (IBinder) msg.obj;
5021                 synchronized (ImfLock.class) {
5022                     final int userId = resolveImeUserIdFromWindowLocked(windowToken);
5023                     final var userData = getUserData(userId);
5024                     try {
5025                         if (windowToken == userData.mImeBindingState.mFocusedWindow
5026                                 && userData.mEnabledSession != null
5027                                 && userData.mEnabledSession.mSession != null) {
5028                             userData.mEnabledSession.mSession.removeImeSurface();
5029                         }
5030                     } catch (RemoteException e) {
5031                     }
5032                 }
5033                 return true;
5034             }
5035 
5036             // ---------------------------------------------------------
5037 
5038             case MSG_SET_INTERACTIVE:
5039                 handleSetInteractive(msg.arg1 != 0);
5040                 return true;
5041 
5042             // --------------------------------------------------------------
5043             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
5044                 if (!Flags.imeSwitcherRevamp()) {
5045                     mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
5046                 }
5047                 synchronized (ImfLock.class) {
5048                     sendOnNavButtonFlagsChangedToAllImesLocked();
5049                 }
5050                 return true;
5051             case MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED: {
5052                 final int userId = msg.arg1;
5053                 final List<InputMethodInfo> imes = (List<InputMethodInfo>) msg.obj;
5054                 mInputMethodListListeners.forEach(
5055                         listener -> listener.onInputMethodListUpdated(imes, userId));
5056                 return true;
5057             }
5058 
5059             // ---------------------------------------------------------------
5060             case MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE: {
5061                 if (mAudioManagerInternal == null) {
5062                     mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
5063                 }
5064                 if (mAudioManagerInternal != null) {
5065                     mAudioManagerInternal.setInputMethodServiceUid(msg.arg1 /* uid */);
5066                 }
5067                 return true;
5068             }
5069 
5070             case MSG_RESET_HANDWRITING: {
5071                 synchronized (ImfLock.class) {
5072                     final var bindingController =
5073                             getInputMethodBindingController(mCurrentImeUserId);
5074                     if (bindingController.supportsStylusHandwriting()
5075                             && bindingController.getCurMethod() != null
5076                             && hasSupportedStylusLocked()) {
5077                         Slog.d(TAG, "Initializing Handwriting Spy");
5078                         mHwController.initializeHandwritingSpy(
5079                                 bindingController.getCurTokenDisplayId());
5080                     } else {
5081                         mHwController.reset();
5082                     }
5083                 }
5084                 return true;
5085             }
5086             case MSG_PREPARE_HANDWRITING_DELEGATION:
5087                 synchronized (ImfLock.class) {
5088                     int userId = msg.arg1;
5089                     String delegate = (String) ((Pair) msg.obj).first;
5090                     String delegator = (String) ((Pair) msg.obj).second;
5091                     mHwController.prepareStylusHandwritingDelegation(
5092                             userId, delegate, delegator, /* connectionless= */ false);
5093                 }
5094                 return true;
5095             case MSG_START_HANDWRITING:
5096                 final var handwritingRequest = (HandwritingRequest) msg.obj;
5097                 synchronized (ImfLock.class) {
5098                     final var userData = handwritingRequest.userData;
5099                     final var bindingController = userData.mBindingController;
5100                     IInputMethodInvoker curMethod = bindingController.getCurMethod();
5101                     if (curMethod == null || userData.mImeBindingState.mFocusedWindow == null) {
5102                         return true;
5103                     }
5104                     final HandwritingModeController.HandwritingSession session =
5105                             mHwController.startHandwritingSession(
5106                                     handwritingRequest.requestId,
5107                                     handwritingRequest.pid,
5108                                     bindingController.getCurMethodUid(),
5109                                     userData.mImeBindingState.mFocusedWindow);
5110                     if (session == null) {
5111                         Slog.e(TAG,
5112                                 "Failed to start handwriting session for requestId: " + msg.arg1);
5113                         return true;
5114                     }
5115 
5116                     if (!curMethod.startStylusHandwriting(session.getRequestId(),
5117                             session.getHandwritingChannel(), session.getRecordedEvents())) {
5118                         // When failed to issue IPCs, re-initialize handwriting state.
5119                         Slog.w(TAG, "Resetting handwriting mode.");
5120                         scheduleResetStylusHandwriting();
5121                     }
5122                 }
5123                 return true;
5124             case MSG_FINISH_HANDWRITING:
5125                 synchronized (ImfLock.class) {
5126                     IInputMethodInvoker curMethod = getCurMethodLocked();
5127                     if (curMethod != null && mHwController.getCurrentRequestId().isPresent()) {
5128                         curMethod.finishStylusHandwriting();
5129                     }
5130                 }
5131                 return true;
5132             case MSG_REMOVE_HANDWRITING_WINDOW:
5133                 synchronized (ImfLock.class) {
5134                     IInputMethodInvoker curMethod = getCurMethodLocked();
5135                     if (curMethod != null) {
5136                         curMethod.removeStylusHandwritingWindow();
5137                     }
5138                 }
5139                 return true;
5140         }
5141         return false;
5142     }
5143 
HandwritingRequest(int requestId, int pid, @NonNull UserData userData)5144     private record HandwritingRequest(int requestId, int pid, @NonNull UserData userData) { }
5145 
5146     @BinderThread
5147     @GuardedBy("ImfLock.class")
onStylusHandwritingReadyLocked(int requestId, int pid, @NonNull UserData userData)5148     private void onStylusHandwritingReadyLocked(int requestId, int pid,
5149             @NonNull UserData userData) {
5150         mHandler.obtainMessage(MSG_START_HANDWRITING,
5151                 new HandwritingRequest(requestId, pid, userData)).sendToTarget();
5152     }
5153 
handleSetInteractive(final boolean interactive)5154     private void handleSetInteractive(final boolean interactive) {
5155         synchronized (ImfLock.class) {
5156             // TODO(b/305849394): Support multiple IMEs.
5157             final int userId = mCurrentImeUserId;
5158             final var userData = getUserData(userId);
5159             final var bindingController = userData.mBindingController;
5160             mIsInteractive = interactive;
5161             updateSystemUiLocked(
5162                     interactive ? bindingController.getImeWindowVis() : 0,
5163                     bindingController.getBackDisposition(), userId);
5164             // Inform the current client of the change in active status
5165             if (userData.mCurClient == null) {
5166                 return;
5167             }
5168             if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
5169                     bindingController.getCurMethodUid())) {
5170                 // Handle IME visibility when interactive changed before finishing the input to
5171                 // ensure we preserve the last state as possible.
5172                 final var visibilityStateComputer = userData.mVisibilityStateComputer;
5173                 final ImeVisibilityResult imeVisRes = visibilityStateComputer.onInteractiveChanged(
5174                         userData.mImeBindingState.mFocusedWindow, interactive);
5175                 if (imeVisRes != null) {
5176                     // Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker.
5177                     mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow,
5178                             null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason(),
5179                             userId);
5180                 }
5181                 // Eligible IME processes use new "setInteractive" protocol.
5182                 userData.mCurClient.mClient.setInteractive(mIsInteractive,
5183                         userData.mInFullscreenMode);
5184             } else {
5185                 // Legacy IME processes continue using legacy "setActive" protocol.
5186                 userData.mCurClient.mClient.setActive(mIsInteractive, userData.mInFullscreenMode);
5187             }
5188         }
5189     }
5190 
5191     @GuardedBy("ImfLock.class")
chooseNewDefaultIMELocked(@serIdInt int userId)5192     private boolean chooseNewDefaultIMELocked(@UserIdInt int userId) {
5193         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
5194         final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
5195                 settings.getEnabledInputMethodList());
5196         if (imi != null) {
5197             ProtoLog.v(IMMS_DEBUG, "New default IME was selected: %s", imi.getId());
5198             resetSelectedInputMethodAndSubtypeLocked(imi.getId(), userId);
5199             return true;
5200         }
5201 
5202         return false;
5203     }
5204 
5205     @NonNull
queryRawInputMethodServiceMap(Context context, @UserIdInt int userId)5206     static RawInputMethodMap queryRawInputMethodServiceMap(Context context, @UserIdInt int userId) {
5207         final Context userAwareContext = context.getUserId() == userId
5208                 ? context
5209                 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
5210 
5211         final int flags = PackageManager.GET_META_DATA
5212                 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
5213                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
5214                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
5215 
5216         // Beware that package visibility filtering will be enforced based on the effective calling
5217         // identity (Binder.getCallingUid()), but our use case always expect Binder.getCallingUid()
5218         // to return Process.SYSTEM_UID here. The actual filtering is implemented separately with
5219         // canCallerAccessInputMethod().
5220         // TODO(b/343108534): Use PackageManagerInternal#queryIntentServices() to pass SYSTEM_UID.
5221         final List<ResolveInfo> services = userAwareContext.getPackageManager().queryIntentServices(
5222                 new Intent(InputMethod.SERVICE_INTERFACE),
5223                 PackageManager.ResolveInfoFlags.of(flags));
5224 
5225         // Note: This is a temporary solution for Bug 261723412.
5226         // TODO(b/339761278): Remove this workaround after switching to InputMethodInfoSafeList.
5227         final List<String> enabledInputMethodList =
5228                 InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId);
5229 
5230         return filterInputMethodServices(enabledInputMethodList, userAwareContext, services);
5231     }
5232 
5233     @NonNull
filterInputMethodServices( List<String> enabledInputMethodList, Context userAwareContext, List<ResolveInfo> services)5234     static RawInputMethodMap filterInputMethodServices(
5235             List<String> enabledInputMethodList, Context userAwareContext,
5236             List<ResolveInfo> services) {
5237         final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>();
5238         final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(services.size());
5239 
5240         for (int i = 0; i < services.size(); ++i) {
5241             ResolveInfo ri = services.get(i);
5242             ServiceInfo si = ri.serviceInfo;
5243             final String imeId = InputMethodInfo.computeId(ri);
5244             if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
5245                 Slog.w(TAG, "Skipping input method " + imeId
5246                         + ": it does not require the permission "
5247                         + android.Manifest.permission.BIND_INPUT_METHOD);
5248                 continue;
5249             }
5250 
5251             ProtoLog.v(IMMS_DEBUG, "Checking %s", imeId);
5252 
5253             try {
5254                 final InputMethodInfo imi = new InputMethodInfo(userAwareContext, ri,
5255                         Collections.emptyList());
5256                 if (imi.isVrOnly()) {
5257                     continue;  // Skip VR-only IME, which isn't supported for now.
5258                 }
5259                 final String packageName = si.packageName;
5260                 // only include IMEs which are from the system, enabled, or below the threshold
5261                 if (si.applicationInfo.isSystemApp() || enabledInputMethodList.contains(imi.getId())
5262                         || imiPackageCount.getOrDefault(packageName, 0)
5263                         < InputMethodInfo.MAX_IMES_PER_PACKAGE) {
5264                     imiPackageCount.put(packageName,
5265                             1 + imiPackageCount.getOrDefault(packageName, 0));
5266 
5267                     methodMap.put(imi.getId(), imi);
5268                     ProtoLog.v(IMMS_DEBUG, "Found an input method %s", imi);
5269                 } else {
5270                     ProtoLog.v(IMMS_DEBUG, "Found an input method, but ignored due threshold: %s",
5271                             imi);
5272                 }
5273             } catch (Exception e) {
5274                 Slog.wtf(TAG, "Unable to load input method " + imeId, e);
5275             }
5276         }
5277         return RawInputMethodMap.of(methodMap);
5278     }
5279 
5280     @GuardedBy("ImfLock.class")
postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme, @UserIdInt int userId)5281     void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme,
5282             @UserIdInt int userId) {
5283         ProtoLog.v(IMMS_DEBUG, "--- re-buildInputMethodList reset = %s"
5284                     + " \n ------ caller=%s", resetDefaultEnabledIme, Debug.getCallers(10));
5285         if (!mSystemReady) {
5286             Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
5287             return;
5288         }
5289 
5290         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
5291 
5292         boolean reenableMinimumNonAuxSystemImes = false;
5293         // TODO: The following code should find better place to live.
5294         if (!resetDefaultEnabledIme) {
5295             boolean enabledImeFound = false;
5296             boolean enabledNonAuxImeFound = false;
5297             final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList();
5298             final int numImes = enabledImes.size();
5299             for (int i = 0; i < numImes; ++i) {
5300                 final InputMethodInfo imi = enabledImes.get(i);
5301                 if (settings.getMethodMap().containsKey(imi.getId())) {
5302                     enabledImeFound = true;
5303                     if (!imi.isAuxiliaryIme()) {
5304                         enabledNonAuxImeFound = true;
5305                         break;
5306                     }
5307                 }
5308             }
5309             if (!enabledImeFound) {
5310                 ProtoLog.v(IMMS_DEBUG,
5311                         "All the enabled IMEs are gone. Reset default enabled IMEs.");
5312                 resetDefaultEnabledIme = true;
5313                 resetSelectedInputMethodAndSubtypeLocked("", userId);
5314             } else if (!enabledNonAuxImeFound) {
5315                 ProtoLog.v(IMMS_DEBUG, "All the enabled non-Aux IMEs are gone. Do partial reset.");
5316                 reenableMinimumNonAuxSystemImes = true;
5317             }
5318         }
5319 
5320         if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
5321             final ArrayList<InputMethodInfo> defaultEnabledIme =
5322                     InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(),
5323                             reenableMinimumNonAuxSystemImes);
5324             final int numImes = defaultEnabledIme.size();
5325             for (int i = 0; i < numImes; ++i) {
5326                 final InputMethodInfo imi = defaultEnabledIme.get(i);
5327                 ProtoLog.v(IMMS_DEBUG, "--- enable ime = %s", imi);
5328                 setInputMethodEnabledLocked(imi.getId(), true, userId);
5329             }
5330         }
5331 
5332         final String defaultImiId = settings.getSelectedInputMethod();
5333         if (!TextUtils.isEmpty(defaultImiId)) {
5334             if (!settings.getMethodMap().containsKey(defaultImiId)) {
5335                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
5336                 if (chooseNewDefaultIMELocked(userId)) {
5337                     updateInputMethodsFromSettingsLocked(true, userId);
5338                 }
5339             } else {
5340                 // Double check that the default IME is certainly enabled.
5341                 setInputMethodEnabledLocked(defaultImiId, true, userId);
5342             }
5343         }
5344 
5345         updateDefaultVoiceImeIfNeededLocked(userId);
5346 
5347         final var userData = getUserData(userId);
5348         userData.mSwitchingController.resetCircularListLocked(mContext, settings);
5349         userData.mHardwareKeyboardShortcutController.update(settings);
5350 
5351         sendOnNavButtonFlagsChangedLocked(userData);
5352 
5353         // Notify InputMethodListListeners of the new installed InputMethods.
5354         final List<InputMethodInfo> inputMethodList = settings.getMethodList();
5355         mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
5356                 userId, 0 /* unused */, inputMethodList).sendToTarget();
5357     }
5358 
5359     @GuardedBy("ImfLock.class")
sendOnNavButtonFlagsChangedToAllImesLocked()5360     void sendOnNavButtonFlagsChangedToAllImesLocked() {
5361         for (int userId : mUserManagerInternal.getUserIds()) {
5362             sendOnNavButtonFlagsChangedLocked(getUserData(userId));
5363         }
5364     }
5365 
5366     @GuardedBy("ImfLock.class")
sendOnNavButtonFlagsChangedLocked(@onNull UserData userData)5367     void sendOnNavButtonFlagsChangedLocked(@NonNull UserData userData) {
5368         final var bindingController = userData.mBindingController;
5369         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
5370         if (curMethod == null) {
5371             // No need to send the data if the IME is not yet bound.
5372             return;
5373         }
5374         curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked(userData));
5375     }
5376 
5377     @WorkerThread
onUpdateResourceOverlay(@serIdInt int userId)5378     private void onUpdateResourceOverlay(@UserIdInt int userId) {
5379         final int profileParentId = mUserManagerInternal.getProfileParentId(userId);
5380         final boolean value =
5381                 InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId);
5382         final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false);
5383         final ArrayList<UserData> updatedUsers = new ArrayList<>();
5384         for (int profileUserId : profileUserIds) {
5385             final var userData = getUserData(profileUserId);
5386             userData.mImeDrawsNavBar.set(value);
5387             updatedUsers.add(userData);
5388         }
5389         synchronized (ImfLock.class) {
5390             updatedUsers.forEach(this::sendOnNavButtonFlagsChangedLocked);
5391         }
5392     }
5393 
5394     @GuardedBy("ImfLock.class")
updateDefaultVoiceImeIfNeededLocked(@serIdInt int userId)5395     private void updateDefaultVoiceImeIfNeededLocked(@UserIdInt int userId) {
5396         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
5397         final String systemSpeechRecognizer =
5398                 mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
5399         final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod();
5400         final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
5401                 settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
5402         if (newSystemVoiceIme == null) {
5403             ProtoLog.v(IMMS_DEBUG, "Found no valid default Voice IME. If the user is still locked,"
5404                     + " this may be expected.");
5405             // Clear DEFAULT_VOICE_INPUT_METHOD when necessary.  Note that InputMethodSettings
5406             // does not update the actual Secure Settings until the user is unlocked.
5407             if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
5408                 settings.putDefaultVoiceInputMethod("");
5409                 // We don't support disabling the voice ime when a package is removed from the
5410                 // config.
5411             }
5412             return;
5413         }
5414         if (TextUtils.equals(currentDefaultVoiceImeId, newSystemVoiceIme.getId())) {
5415             return;
5416         }
5417         ProtoLog.v(IMMS_DEBUG, "Enabling the default Voice IME: %s userId: %s", newSystemVoiceIme,
5418                 userId);
5419         setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true, userId);
5420         settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
5421     }
5422 
5423     // ----------------------------------------------------------------------
5424 
5425     /**
5426      * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}.
5427      *
5428      * @param id      ID of the IME is to be manipulated. It is OK to pass IME ID that is currently
5429      *                not recognized by the system
5430      * @param enabled {@code true} if {@code id} needs to be enabled
5431      * @param userId  the user ID to be updated
5432      * @return {@code true} if the IME was previously enabled
5433      */
5434     @GuardedBy("ImfLock.class")
setInputMethodEnabledLocked(String id, boolean enabled, @UserIdInt int userId)5435     private boolean setInputMethodEnabledLocked(String id, boolean enabled, @UserIdInt int userId) {
5436         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
5437         if (enabled) {
5438             final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
5439             final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
5440                     enabledImeIdsStr, id);
5441             if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
5442                 // We are enabling this input method, but it is already enabled.
5443                 // Nothing to do. The previous state was enabled.
5444                 return true;
5445             }
5446             settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
5447             // Previous state was disabled.
5448             return false;
5449         } else {
5450             final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings
5451                     .getEnabledInputMethodsAndSubtypeList();
5452             StringBuilder builder = new StringBuilder();
5453             if (settings.buildAndPutEnabledInputMethodsStrRemovingId(
5454                     builder, enabledInputMethodsList, id)) {
5455                 final var bindingController = getInputMethodBindingController(userId);
5456                 if (bindingController.getDeviceIdToShowIme() == DEVICE_ID_DEFAULT) {
5457                     // Disabled input method is currently selected, switch to another one.
5458                     final String selId = settings.getSelectedInputMethod();
5459                     if (id.equals(selId) && !chooseNewDefaultIMELocked(userId)) {
5460                         Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
5461                         resetSelectedInputMethodAndSubtypeLocked("", userId);
5462                     }
5463                 } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) {
5464                     // Disabled default device IME while using a virtual device one, choose a
5465                     // new default one but only update the settings.
5466                     InputMethodInfo newDefaultIme =
5467                             InputMethodInfoUtils.getMostApplicableDefaultIME(
5468                                     settings.getEnabledInputMethodList());
5469                     settings.putSelectedDefaultDeviceInputMethod(
5470                             newDefaultIme == null ? null : newDefaultIme.getId());
5471                 }
5472                 // Previous state was enabled.
5473                 return true;
5474             } else {
5475                 // We are disabling the input method but it is already disabled.
5476                 // Nothing to do.  The previous state was disabled.
5477                 return false;
5478             }
5479         }
5480     }
5481 
5482     @GuardedBy("ImfLock.class")
setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeIndex, boolean setSubtypeOnly, @UserIdInt int userId)5483     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeIndex,
5484             boolean setSubtypeOnly, @UserIdInt int userId) {
5485         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
5486         final var bindingController = getInputMethodBindingController(userId);
5487         settings.saveCurrentInputMethodAndSubtypeToHistory(bindingController.getSelectedMethodId(),
5488                 bindingController.getCurrentSubtype());
5489 
5490         // Set Subtype here
5491         final int newSubtypeHashcode;
5492         final InputMethodSubtype newSubtype;
5493         if (imi == null || subtypeIndex < 0) {
5494             newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
5495             newSubtype = null;
5496         } else {
5497             if (subtypeIndex < imi.getSubtypeCount()) {
5498                 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeIndex);
5499                 newSubtypeHashcode = subtype.hashCode();
5500                 newSubtype = subtype;
5501             } else {
5502                 // TODO(b/347093491): Probably this should be determined from the new subtype.
5503                 newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
5504                 // If the subtype is not specified, choose the most applicable one
5505                 // TODO(b/347083680): The method below has questionable behaviors.
5506                 newSubtype = bindingController.getCurrentInputMethodSubtype();
5507             }
5508         }
5509         settings.putSelectedSubtype(newSubtypeHashcode);
5510         bindingController.setCurrentSubtype(newSubtype);
5511         notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, newSubtype);
5512 
5513         if (!setSubtypeOnly) {
5514             // Set InputMethod here
5515             settings.putSelectedInputMethod(imi != null ? imi.getId() : "");
5516         }
5517 
5518         if (Flags.imeSwitcherRevamp()) {
5519             getUserData(userId).mSwitchingController.onInputMethodSubtypeChanged();
5520         }
5521     }
5522 
5523     @GuardedBy("ImfLock.class")
resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme, @UserIdInt int userId)5524     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme,
5525             @UserIdInt int userId) {
5526         final var bindingController = getInputMethodBindingController(userId);
5527         bindingController.setDisplayIdToShowIme(INVALID_DISPLAY);
5528         bindingController.setDeviceIdToShowIme(DEVICE_ID_DEFAULT);
5529 
5530         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
5531         settings.putSelectedDefaultDeviceInputMethod(null);
5532 
5533         InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
5534         int lastSubtypeIndex = NOT_A_SUBTYPE_INDEX;
5535         // newDefaultIme is empty when there is no candidate for the selected IME.
5536         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
5537             String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
5538             if (subtypeHashCode != null) {
5539                 try {
5540                     lastSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi,
5541                             Integer.parseInt(subtypeHashCode));
5542                 } catch (NumberFormatException e) {
5543                     Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
5544                 }
5545             }
5546         }
5547         setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeIndex, false, userId);
5548     }
5549 
5550     /**
5551      * Gets the current subtype of this input method.
5552      *
5553      * @param userId User ID to be queried about
5554      * @return the current {@link InputMethodSubtype} for the specified user
5555      */
5556     @Nullable
5557     @Override
getCurrentInputMethodSubtype(@serIdInt int userId)5558     public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
5559         if (UserHandle.getCallingUserId() != userId) {
5560             mContext.enforceCallingOrSelfPermission(
5561                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
5562         }
5563         synchronized (ImfLock.class) {
5564             final var bindingController = getInputMethodBindingController(userId);
5565             // TODO(b/347083680): The method below has questionable behaviors.
5566             return bindingController.getCurrentInputMethodSubtype();
5567         }
5568     }
5569 
5570     @GuardedBy("ImfLock.class")
switchToInputMethodLocked(@onNull String imeId, int subtypeIndex, @UserIdInt int userId)5571     private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeIndex,
5572             @UserIdInt int userId) {
5573         final var settings = InputMethodSettingsRepository.get(userId);
5574         final var enabledImes = settings.getEnabledInputMethodList();
5575         if (!CollectionUtils.any(enabledImes, imi -> imi.getId().equals(imeId))) {
5576             return false; // IME is not found or not enabled.
5577         }
5578         setInputMethodLocked(imeId, subtypeIndex, userId);
5579         return true;
5580     }
5581 
5582     /**
5583      * Filter the access to the input method by rules of the package visibility. Return {@code true}
5584      * if the given input method is the currently selected one or visible to the caller.
5585      *
5586      * @param targetPkgName the package name of input method to check
5587      * @param callingUid    the caller that is going to access the input method
5588      * @param userId        the user ID where the input method resides
5589      * @param settings      the input method settings under the given user ID
5590      * @return {@code true} if caller is able to access the input method
5591      */
canCallerAccessInputMethod(@onNull String targetPkgName, int callingUid, @UserIdInt int userId, @NonNull InputMethodSettings settings)5592     private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid,
5593             @UserIdInt int userId, @NonNull InputMethodSettings settings) {
5594         final String methodId = settings.getSelectedInputMethod();
5595         final ComponentName selectedInputMethod = methodId != null
5596                 ? InputMethodUtils.convertIdToComponentName(methodId) : null;
5597         if (selectedInputMethod != null
5598                 && selectedInputMethod.getPackageName().equals(targetPkgName)) {
5599             return true;
5600         }
5601         final boolean canAccess = !mPackageManagerInternal.filterAppAccess(
5602                 targetPkgName, callingUid, userId);
5603         if (DEBUG && !canAccess) {
5604             Slog.d(TAG, "Input method " + targetPkgName
5605                     + " is not visible to the caller " + callingUid);
5606         }
5607         return canAccess;
5608     }
5609 
5610     @GuardedBy("ImfLock.class")
switchKeyboardLayoutLocked(int direction, @NonNull UserData userData)5611     private void switchKeyboardLayoutLocked(int direction, @NonNull UserData userData) {
5612         final int userId = userData.mUserId;
5613         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
5614 
5615         final var bindingController = userData.mBindingController;
5616         final InputMethodInfo currentImi = settings.getMethodMap().get(
5617                 bindingController.getSelectedMethodId());
5618         if (currentImi == null) {
5619             return;
5620         }
5621         final var currentSubtype = bindingController.getCurrentSubtype();
5622         final InputMethodSubtypeHandle nextSubtypeHandle;
5623         if (Flags.imeSwitcherRevamp()) {
5624             final var nextItem = userData.mSwitchingController
5625                     .getNextInputMethodForHardware(
5626                             false /* onlyCurrentIme */, currentImi, currentSubtype, MODE_AUTO,
5627                             direction > 0 /* forward */);
5628             if (nextItem == null) {
5629                 Slog.i(TAG, "Hardware keyboard switching shortcut,"
5630                         + " next input method and subtype not found");
5631                 return;
5632             }
5633 
5634             final var nextSubtype = nextItem.mSubtypeIndex > NOT_A_SUBTYPE_INDEX
5635                     ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeIndex) : null;
5636             nextSubtypeHandle = InputMethodSubtypeHandle.of(nextItem.mImi, nextSubtype);
5637         } else {
5638             final InputMethodSubtypeHandle currentSubtypeHandle =
5639                     InputMethodSubtypeHandle.of(currentImi, currentSubtype);
5640             nextSubtypeHandle = userData.mHardwareKeyboardShortcutController.onSubtypeSwitch(
5641                         currentSubtypeHandle, direction > 0);
5642         }
5643         if (nextSubtypeHandle == null) {
5644             return;
5645         }
5646         final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId());
5647         if (nextImi == null) {
5648             return;
5649         }
5650 
5651         final int subtypeCount = nextImi.getSubtypeCount();
5652         if (subtypeCount == 0) {
5653             if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
5654                 setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_INDEX, userId);
5655             }
5656             return;
5657         }
5658 
5659         for (int i = 0; i < subtypeCount; ++i) {
5660             if (nextSubtypeHandle.equals(
5661                     InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
5662                 setInputMethodLocked(nextImi.getId(), i, userId);
5663                 return;
5664             }
5665         }
5666     }
5667 
publishLocalService()5668     private void publishLocalService() {
5669         LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal);
5670     }
5671 
5672     private final class LocalServiceImpl extends InputMethodManagerInternal {
5673 
5674         @ImfLockFree
5675         @Override
setInteractive(boolean interactive)5676         public void setInteractive(boolean interactive) {
5677             // Do everything in handler so as not to block the caller.
5678             mHandler.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, 0).sendToTarget();
5679         }
5680 
5681         @ImfLockFree
5682         @Override
hideInputMethod(@oftInputShowHideReason int reason, int originatingDisplayId)5683         public void hideInputMethod(@SoftInputShowHideReason int reason,
5684                 int originatingDisplayId) {
5685             mHandler.removeMessages(MSG_HIDE_INPUT_METHOD);
5686             mHandler.obtainMessage(MSG_HIDE_INPUT_METHOD, reason, originatingDisplayId)
5687                     .sendToTarget();
5688         }
5689 
5690         @ImfLockFree
5691         @NonNull
5692         @Override
getInputMethodListAsUser(@serIdInt int userId)5693         public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
5694             return getInputMethodListInternal(userId, DirectBootAwareness.AUTO, Process.SYSTEM_UID);
5695         }
5696 
5697         @ImfLockFree
5698         @NonNull
5699         @Override
getEnabledInputMethodListAsUser(@serIdInt int userId)5700         public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
5701             return getEnabledInputMethodListInternal(userId, Process.SYSTEM_UID);
5702         }
5703 
5704         @ImfLockFree
5705         @NonNull
5706         @Override
getEnabledInputMethodSubtypeListAsUser( String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId)5707         public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
5708                 String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
5709             return getEnabledInputMethodSubtypeListInternal(imiId, allowsImplicitlyEnabledSubtypes,
5710                     userId, Process.SYSTEM_UID);
5711         }
5712 
5713         @Override
onCreateInlineSuggestionsRequest(@serIdInt int userId, InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb)5714         public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
5715                 InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb) {
5716             // Get the device global touch exploration state before lock to avoid deadlock.
5717             final boolean touchExplorationEnabled = AccessibilityManagerInternal.get()
5718                     .isTouchExplorationEnabled(userId);
5719 
5720             synchronized (ImfLock.class) {
5721                 getInputMethodBindingController(userId).onCreateInlineSuggestionsRequest(
5722                         requestInfo, cb, touchExplorationEnabled);
5723             }
5724         }
5725 
5726         @Override
switchToInputMethod(@onNull String imeId, int subtypeIndex, @UserIdInt int userId)5727         public boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
5728                 @UserIdInt int userId) {
5729             synchronized (ImfLock.class) {
5730                 return switchToInputMethodLocked(imeId, subtypeIndex, userId);
5731             }
5732         }
5733 
5734         @Override
setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId)5735         public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
5736             synchronized (ImfLock.class) {
5737                 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
5738                 if (!settings.getMethodMap().containsKey(imeId)) {
5739                     return false; // IME is not found.
5740                 }
5741                 setInputMethodEnabledLocked(imeId, enabled, userId);
5742                 return true;
5743             }
5744         }
5745 
5746         @Override
setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId)5747         public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) {
5748             Preconditions.checkArgument(deviceId != DEVICE_ID_DEFAULT,
5749                     TextUtils.formatSimple("DeviceId %d is not a virtual device id.", deviceId));
5750             synchronized (ImfLock.class) {
5751                 if (imeId == null) {
5752                     mVirtualDeviceMethodMap.remove(deviceId);
5753                 } else if (mVirtualDeviceMethodMap.contains(deviceId)) {
5754                     throw new IllegalArgumentException("Virtual device " + deviceId
5755                             + " already has a custom input method component");
5756                 } else {
5757                     mVirtualDeviceMethodMap.put(deviceId, imeId);
5758                 }
5759             }
5760         }
5761 
5762         @ImfLockFree
5763         @Override
registerInputMethodListListener(InputMethodListListener listener)5764         public void registerInputMethodListListener(InputMethodListListener listener) {
5765             mInputMethodListListeners.addIfAbsent(listener);
5766         }
5767 
5768         @Override
transferTouchFocusToImeWindow(@onNull IBinder sourceInputToken, int displayId, @UserIdInt int userId)5769         public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
5770                 int displayId, @UserIdInt int userId) {
5771             //TODO(b/150843766): Check if Input Token is valid.
5772             final IBinder curHostInputToken;
5773             synchronized (ImfLock.class) {
5774                 final var bindingController = getInputMethodBindingController(userId);
5775                 if (displayId != bindingController.getCurTokenDisplayId()) {
5776                     return false;
5777                 }
5778                 curHostInputToken = bindingController.getCurHostInputToken();
5779                 if (curHostInputToken == null) {
5780                     return false;
5781                 }
5782             }
5783             return mInputManagerInternal.transferTouchGesture(
5784                     sourceInputToken, curHostInputToken, /* transferEntireGesture */ false);
5785         }
5786 
5787         @Override
reportImeControl(@ullable IBinder windowToken)5788         public void reportImeControl(@Nullable IBinder windowToken) {
5789             synchronized (ImfLock.class) {
5790                 final int userId = resolveImeUserIdFromWindowLocked(windowToken);
5791                 final var userData = getUserData(userId);
5792                 if (userData.mImeBindingState.mFocusedWindow != windowToken) {
5793                     // A perceptible value was set for the focused window, but it is no longer in
5794                     // control, so we reset the perceptible for the window passed as argument.
5795                     // TODO(b/314149476): Investigate whether this logic is still relevant, if not
5796                     //     then consider removing using concurrent_input_methods feature flag.
5797                     mFocusedWindowPerceptible.put(windowToken, true);
5798                 }
5799             }
5800         }
5801 
5802         @Override
onImeParentChanged(int displayId)5803         public void onImeParentChanged(int displayId) {
5804             synchronized (ImfLock.class) {
5805                 final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
5806                 final var userData = getUserData(userId);
5807                 // Hide the IME method menu only when the IME surface parent is changed by the
5808                 // input target changed, in case seeing the dialog dismiss flickering during
5809                 // the next focused window starting the input connection.
5810                 final var visibilityStateComputer = userData.mVisibilityStateComputer;
5811                 if (visibilityStateComputer.getLastImeTargetWindow()
5812                         != userData.mImeBindingState.mFocusedWindow) {
5813                     if (Flags.imeSwitcherRevamp()) {
5814                         final var bindingController = getInputMethodBindingController(userId);
5815                         mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
5816                     } else {
5817                         mMenuController.hideInputMethodMenuLocked(userId);
5818                     }
5819                 }
5820             }
5821         }
5822 
5823         @ImfLockFree
5824         @Override
removeImeSurface(int displayId)5825         public void removeImeSurface(int displayId) {
5826             mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
5827         }
5828 
5829         @Override
setHasVisibleImeLayeringOverlay(boolean hasVisibleOverlay, int displayId)5830         public void setHasVisibleImeLayeringOverlay(boolean hasVisibleOverlay, int displayId) {
5831             synchronized (ImfLock.class) {
5832                 final var userId = resolveImeUserIdFromDisplayIdLocked(displayId);
5833                 getUserData(userId).mVisibilityStateComputer.setHasVisibleImeLayeringOverlay(
5834                         hasVisibleOverlay);
5835             }
5836         }
5837 
5838         @Override
onImeInputTargetVisibilityChanged(@onNull IBinder imeInputTarget, boolean visibleAndNotRemoved, int displayId)5839         public void onImeInputTargetVisibilityChanged(@NonNull IBinder imeInputTarget,
5840                 boolean visibleAndNotRemoved, int displayId) {
5841             synchronized (ImfLock.class) {
5842                 final var userId = resolveImeUserIdFromDisplayIdLocked(displayId);
5843                 getUserData(userId).mVisibilityStateComputer.onImeInputTargetVisibilityChanged(
5844                         imeInputTarget, visibleAndNotRemoved);
5845             }
5846         }
5847 
5848         @ImfLockFree
5849         @Override
updateImeWindowStatus(boolean disableImeIcon, int displayId)5850         public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
5851             mHandler.post(() -> {
5852                 synchronized (ImfLock.class) {
5853                     updateImeWindowStatusLocked(disableImeIcon, displayId);
5854                 }
5855             });
5856         }
5857 
5858         @Override
updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId)5859         public void updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId) {
5860             synchronized (ImfLock.class) {
5861                 updateSystemUiLocked(userId);
5862                 final var userData = getUserData(userId);
5863                 sendOnNavButtonFlagsChangedLocked(userData);
5864             }
5865         }
5866 
5867         @Override
onSessionForAccessibilityCreated(int accessibilityConnectionId, IAccessibilityInputMethodSession session, @UserIdInt int userId)5868         public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
5869                 IAccessibilityInputMethodSession session, @UserIdInt int userId) {
5870             synchronized (ImfLock.class) {
5871                 final var userData = getUserData(userId);
5872                 final var bindingController = userData.mBindingController;
5873                 // TODO(b/305829876): Implement user ID verification
5874                 if (userData.mCurClient != null) {
5875                     clearClientSessionForAccessibilityLocked(userData.mCurClient,
5876                             accessibilityConnectionId);
5877                     userData.mCurClient.mAccessibilitySessions.put(
5878                             accessibilityConnectionId,
5879                             new AccessibilitySessionState(userData.mCurClient,
5880                                     accessibilityConnectionId,
5881                                     session));
5882 
5883                     attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY,
5884                             true, userId);
5885 
5886                     final SessionState sessionState = userData.mCurClient.mCurSession;
5887                     final IInputMethodSession imeSession = sessionState == null
5888                             ? null : sessionState.mSession;
5889                     final SparseArray<IAccessibilityInputMethodSession>
5890                             accessibilityInputMethodSessions =
5891                             createAccessibilityInputMethodSessions(
5892                                     userData.mCurClient.mAccessibilitySessions);
5893                     final InputBindResult res = new InputBindResult(
5894                             InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION,
5895                             imeSession, accessibilityInputMethodSessions, /* channel= */ null,
5896                             bindingController.getCurId(),
5897                             bindingController.getSequenceNumber(),
5898                             /* isInputMethodSuppressingSpellChecker= */ false);
5899                     userData.mCurClient.mClient.onBindAccessibilityService(res,
5900                             accessibilityConnectionId);
5901                 }
5902             }
5903         }
5904 
5905         @Override
unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, @UserIdInt int userId)5906         public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
5907                 @UserIdInt int userId) {
5908             synchronized (ImfLock.class) {
5909                 final var userData = getUserData(userId);
5910                 final var bindingController = userData.mBindingController;
5911                 // TODO(b/305829876): Implement user ID verification
5912                 if (userData.mCurClient != null) {
5913                     ProtoLog.v(IMMS_DEBUG, "unbindAccessibilityFromCurrentClientLocked: client=%s",
5914                             userData.mCurClient.mClient.asBinder());
5915                     // A11yManagerService unbinds the disabled accessibility service. We don't need
5916                     // to do it here.
5917                     userData.mCurClient.mClient.onUnbindAccessibilityService(
5918                             bindingController.getSequenceNumber(),
5919                             accessibilityConnectionId);
5920                 }
5921                 // We only have sessions when we bound to an input method. Remove this session
5922                 // from all clients.
5923                 if (bindingController.getCurMethod() != null) {
5924                     // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
5925                     @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c ->
5926                             clearClientSessionForAccessibilityLocked(c, accessibilityConnectionId);
5927                     mClientController.forAllClients(clearClientSession);
5928 
5929                     AccessibilitySessionState session = userData.mEnabledAccessibilitySessions.get(
5930                             accessibilityConnectionId);
5931                     if (session != null) {
5932                         finishSessionForAccessibilityLocked(session);
5933                         userData.mEnabledAccessibilitySessions.remove(accessibilityConnectionId);
5934                     }
5935                 }
5936             }
5937         }
5938 
5939         @ImfLockFree
5940         @Override
maybeFinishStylusHandwriting()5941         public void maybeFinishStylusHandwriting() {
5942             mHandler.removeMessages(MSG_FINISH_HANDWRITING);
5943             mHandler.obtainMessage(MSG_FINISH_HANDWRITING).sendToTarget();
5944         }
5945 
5946         @Override
onSwitchKeyboardLayoutShortcut(int direction, int displayId, IBinder targetWindowToken)5947         public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
5948                 IBinder targetWindowToken) {
5949             synchronized (ImfLock.class) {
5950                 final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
5951                 switchKeyboardLayoutLocked(direction, getUserData(userId));
5952             }
5953         }
5954     }
5955 
5956     @BinderThread
5957     @GuardedBy("ImfLock.class")
5958     @Nullable
createInputContentUriTokenLocked(@onNull Uri contentUri, @NonNull String packageName, @NonNull UserData userData)5959     private IInputContentUriToken createInputContentUriTokenLocked(@NonNull Uri contentUri,
5960             @NonNull String packageName, @NonNull UserData userData) {
5961         Objects.requireNonNull(packageName, "packageName must not be null");
5962         Objects.requireNonNull(contentUri, "contentUri must not be null");
5963         final String contentUriScheme = contentUri.getScheme();
5964         if (!"content".equals(contentUriScheme)) {
5965             throw new InvalidParameterException("contentUri must have content scheme");
5966         }
5967 
5968         final int uid = Binder.getCallingUid();
5969         final var bindingController = userData.mBindingController;
5970         if (bindingController.getSelectedMethodId() == null) {
5971             return null;
5972         }
5973         // We cannot simply distinguish a bad IME that reports an arbitrary package name from
5974         // an unfortunate IME whose internal state is already obsolete due to the asynchronous
5975         // nature of our system.  Let's compare it with our internal record.
5976         final var curPackageName = userData.mCurEditorInfo != null
5977                 ? userData.mCurEditorInfo.packageName : null;
5978         if (!TextUtils.equals(curPackageName, packageName)) {
5979             Slog.e(TAG, "Ignoring createInputContentUriTokenLocked mCurEditorInfo.packageName="
5980                     + curPackageName + " packageName=" + packageName);
5981             return null;
5982         }
5983         // This user ID can never be spoofed.
5984         final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid);
5985         // This user ID may be invalid if "contentUri" embedded an invalid user ID.
5986         final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
5987                 userData.mUserId);
5988         final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
5989         // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
5990         // actually has the right to grant a read permission for "contentUriWithoutUserId" that
5991         // is claimed to belong to "contentUriOwnerUserId".  For example, specifying random
5992         // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
5993         // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
5994         // actually allowed to "uid", which is guaranteed to be the IME's one.
5995         return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
5996                 packageName, contentUriOwnerUserId, appUserId);
5997     }
5998 
5999     @BinderThread
6000     @GuardedBy("ImfLock.class")
reportFullscreenModeLocked(boolean fullscreen, @NonNull UserData userData)6001     private void reportFullscreenModeLocked(boolean fullscreen, @NonNull UserData userData) {
6002         if (userData.mCurClient != null) {
6003             userData.mInFullscreenMode = fullscreen;
6004             userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
6005         }
6006     }
6007 
6008     private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
6009         /**
6010          * {@inheritDoc}
6011          */
6012         @BinderThread
6013         @Override
6014         public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
6015                 boolean asProto) {
6016             if (asProto) {
6017                 dumpAsProtoNoCheck(fd);
6018             } else {
6019                 dumpAsStringNoCheck(fd, pw, args, true /* isCritical */);
6020             }
6021         }
6022 
6023         /**
6024          * {@inheritDoc}
6025          */
6026         @BinderThread
6027         @Override
6028         public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
6029             dumpNormal(fd, pw, args, asProto);
6030         }
6031 
6032         /**
6033          * {@inheritDoc}
6034          */
6035         @BinderThread
6036         @Override
6037         public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
6038             if (asProto) {
6039                 dumpAsProtoNoCheck(fd);
6040             } else {
6041                 dumpAsStringNoCheck(fd, pw, args, false /* isCritical */);
6042             }
6043         }
6044 
6045         /**
6046          * {@inheritDoc}
6047          */
6048         @BinderThread
6049         @Override
6050         public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
6051             dumpNormal(fd, pw, args, asProto);
6052         }
6053 
6054         @BinderThread
6055         private void dumpAsProtoNoCheck(FileDescriptor fd) {
6056             final ProtoOutputStream proto = new ProtoOutputStream(fd);
6057             // Dump in the format of an ImeTracing trace with a single entry.
6058             final long magicNumber =
6059                     ((long) InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER_H << 32)
6060                             | InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER_L;
6061             final long timeOffsetNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
6062                     - SystemClock.elapsedRealtimeNanos();
6063             proto.write(InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER,
6064                     magicNumber);
6065             proto.write(InputMethodManagerServiceTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS,
6066                     timeOffsetNs);
6067             final long token = proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
6068             proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
6069                     SystemClock.elapsedRealtimeNanos());
6070             proto.write(InputMethodManagerServiceTraceProto.WHERE,
6071                     "InputMethodManagerService.mPriorityDumper#dumpAsProtoNoCheck");
6072             dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
6073             proto.end(token);
6074             proto.flush();
6075         }
6076     };
6077 
6078     @BinderThread
6079     @Override
dump(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args)6080     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) {
6081         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
6082 
6083         PriorityDump.dump(mPriorityDumper, fd, pw, args);
6084     }
6085 
6086     @BinderThread
dumpAsStringNoCheck(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args, boolean isCritical)6087     private void dumpAsStringNoCheck(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
6088             @NonNull String[] args, boolean isCritical) {
6089         final int argUserId = parseUserIdFromDumpArgs(args);
6090         final Printer p = new PrintWriterPrinter(pw);
6091         p.println("Input Method Manager Service state:");
6092         p.println("  mSystemReady=" + mSystemReady);
6093         p.println("  mInteractive=" + mIsInteractive);
6094         p.println("  mConcurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled);
6095         final int currentImeUserId;
6096         synchronized (ImfLock.class) {
6097             currentImeUserId = mCurrentImeUserId;
6098             p.println("  mCurrentImeUserId=" + currentImeUserId);
6099             p.println("  mStylusIds=" + (mStylusIds != null
6100                     ? Arrays.toString(mStylusIds.toArray()) : ""));
6101         }
6102         // TODO(b/305849394): Make mMenuController multi-user aware.
6103         if (Flags.imeSwitcherRevamp()) {
6104             p.println("  mMenuControllerNew:");
6105             mMenuControllerNew.dump(p, "    ");
6106         } else {
6107             p.println("  mMenuController:");
6108             mMenuController.dump(p, "    ");
6109         }
6110         dumpClientController(p);
6111         dumpUserRepository(p);
6112 
6113         // TODO(b/365868861): Make StartInputHistory and ImeTracker multi-user aware.
6114         synchronized (ImfLock.class) {
6115             p.println("  mStartInputHistory:");
6116             mStartInputHistory.dump(pw, "    ");
6117 
6118             p.println("  mSoftInputShowHideHistory:");
6119             mSoftInputShowHideHistory.dump(pw, "    ");
6120         }
6121 
6122         p.println("  mImeTrackerService#History:");
6123         mImeTrackerService.dump(pw, "    ");
6124 
6125         if (mConcurrentMultiUserModeEnabled && argUserId == UserHandle.USER_NULL) {
6126             mUserDataRepository.forAllUserData(
6127                     u -> dumpAsStringNoCheckForUser(u, fd, pw, args, isCritical));
6128         } else {
6129             final int userId = argUserId != UserHandle.USER_NULL ? argUserId : currentImeUserId;
6130             final var userData = getUserData(userId);
6131             dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical);
6132         }
6133     }
6134 
6135     @UserIdInt
parseUserIdFromDumpArgs(@onNull String[] args)6136     private static int parseUserIdFromDumpArgs(@NonNull String[] args) {
6137         final int userIdx = Arrays.binarySearch(args, "--user");
6138         if (userIdx == -1 || userIdx == args.length - 1) {
6139             return UserHandle.USER_NULL;
6140         }
6141         return Integer.parseInt(args[userIdx + 1]);
6142     }
6143 
6144     // TODO(b/356239178): Update dump format output to better group per-user info.
6145     @BinderThread
dumpAsStringNoCheckForUser(@onNull UserData userData, @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args, boolean isCritical)6146     private void dumpAsStringNoCheckForUser(@NonNull UserData userData, @NonNull FileDescriptor fd,
6147             @NonNull PrintWriter pw, @NonNull String[] args, boolean isCritical) {
6148         final Printer p = new PrintWriterPrinter(pw);
6149         ClientState client;
6150         IInputMethodInvoker method;
6151         p.println("  UserId=" + userData.mUserId);
6152         synchronized (ImfLock.class) {
6153             final var bindingController = userData.mBindingController;
6154             client = userData.mCurClient;
6155             method = bindingController.getCurMethod();
6156             p.println("    mBindingController:");
6157             bindingController.dump(pw, "      ");
6158             p.println("    mCurClient=" + client);
6159             p.println("    mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
6160             p.println("    mImeBindingState:");
6161             userData.mImeBindingState.dump(p, "      ");
6162             p.println("    mBoundToMethod=" + userData.mBoundToMethod);
6163             p.println("    mEnabledSession=" + userData.mEnabledSession);
6164             p.println("    mVisibilityStateComputer:");
6165             userData.mVisibilityStateComputer.dump(pw, "      ");
6166             p.println("    mInFullscreenMode=" + userData.mInFullscreenMode);
6167 
6168             final var settings = InputMethodSettingsRepository.get(userData.mUserId);
6169             final List<InputMethodInfo> methodList = settings.getMethodList();
6170             final int numImes = methodList.size();
6171             p.println("    Input Methods:");
6172             for (int i = 0; i < numImes; i++) {
6173                 final InputMethodInfo info = methodList.get(i);
6174                 p.println("      InputMethod #" + i + ":");
6175                 info.dump(p, "        ");
6176             }
6177         }
6178 
6179         // Exit here for critical dump, as remaining sections require IPCs to other processes.
6180         if (isCritical) {
6181             return;
6182         }
6183 
6184         p.println("");
6185         if (client != null) {
6186             pw.flush();
6187             try {
6188                 TransferPipe.dumpAsync(client.mClient.asBinder(), fd, args);
6189             } catch (IOException | RemoteException e) {
6190                 p.println("Failed to dump input method client: " + e);
6191             }
6192         } else {
6193             p.println("No input method client.");
6194         }
6195         synchronized (ImfLock.class) {
6196             final var focusedWindowClient = userData.mImeBindingState.mFocusedWindowClient;
6197             if (focusedWindowClient != null && client != focusedWindowClient) {
6198                 p.println("");
6199                 p.println("Warning: Current input method client doesn't match the last focused"
6200                         + " window.");
6201                 p.println("Dumping input method client in the last focused window just in case.");
6202                 p.println("");
6203                 pw.flush();
6204                 try {
6205                     TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args);
6206                 } catch (IOException | RemoteException e) {
6207                     p.println("Failed to dump input method client in focused window: " + e);
6208                 }
6209             }
6210         }
6211 
6212         p.println("");
6213         if (method != null) {
6214             pw.flush();
6215             try {
6216                 TransferPipe.dumpAsync(method.asBinder(), fd, args);
6217             } catch (IOException | RemoteException e) {
6218                 p.println("Failed to dump input method service: " + e);
6219             }
6220         } else {
6221             p.println("No input method service.");
6222         }
6223     }
6224 
dumpClientController(@onNull Printer p)6225     private void dumpClientController(@NonNull Printer p) {
6226         p.println("  mClientController:");
6227         // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
6228         @SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
6229             p.println("    " + c + ":");
6230             p.println("      client=" + c.mClient);
6231             p.println("      fallbackInputConnection=" + c.mFallbackInputConnection);
6232             p.println("      sessionRequested=" + c.mSessionRequested);
6233             p.println("      sessionRequestedForAccessibility="
6234                     + c.mSessionRequestedForAccessibility);
6235             p.println("      curSession=" + c.mCurSession);
6236             p.println("      selfReportedDisplayId=" + c.mSelfReportedDisplayId);
6237             p.println("      uid=" + c.mUid);
6238             p.println("      pid=" + c.mPid);
6239         };
6240         synchronized (ImfLock.class) {
6241             mClientController.forAllClients(clientControllerDump);
6242         }
6243     }
6244 
dumpUserRepository(@onNull Printer p)6245     private void dumpUserRepository(@NonNull Printer p) {
6246         p.println("  mUserDataRepository:");
6247         // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
6248         @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump = u -> {
6249             p.println("    userId=" + u.mUserId);
6250             p.println("      unlocked=" + u.mIsUnlockingOrUnlocked.get());
6251             p.println("      hasMainConnection=" + u.mBindingController.hasMainConnection());
6252             p.println("      isVisibleBound=" + u.mBindingController.isVisibleBound());
6253             p.println("      boundToMethod=" + u.mBoundToMethod);
6254             p.println("      curClient=" + u.mCurClient);
6255             if (u.mCurEditorInfo != null) {
6256                 p.println("      curEditorInfo:");
6257                 u.mCurEditorInfo.dump(p, "        ", false /* dumpExtras */);
6258             } else {
6259                 p.println("      curEditorInfo: null");
6260             }
6261             p.println("      imeBindingState:");
6262             u.mImeBindingState.dump(p, "        ");
6263             p.println("      enabledSession=" + u.mEnabledSession);
6264             p.println("      inFullscreenMode=" + u.mInFullscreenMode);
6265             p.println("      imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
6266             p.println("      switchingController:");
6267             u.mSwitchingController.dump(p, "        ");
6268             p.println("      mLastEnabledInputMethodsStr=" + u.mLastEnabledInputMethodsStr);
6269         };
6270         synchronized (ImfLock.class) {
6271             mUserDataRepository.forAllUserData(userDataDump);
6272         }
6273     }
6274 
6275     @BinderThread
6276     @Override
onShellCommand(@ullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver, @NonNull Binder self)6277     public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
6278             @Nullable FileDescriptor err,
6279             @NonNull String[] args, @Nullable ShellCallback callback,
6280             @NonNull ResultReceiver resultReceiver, @NonNull Binder self) {
6281         final int callingUid = Binder.getCallingUid();
6282         // Reject any incoming calls from non-shell users, including ones from the system user.
6283         if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
6284             // Note that Binder#onTransact() will automatically close "in", "out", and "err" when
6285             // returned from this method, hence there is no need to close those FDs.
6286             // "resultReceiver" is the only thing that needs to be taken care of here.
6287             if (resultReceiver != null) {
6288                 resultReceiver.send(ShellCommandResult.FAILURE, null);
6289             }
6290             final String errorMsg = "InputMethodManagerService does not support shell commands from"
6291                     + " non-shell users. callingUid=" + callingUid
6292                     + " args=" + Arrays.toString(args);
6293             if (Process.isCoreUid(callingUid)) {
6294                 // Let's not crash the calling process if the caller is one of core components.
6295                 Slog.e(TAG, errorMsg);
6296                 return;
6297             }
6298             throw new SecurityException(errorMsg);
6299         }
6300         new ShellCommandImpl(this).exec(
6301                 self, in, out, err, args, callback, resultReceiver);
6302     }
6303 
6304     private static final class ShellCommandImpl extends ShellCommand {
6305         @NonNull
6306         final InputMethodManagerService mService;
6307 
ShellCommandImpl(InputMethodManagerService service)6308         ShellCommandImpl(InputMethodManagerService service) {
6309             mService = service;
6310         }
6311 
6312         @BinderThread
6313         @ShellCommandResult
6314         @Override
onCommand(@ullable String cmd)6315         public int onCommand(@Nullable String cmd) {
6316             final long identity = Binder.clearCallingIdentity();
6317             try {
6318                 return onCommandWithSystemIdentity(cmd);
6319             } finally {
6320                 Binder.restoreCallingIdentity(identity);
6321             }
6322         }
6323 
6324         @BinderThread
6325         @ShellCommandResult
onCommandWithSystemIdentity(@ullable String cmd)6326         private int onCommandWithSystemIdentity(@Nullable String cmd) {
6327             switch (TextUtils.emptyIfNull(cmd)) {
6328                 case "tracing":
6329                     return mService.handleShellCommandTraceInputMethod(this);
6330                 case "ime": {  // For "adb shell ime <command>".
6331                     final String imeCommand = TextUtils.emptyIfNull(getNextArg());
6332                     switch (imeCommand) {
6333                         case "":
6334                         case "-h":
6335                         case "help":
6336                             return onImeCommandHelp();
6337                         case "list":
6338                             return mService.handleShellCommandListInputMethods(this);
6339                         case "enable":
6340                             return mService.handleShellCommandEnableDisableInputMethod(this, true);
6341                         case "disable":
6342                             return mService.handleShellCommandEnableDisableInputMethod(this, false);
6343                         case "set":
6344                             return mService.handleShellCommandSetInputMethod(this);
6345                         case "reset":
6346                             return mService.handleShellCommandResetInputMethod(this);
6347                         case "tracing":  // TODO(b/180765389): Unsupport "adb shell ime tracing"
6348                             return mService.handleShellCommandTraceInputMethod(this);
6349                         default:
6350                             getOutPrintWriter().println("Unknown command: " + imeCommand);
6351                             return ShellCommandResult.FAILURE;
6352                     }
6353                 }
6354                 default:
6355                     return handleDefaultCommands(cmd);
6356             }
6357         }
6358 
6359         @BinderThread
6360         @Override
onHelp()6361         public void onHelp() {
6362             try (PrintWriter pw = getOutPrintWriter()) {
6363                 pw.println("InputMethodManagerService commands:");
6364                 pw.println("  help");
6365                 pw.println("    Prints this help text.");
6366                 pw.println("  dump [options]");
6367                 pw.println("    Synonym of dumpsys.");
6368                 pw.println("  ime <command> [options]");
6369                 pw.println("    Manipulate IMEs.  Run \"ime help\" for details.");
6370                 pw.println("  tracing <command>");
6371                 pw.println("    start: Start tracing.");
6372                 pw.println("    stop : Stop tracing.");
6373                 pw.println("    help : Show help.");
6374             }
6375         }
6376 
6377         @BinderThread
6378         @ShellCommandResult
onImeCommandHelp()6379         private int onImeCommandHelp() {
6380             try (IndentingPrintWriter pw =
6381                          new IndentingPrintWriter(getOutPrintWriter(), "  ", 100)) {
6382                 pw.println("ime <command>:");
6383                 pw.increaseIndent();
6384 
6385                 pw.println("list [-a] [-s]");
6386                 pw.increaseIndent();
6387                 pw.println("prints all enabled input methods.");
6388                 pw.increaseIndent();
6389                 pw.println("-a: see all input methods");
6390                 pw.println("-s: only a single summary line of each");
6391                 pw.decreaseIndent();
6392                 pw.decreaseIndent();
6393 
6394                 pw.println("enable [--user <USER_ID>] <ID>");
6395                 pw.increaseIndent();
6396                 pw.println("allows the given input method ID to be used.");
6397                 pw.increaseIndent();
6398                 pw.print("--user <USER_ID>: Specify which user to enable.");
6399                 pw.println(" Assumes the current user if not specified.");
6400                 pw.decreaseIndent();
6401                 pw.decreaseIndent();
6402 
6403                 pw.println("disable [--user <USER_ID>] <ID>");
6404                 pw.increaseIndent();
6405                 pw.println("disallows the given input method ID to be used.");
6406                 pw.increaseIndent();
6407                 pw.print("--user <USER_ID>: Specify which user to disable.");
6408                 pw.println(" Assumes the current user if not specified.");
6409                 pw.decreaseIndent();
6410                 pw.decreaseIndent();
6411 
6412                 pw.println("set [--user <USER_ID>] <ID>");
6413                 pw.increaseIndent();
6414                 pw.println("switches to the given input method ID.");
6415                 pw.increaseIndent();
6416                 pw.print("--user <USER_ID>: Specify which user to enable.");
6417                 pw.println(" Assumes the current user if not specified.");
6418                 pw.decreaseIndent();
6419                 pw.decreaseIndent();
6420 
6421                 pw.println("reset [--user <USER_ID>]");
6422                 pw.increaseIndent();
6423                 pw.println("reset currently selected/enabled IMEs to the default ones as if "
6424                         + "the device is initially booted with the current locale.");
6425                 pw.increaseIndent();
6426                 pw.print("--user <USER_ID>: Specify which user to reset.");
6427                 pw.println(" Assumes the current user if not specified.");
6428                 pw.decreaseIndent();
6429 
6430                 pw.decreaseIndent();
6431 
6432                 pw.decreaseIndent();
6433             }
6434             return ShellCommandResult.SUCCESS;
6435         }
6436     }
6437 
6438     // ----------------------------------------------------------------------
6439     // Shell command handlers:
6440 
6441     /**
6442      * Handles {@code adb shell ime list}.
6443      *
6444      * @param shellCommand {@link ShellCommand} object that is handling this command
6445      * @return exit code of the command
6446      */
6447     @BinderThread
6448     @ShellCommandResult
handleShellCommandListInputMethods(@onNull ShellCommand shellCommand)6449     private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) {
6450         boolean all = false;
6451         boolean brief = false;
6452         int userIdToBeResolved = UserHandle.USER_CURRENT;
6453         while (true) {
6454             final String nextOption = shellCommand.getNextOption();
6455             if (nextOption == null) {
6456                 break;
6457             }
6458             switch (nextOption) {
6459                 case "-a":
6460                     all = true;
6461                     break;
6462                 case "-s":
6463                     brief = true;
6464                     break;
6465                 case "-u":
6466                 case "--user":
6467                     userIdToBeResolved = UserHandle.parseUserArg(shellCommand.getNextArgRequired());
6468                     break;
6469             }
6470         }
6471         final int[] userIds;
6472         synchronized (ImfLock.class) {
6473             userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentImeUserId,
6474                     shellCommand.getErrPrintWriter());
6475         }
6476         try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
6477             for (int userId : userIds) {
6478                 final List<InputMethodInfo> methods = all
6479                         ? getInputMethodListInternal(
6480                                 userId, DirectBootAwareness.AUTO, Process.SHELL_UID)
6481                         : getEnabledInputMethodListInternal(userId, Process.SHELL_UID);
6482                 if (userIds.length > 1) {
6483                     pr.print("User #");
6484                     pr.print(userId);
6485                     pr.println(":");
6486                 }
6487                 for (InputMethodInfo info : methods) {
6488                     if (brief) {
6489                         pr.println(info.getId());
6490                     } else {
6491                         pr.print(info.getId());
6492                         pr.println(":");
6493                         info.dump(pr::println, "  ");
6494                     }
6495                 }
6496             }
6497         }
6498         return ShellCommandResult.SUCCESS;
6499     }
6500 
6501     /**
6502      * Handles {@code adb shell ime enable} and {@code adb shell ime disable}.
6503      *
6504      * @param shellCommand {@link ShellCommand} object that is handling this command
6505      * @param enabled      {@code true} if the command was {@code adb shell ime enable}
6506      * @return exit code of the command
6507      */
6508     @BinderThread
6509     @ShellCommandResult
handleShellCommandEnableDisableInputMethod( @onNull ShellCommand shellCommand, boolean enabled)6510     private int handleShellCommandEnableDisableInputMethod(
6511             @NonNull ShellCommand shellCommand, boolean enabled) {
6512         final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand);
6513         final String imeId = shellCommand.getNextArgRequired();
6514         boolean hasFailed = false;
6515         try (PrintWriter out = shellCommand.getOutPrintWriter();
6516              PrintWriter error = shellCommand.getErrPrintWriter()) {
6517             synchronized (ImfLock.class) {
6518                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
6519                         mCurrentImeUserId, shellCommand.getErrPrintWriter());
6520                 for (int userId : userIds) {
6521                     if (!userHasDebugPriv(userId, shellCommand)) {
6522                         continue;
6523                     }
6524                     hasFailed |= !handleShellCommandEnableDisableInputMethodInternalLocked(
6525                             userId, imeId, enabled, out, error);
6526                 }
6527             }
6528         }
6529         return hasFailed ? ShellCommandResult.FAILURE : ShellCommandResult.SUCCESS;
6530     }
6531 
6532     /**
6533      * A special helper method for commands that only have {@code -u} and {@code --user} options.
6534      *
6535      * <p>You cannot use this helper method if the command has other options.</p>
6536      *
6537      * <p>CAVEAT: This method must be called only once before any other
6538      * {@link ShellCommand#getNextArg()} and {@link ShellCommand#getNextArgRequired()} for the
6539      * main arguments.</p>
6540      *
6541      * @param shellCommand {@link ShellCommand} from which options should be obtained
6542      * @return user ID to be resolved. {@link UserHandle#CURRENT} if not specified
6543      */
6544     @BinderThread
6545     @UserIdInt
handleOptionsForCommandsThatOnlyHaveUserOption(ShellCommand shellCommand)6546     private static int handleOptionsForCommandsThatOnlyHaveUserOption(ShellCommand shellCommand) {
6547         while (true) {
6548             final String nextOption = shellCommand.getNextOption();
6549             if (nextOption == null) {
6550                 break;
6551             }
6552             switch (nextOption) {
6553                 case "-u":
6554                 case "--user":
6555                     return UserHandle.parseUserArg(shellCommand.getNextArgRequired());
6556             }
6557         }
6558         return UserHandle.USER_CURRENT;
6559     }
6560 
6561     /**
6562      * Handles core logic of {@code adb shell ime enable} and {@code adb shell ime disable}.
6563      *
6564      * @param userId  user ID specified to the command (pseudo user IDs are not supported)
6565      * @param imeId   IME ID specified to the command
6566      * @param enabled {@code true} for {@code adb shell ime enable}
6567      * @param out     {@link PrintWriter} to output standard messages
6568      * @param error   {@link PrintWriter} to output error messages
6569      * @return {@code false} if it fails to enable the IME
6570      */
6571     @BinderThread
6572     @GuardedBy("ImfLock.class")
handleShellCommandEnableDisableInputMethodInternalLocked( @serIdInt int userId, String imeId, boolean enabled, PrintWriter out, PrintWriter error)6573     private boolean handleShellCommandEnableDisableInputMethodInternalLocked(
6574             @UserIdInt int userId, String imeId, boolean enabled, PrintWriter out,
6575             PrintWriter error) {
6576         final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
6577         if (enabled && !settings.getMethodMap().containsKey(imeId)) {
6578             error.print("Unknown input method ");
6579             error.print(imeId);
6580             error.println(" cannot be enabled for user #" + userId);
6581             // Also print this failure into logcat for better debuggability.
6582             Slog.e(TAG, "\"ime enable " + imeId + "\" for user #" + userId
6583                     + " failed due to its unrecognized IME ID.");
6584             return false;
6585         }
6586 
6587         final boolean previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled, userId);
6588         out.print("Input method ");
6589         out.print(imeId);
6590         out.print(": ");
6591         out.print((enabled == previouslyEnabled) ? "already " : "now ");
6592         out.print(enabled ? "enabled" : "disabled");
6593         out.print(" for user #");
6594         out.println(userId);
6595         return true;
6596     }
6597 
6598     /**
6599      * Handles {@code adb shell ime set}.
6600      *
6601      * @param shellCommand {@link ShellCommand} object that is handling this command
6602      * @return Exit code of the command.
6603      */
6604     @BinderThread
6605     @ShellCommandResult
handleShellCommandSetInputMethod(@onNull ShellCommand shellCommand)6606     private int handleShellCommandSetInputMethod(@NonNull ShellCommand shellCommand) {
6607         final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand);
6608         final String imeId = shellCommand.getNextArgRequired();
6609         boolean hasFailed = false;
6610         try (PrintWriter out = shellCommand.getOutPrintWriter();
6611              PrintWriter error = shellCommand.getErrPrintWriter()) {
6612             synchronized (ImfLock.class) {
6613                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
6614                         mCurrentImeUserId, shellCommand.getErrPrintWriter());
6615                 for (int userId : userIds) {
6616                     if (!userHasDebugPriv(userId, shellCommand)) {
6617                         continue;
6618                     }
6619                     boolean failedToSelectUnknownIme = !switchToInputMethodLocked(imeId,
6620                             NOT_A_SUBTYPE_INDEX, userId);
6621                     if (failedToSelectUnknownIme) {
6622                         error.print("Unknown input method ");
6623                         error.print(imeId);
6624                         error.print(" cannot be selected for user #");
6625                         error.println(userId);
6626                         // Also print this failure into logcat for better debuggability.
6627                         Slog.e(TAG, "\"ime set " + imeId + "\" for user #" + userId
6628                                 + " failed due to its unrecognized IME ID.");
6629                     } else {
6630                         out.print("Input method ");
6631                         out.print(imeId);
6632                         out.print(" selected for user #");
6633                         out.println(userId);
6634 
6635                         // Workaround for b/354782333.
6636                         final InputMethodSettings settings =
6637                                 InputMethodSettingsRepository.get(userId);
6638                         final var bindingController = getInputMethodBindingController(userId);
6639                         final int deviceId = bindingController.getDeviceIdToShowIme();
6640                         final String settingsValue;
6641                         if (deviceId == DEVICE_ID_DEFAULT) {
6642                             settingsValue = settings.getSelectedInputMethod();
6643                         } else {
6644                             settingsValue = settings.getSelectedDefaultDeviceInputMethod();
6645                         }
6646                         if (!TextUtils.equals(settingsValue, imeId)) {
6647                             Slog.w(TAG, "DEFAULT_INPUT_METHOD=" + settingsValue
6648                                     + " is not updated. Fixing it up to " + imeId
6649                                     + " See b/354782333.");
6650                             if (deviceId == DEVICE_ID_DEFAULT) {
6651                                 settings.putSelectedInputMethod(imeId);
6652                             } else {
6653                                 settings.putSelectedDefaultDeviceInputMethod(imeId);
6654                             }
6655                         }
6656                     }
6657                     hasFailed |= failedToSelectUnknownIme;
6658                 }
6659             }
6660         }
6661         return hasFailed ? ShellCommandResult.FAILURE : ShellCommandResult.SUCCESS;
6662     }
6663 
6664     /**
6665      * Handles {@code adb shell ime reset-ime}.
6666      *
6667      * @param shellCommand {@link ShellCommand} object that is handling this command
6668      * @return Exit code of the command.
6669      */
6670     @BinderThread
6671     @ShellCommandResult
handleShellCommandResetInputMethod(@onNull ShellCommand shellCommand)6672     private int handleShellCommandResetInputMethod(@NonNull ShellCommand shellCommand) {
6673         final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand);
6674         synchronized (ImfLock.class) {
6675             try (PrintWriter out = shellCommand.getOutPrintWriter()) {
6676                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
6677                         mCurrentImeUserId, shellCommand.getErrPrintWriter());
6678                 for (int userId : userIds) {
6679                     if (!userHasDebugPriv(userId, shellCommand)) {
6680                         continue;
6681                     }
6682                     // Skip on headless user
6683                     final var userInfo = mUserManagerInternal.getUserInfo(userId);
6684                     if (userInfo != null && USER_TYPE_SYSTEM_HEADLESS.equals(userInfo.userType)) {
6685                         continue;
6686                     }
6687                     final String nextIme;
6688                     final List<InputMethodInfo> nextEnabledImes;
6689                     final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
6690                     final var userData = getUserData(userId);
6691                     if (Flags.refactorInsetsController()) {
6692                         setImeVisibilityOnFocusedWindowClient(false, userData,
6693                                 null /* TODO(b329229469) initialize statsToken here? */);
6694                     } else {
6695                         hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
6696                                 0 /* flags */,
6697                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId);
6698                     }
6699                     final var bindingController = userData.mBindingController;
6700                     bindingController.unbindCurrentMethod();
6701 
6702                     // Enable default IMEs, disable others
6703                     var toDisable = settings.getEnabledInputMethodList();
6704                     var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
6705                             mContext, settings.getMethodList());
6706                     toDisable.removeAll(defaultEnabled);
6707                     for (InputMethodInfo info : toDisable) {
6708                         setInputMethodEnabledLocked(info.getId(), false, userId);
6709                     }
6710                     for (InputMethodInfo info : defaultEnabled) {
6711                         setInputMethodEnabledLocked(info.getId(), true, userId);
6712                     }
6713                     // Choose new default IME, reset to none if no IME available.
6714                     if (!chooseNewDefaultIMELocked(userId)) {
6715                         resetSelectedInputMethodAndSubtypeLocked(null, userId);
6716                     }
6717                     updateInputMethodsFromSettingsLocked(true /* enabledMayChange */, userId);
6718                     InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
6719                             getPackageManagerForUser(mContext, settings.getUserId()),
6720                             settings.getEnabledInputMethodList());
6721                     nextIme = settings.getSelectedInputMethod();
6722                     nextEnabledImes = settings.getEnabledInputMethodList();
6723                     out.println("Reset current and enabled IMEs for user #" + userId);
6724                     out.println("  Selected: " + nextIme);
6725                     nextEnabledImes.forEach(ime -> out.println("   Enabled: " + ime.getId()));
6726                 }
6727             }
6728         }
6729         return ShellCommandResult.SUCCESS;
6730     }
6731 
6732     @GuardedBy("ImfLock.class")
setImeVisibilityOnFocusedWindowClient(boolean visible, UserData userData, @NonNull ImeTracker.Token statsToken)6733     boolean setImeVisibilityOnFocusedWindowClient(boolean visible, UserData userData,
6734             @NonNull ImeTracker.Token statsToken) {
6735         if (Flags.refactorInsetsController()) {
6736             if (userData.mImeBindingState.mFocusedWindowClient != null) {
6737                 userData.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visible,
6738                         statsToken);
6739                 return true;
6740             }
6741             ImeTracker.forLogging().onFailed(statsToken,
6742                     ImeTracker.PHASE_SERVER_SET_VISIBILITY_ON_FOCUSED_WINDOW);
6743         }
6744         return false;
6745     }
6746 
6747     /**
6748      * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}.
6749      *
6750      * @param shellCommand {@link ShellCommand} object that is handling this command
6751      * @return Exit code of the command.
6752      */
6753     @BinderThread
6754     @ShellCommandResult
handleShellCommandTraceInputMethod(@onNull ShellCommand shellCommand)6755     private int handleShellCommandTraceInputMethod(@NonNull ShellCommand shellCommand) {
6756         final String cmd = shellCommand.getNextArgRequired();
6757         try (PrintWriter pw = shellCommand.getOutPrintWriter()) {
6758             switch (cmd) {
6759                 case "start":
6760                     ImeTracing.getInstance().startTrace(pw);
6761                     break;  // proceed to the next step to update the IME client processes.
6762                 case "stop":
6763                     ImeTracing.getInstance().stopTrace(pw);
6764                     break;  // proceed to the next step to update the IME client processes.
6765                 case "save-for-bugreport":
6766                     ImeTracing.getInstance().saveForBugreport(pw);
6767                     // no need to update the IME client processes.
6768                     return ShellCommandResult.SUCCESS;
6769                 default:
6770                     pw.println("Unknown command: " + cmd);
6771                     pw.println("Input method trace options:");
6772                     pw.println("  start: Start tracing");
6773                     pw.println("  stop: Stop tracing");
6774                     // no need to update the IME client processes.
6775                     return ShellCommandResult.FAILURE;
6776             }
6777         }
6778         boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
6779         synchronized (ImfLock.class) {
6780             // TODO(b/322816970): Replace this with lambda.
6781             mClientController.forAllClients(new Consumer<ClientState>() {
6782 
6783                 @GuardedBy("ImfLock.class")
6784                 @Override
6785                 public void accept(ClientState c) {
6786                     c.mClient.setImeTraceEnabled(isImeTraceEnabled);
6787                 }
6788             });
6789         }
6790         return ShellCommandResult.SUCCESS;
6791     }
6792 
6793     /**
6794      * @param userId the actual user handle obtained by {@link UserHandle#getIdentifier()}
6795      *               and *not* pseudo ids like {@link UserHandle#USER_ALL etc}
6796      * @return {@code true} if userId has debugging privileges
6797      * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}
6798      */
userHasDebugPriv(@serIdInt int userId, ShellCommand shellCommand)6799     private boolean userHasDebugPriv(@UserIdInt int userId, ShellCommand shellCommand) {
6800         if (mUserManagerInternal.hasUserRestriction(
6801                 UserManager.DISALLOW_DEBUGGING_FEATURES, userId)) {
6802             shellCommand.getErrPrintWriter().println("User #" + userId
6803                     + " is restricted with DISALLOW_DEBUGGING_FEATURES.");
6804             return false;
6805         }
6806         return true;
6807     }
6808 
6809     /** @hide */
6810     @Override
getImeTrackerService()6811     public IImeTracker getImeTrackerService() {
6812         return mImeTrackerService;
6813     }
6814 
6815     /**
6816      * Creates an IME request tracking token for the current focused client.
6817      *
6818      * @param show   whether this is a show or a hide request
6819      * @param reason the reason why the IME request was created
6820      */
6821     @NonNull
6822     @GuardedBy("ImfLock.class")
createStatsTokenForFocusedClient(boolean show, @SoftInputShowHideReason int reason, @UserIdInt int userId)6823     private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
6824             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
6825         final var userData = getUserData(userId);
6826         final int uid = userData.mImeBindingState.mFocusedWindowClient != null
6827                 ? userData.mImeBindingState.mFocusedWindowClient.mUid
6828                 : -1;
6829         final var packageName = userData.mImeBindingState.mFocusedWindowEditorInfo != null
6830                 ? userData.mImeBindingState.mFocusedWindowEditorInfo.packageName
6831                 : "uid(" + uid + ")";
6832 
6833         return ImeTracker.forLogging().onStart(packageName, uid,
6834                 show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_SERVER,
6835                 reason, false /* fromUser */);
6836     }
6837 
6838     private static final class InputMethodPrivilegedOperationsImpl
6839             extends IInputMethodPrivilegedOperations.Stub {
6840         @NonNull
6841         private final InputMethodManagerService mImms;
6842         @NonNull
6843         private final IBinder mToken;
6844         @NonNull
6845         private final UserData mUserData;
6846 
InputMethodPrivilegedOperationsImpl(@onNull InputMethodManagerService imms, @NonNull IBinder token, @NonNull UserData userData)6847         InputMethodPrivilegedOperationsImpl(@NonNull InputMethodManagerService imms,
6848                 @NonNull IBinder token, @NonNull UserData userData) {
6849             mImms = imms;
6850             mToken = token;
6851             mUserData = userData;
6852         }
6853 
6854         @BinderThread
6855         @Override
setImeWindowStatusAsync(@meWindowVisibility int vis, @BackDispositionMode int backDisposition)6856         public void setImeWindowStatusAsync(@ImeWindowVisibility int vis,
6857                 @BackDispositionMode int backDisposition) {
6858             synchronized (ImfLock.class) {
6859                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
6860                     return;
6861                 }
6862                 mImms.setImeWindowStatusLocked(vis, backDisposition, mUserData);
6863             }
6864         }
6865 
6866         @BinderThread
6867         @Override
reportStartInputAsync(IBinder startInputToken)6868         public void reportStartInputAsync(IBinder startInputToken) {
6869             synchronized (ImfLock.class) {
6870                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
6871                     return;
6872                 }
6873                 mImms.reportStartInputLocked(startInputToken, mUserData);
6874             }
6875         }
6876 
6877         @BinderThread
6878         @Override
setHandwritingSurfaceNotTouchable(boolean notTouchable)6879         public void setHandwritingSurfaceNotTouchable(boolean notTouchable) {
6880             synchronized (ImfLock.class) {
6881                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
6882                     return;
6883                 }
6884                 mImms.mHwController.setNotTouchable(notTouchable);
6885             }
6886         }
6887 
6888         @BinderThread
6889         @Override
setHandwritingTouchableRegion(Region region)6890         public void setHandwritingTouchableRegion(Region region) {
6891             synchronized (ImfLock.class) {
6892                 mImms.mHwController.setHandwritingTouchableRegion(region);
6893             }
6894         }
6895 
6896         @BinderThread
6897         @Override
createInputContentUriToken(Uri contentUri, String packageName, AndroidFuture future )6898         public void createInputContentUriToken(Uri contentUri, String packageName,
6899                 AndroidFuture future /* T=IBinder */) {
6900             @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
6901             try {
6902                 synchronized (ImfLock.class) {
6903                     if (!calledWithValidTokenLocked(mToken, mUserData)) {
6904                         typedFuture.complete(null);
6905                         return;
6906                     }
6907                     typedFuture.complete(mImms.createInputContentUriTokenLocked(
6908                             contentUri, packageName, mUserData).asBinder());
6909                 }
6910             } catch (Throwable e) {
6911                 typedFuture.completeExceptionally(e);
6912             }
6913         }
6914 
6915         @BinderThread
6916         @Override
reportFullscreenModeAsync(boolean fullscreen)6917         public void reportFullscreenModeAsync(boolean fullscreen) {
6918             synchronized (ImfLock.class) {
6919                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
6920                     return;
6921                 }
6922                 mImms.reportFullscreenModeLocked(fullscreen, mUserData);
6923             }
6924         }
6925 
6926         @BinderThread
6927         @Override
setInputMethod(String id, AndroidFuture future )6928         public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
6929             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
6930             try {
6931                 synchronized (ImfLock.class) {
6932                     if (!calledWithValidTokenLocked(mToken, mUserData)) {
6933                         typedFuture.complete(null);
6934                         return;
6935                     }
6936                     mImms.setInputMethodAndSubtypeLocked(id, null /* subtype */, mUserData);
6937                     typedFuture.complete(null);
6938                 }
6939             } catch (Throwable e) {
6940                 typedFuture.completeExceptionally(e);
6941             }
6942         }
6943 
6944         @BinderThread
6945         @Override
setInputMethodAndSubtype(String id, InputMethodSubtype subtype, AndroidFuture future )6946         public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype,
6947                 AndroidFuture future /* T=Void */) {
6948             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
6949             try {
6950                 synchronized (ImfLock.class) {
6951                     if (!calledWithValidTokenLocked(mToken, mUserData)) {
6952                         typedFuture.complete(null);
6953                         return;
6954                     }
6955                     mImms.setInputMethodAndSubtypeLocked(id, subtype, mUserData);
6956                     typedFuture.complete(null);
6957                 }
6958             } catch (Throwable e) {
6959                 typedFuture.completeExceptionally(e);
6960             }
6961         }
6962 
6963         @BinderThread
6964         @Override
hideMySoftInput(@onNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, AndroidFuture future )6965         public void hideMySoftInput(@NonNull ImeTracker.Token statsToken,
6966                 @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
6967                 AndroidFuture future /* T=Void */) {
6968             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
6969             try {
6970                 synchronized (ImfLock.class) {
6971                     if (!calledWithValidTokenLocked(mToken, mUserData)) {
6972                         ImeTracker.forLogging().onFailed(statsToken,
6973                                 ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
6974                         typedFuture.complete(null);
6975                         return;
6976                     }
6977                     ImeTracker.forLogging().onProgress(statsToken,
6978                             ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
6979                     Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
6980                     final long ident = Binder.clearCallingIdentity();
6981                     try {
6982                         mImms.hideMySoftInputLocked(statsToken, flags, reason, mUserData);
6983                         typedFuture.complete(null);
6984                     } finally {
6985                         Binder.restoreCallingIdentity(ident);
6986                         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
6987                     }
6988                 }
6989             } catch (Throwable e) {
6990                 typedFuture.completeExceptionally(e);
6991             }
6992         }
6993 
6994         @BinderThread
6995         @Override
showMySoftInput(@onNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, AndroidFuture future )6996         public void showMySoftInput(@NonNull ImeTracker.Token statsToken,
6997                 @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
6998                 AndroidFuture future /* T=Void */) {
6999             @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
7000             try {
7001                 synchronized (ImfLock.class) {
7002                     if (!calledWithValidTokenLocked(mToken, mUserData)) {
7003                         ImeTracker.forLogging().onFailed(statsToken,
7004                                 ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
7005                         typedFuture.complete(null);
7006                         return;
7007                     }
7008                     ImeTracker.forLogging().onProgress(statsToken,
7009                             ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
7010                     Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
7011                     final long ident = Binder.clearCallingIdentity();
7012                     try {
7013                         mImms.showMySoftInputLocked(statsToken, flags, reason, mUserData);
7014                         typedFuture.complete(null);
7015                     } finally {
7016                         Binder.restoreCallingIdentity(ident);
7017                         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
7018                     }
7019                 }
7020             } catch (Throwable e) {
7021                 typedFuture.completeExceptionally(e);
7022             }
7023         }
7024 
7025         @BinderThread
7026         @Override
updateStatusIconAsync(String packageName, @DrawableRes int iconId)7027         public void updateStatusIconAsync(String packageName, @DrawableRes int iconId) {
7028             synchronized (ImfLock.class) {
7029                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
7030                     return;
7031                 }
7032                 final long ident = Binder.clearCallingIdentity();
7033                 try {
7034                     mImms.updateStatusIconLocked(packageName, iconId, mUserData);
7035                 } finally {
7036                     Binder.restoreCallingIdentity(ident);
7037                 }
7038             }
7039         }
7040 
7041         @BinderThread
7042         @Override
switchToPreviousInputMethod(AndroidFuture future )7043         public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
7044             @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
7045             try {
7046                 synchronized (ImfLock.class) {
7047                     if (!calledWithValidTokenLocked(mToken, mUserData)) {
7048                         typedFuture.complete(false);
7049                         return;
7050                     }
7051                     typedFuture.complete(mImms.switchToPreviousInputMethodLocked(mUserData));
7052                 }
7053             } catch (Throwable e) {
7054                 typedFuture.completeExceptionally(e);
7055             }
7056         }
7057 
7058         @BinderThread
7059         @Override
switchToNextInputMethod(boolean onlyCurrentIme, AndroidFuture future )7060         public void switchToNextInputMethod(boolean onlyCurrentIme,
7061                 AndroidFuture future /* T=Boolean */) {
7062             @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
7063             try {
7064                 synchronized (ImfLock.class) {
7065                     if (!calledWithValidTokenLocked(mToken, mUserData)) {
7066                         typedFuture.complete(false);
7067                         return;
7068                     }
7069                     typedFuture.complete(mImms.switchToNextInputMethodLocked(onlyCurrentIme,
7070                             mUserData));
7071                 }
7072             } catch (Throwable e) {
7073                 typedFuture.completeExceptionally(e);
7074             }
7075         }
7076 
7077         @BinderThread
7078         @Override
shouldOfferSwitchingToNextInputMethod(AndroidFuture future )7079         public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
7080             @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
7081             try {
7082                 synchronized (ImfLock.class) {
7083                     if (!calledWithValidTokenLocked(mToken, mUserData)) {
7084                         typedFuture.complete(false);
7085                         return;
7086                     }
7087                     typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethodLocked(
7088                             mUserData));
7089                 }
7090             } catch (Throwable e) {
7091                 typedFuture.completeExceptionally(e);
7092             }
7093         }
7094 
7095         @BinderThread
7096         @Override
onImeSwitchButtonClickFromClient(int displayId)7097         public void onImeSwitchButtonClickFromClient(int displayId) {
7098             synchronized (ImfLock.class) {
7099                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
7100                     return;
7101                 }
7102                 mImms.onImeSwitchButtonClickLocked(displayId, mUserData);
7103             }
7104         }
7105 
7106         @BinderThread
7107         @Override
notifyUserActionAsync()7108         public void notifyUserActionAsync() {
7109             synchronized (ImfLock.class) {
7110                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
7111                     return;
7112                 }
7113                 mImms.notifyUserActionLocked(mUserData);
7114             }
7115         }
7116 
7117         @BinderThread
7118         @Override
applyImeVisibilityAsync(IBinder windowToken, boolean setVisible, @NonNull ImeTracker.Token statsToken)7119         public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
7120                 @NonNull ImeTracker.Token statsToken) {
7121             synchronized (ImfLock.class) {
7122                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
7123                     ImeTracker.forLogging().onFailed(statsToken,
7124                             ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
7125                     return;
7126                 }
7127                 ImeTracker.forLogging().onProgress(statsToken,
7128                         ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
7129                 mImms.applyImeVisibilityLocked(windowToken, setVisible, statsToken, mUserData);
7130             }
7131         }
7132 
7133         @BinderThread
7134         @Override
onStylusHandwritingReady(int requestId, int pid)7135         public void onStylusHandwritingReady(int requestId, int pid) {
7136             synchronized (ImfLock.class) {
7137                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
7138                     return;
7139                 }
7140                 mImms.onStylusHandwritingReadyLocked(requestId, pid, mUserData);
7141             }
7142         }
7143 
7144         @BinderThread
7145         @Override
resetStylusHandwriting(int requestId)7146         public void resetStylusHandwriting(int requestId) {
7147             synchronized (ImfLock.class) {
7148                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
7149                     return;
7150                 }
7151                 mImms.resetStylusHandwritingLocked(requestId);
7152             }
7153         }
7154 
7155         @BinderThread
7156         @Override
switchKeyboardLayoutAsync(int direction)7157         public void switchKeyboardLayoutAsync(int direction) {
7158             synchronized (ImfLock.class) {
7159                 if (!calledWithValidTokenLocked(mToken, mUserData)) {
7160                     return;
7161                 }
7162                 final long ident = Binder.clearCallingIdentity();
7163                 try {
7164                     mImms.switchKeyboardLayoutLocked(direction, mUserData);
7165                 } finally {
7166                     Binder.restoreCallingIdentity(ident);
7167                 }
7168             }
7169         }
7170 
7171         /**
7172          * Returns true iff the caller is identified to be the current input method with the token.
7173          *
7174          * @param token the window token given to the input method when it was started
7175          * @param userData {@link UserData} of the calling IME process
7176          * @return true if and only if non-null valid token is specified
7177          */
7178         @GuardedBy("ImfLock.class")
calledWithValidTokenLocked(@onNull IBinder token, @NonNull UserData userData)7179         private static boolean calledWithValidTokenLocked(@NonNull IBinder token,
7180                 @NonNull UserData userData) {
7181             Objects.requireNonNull(token, "token must not be null");
7182             final var bindingController = userData.mBindingController;
7183             if (token != bindingController.getCurToken()) {
7184                 Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
7185                         + " uid:" + Binder.getCallingUid() + " token:" + token);
7186                 return false;
7187             }
7188             return true;
7189         }
7190     }
7191 }
7192