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