• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
20 import static android.content.Context.DEVICE_ID_DEFAULT;
21 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
22 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
23 import static android.view.Display.INVALID_DISPLAY;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.UserIdInt;
28 import android.app.ActivityOptions;
29 import android.app.PendingIntent;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.ServiceConnection;
34 import android.content.pm.PackageManagerInternal;
35 import android.inputmethodservice.InputMethodService;
36 import android.inputmethodservice.InputMethodService.BackDispositionMode;
37 import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
38 import android.os.Binder;
39 import android.os.IBinder;
40 import android.os.Process;
41 import android.os.SystemClock;
42 import android.os.Trace;
43 import android.os.UserHandle;
44 import android.provider.Settings;
45 import android.util.EventLog;
46 import android.util.Slog;
47 import android.view.Display;
48 import android.view.WindowManager;
49 import android.view.inputmethod.InputMethod;
50 import android.view.inputmethod.InputMethodInfo;
51 import android.view.inputmethod.InputMethodManager;
52 import android.view.inputmethod.InputMethodSubtype;
53 
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.inputmethod.IInputMethod;
57 import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
58 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
59 import com.android.internal.inputmethod.InputBindResult;
60 import com.android.internal.inputmethod.UnbindReason;
61 import com.android.server.EventLogTags;
62 import com.android.server.wm.WindowManagerInternal;
63 
64 import java.io.PrintWriter;
65 import java.util.concurrent.CountDownLatch;
66 
67 /**
68  * A controller managing the state of the input method binding.
69  */
70 final class InputMethodBindingController {
71     static final boolean DEBUG = false;
72     private static final String TAG = InputMethodBindingController.class.getSimpleName();
73 
74     /** Time in milliseconds that the IME service has to bind before it is reconnected. */
75     static final long TIME_TO_RECONNECT = 3 * 1000;
76 
77     @UserIdInt private final int mUserId;
78     @NonNull private final InputMethodManagerService mService;
79     @NonNull private final Context mContext;
80     @NonNull private final AutofillSuggestionsController mAutofillController;
81     @NonNull private final PackageManagerInternal mPackageManagerInternal;
82     @NonNull private final WindowManagerInternal mWindowManagerInternal;
83 
84     @GuardedBy("ImfLock.class") private long mLastBindTime;
85     @GuardedBy("ImfLock.class") private boolean mHasMainConnection;
86     @GuardedBy("ImfLock.class") @Nullable private String mCurId;
87     @GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId;
88     @GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent;
89     @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod;
90     @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID;
91     @GuardedBy("ImfLock.class") @Nullable private IBinder mCurToken;
92     @GuardedBy("ImfLock.class") @Nullable private InputMethodSubtype mCurrentSubtype;
93     @GuardedBy("ImfLock.class") private int mCurTokenDisplayId = INVALID_DISPLAY;
94     @GuardedBy("ImfLock.class") private int mCurSeq;
95     @GuardedBy("ImfLock.class") private boolean mVisibleBound;
96     @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
97     @GuardedBy("ImfLock.class") private boolean mSupportsConnectionlessStylusHw;
98 
99     /** The display id for which the latest startInput was called. */
100     @GuardedBy("ImfLock.class") private int mDisplayIdToShowIme = INVALID_DISPLAY;
101     @GuardedBy("ImfLock.class") private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
102 
103     /**
104      * A set of status bits regarding the active IME.
105      *
106      * <em>Do not update this value outside of {@link #setImeWindowVis} and
107      * {@link InputMethodBindingController#unbindCurrentMethod}.</em>
108      */
109     @ImeWindowVisibility
110     @GuardedBy("ImfLock.class")
111     private int mImeWindowVis;
112 
113     @BackDispositionMode
114     @GuardedBy("ImfLock.class")
115     private int mBackDisposition = BACK_DISPOSITION_DEFAULT;
116 
117     @Nullable private CountDownLatch mLatchForTesting;
118 
119     /**
120      * Binding flags for establishing connection to the {@link InputMethodService}.
121      */
122     @VisibleForTesting
123     static final int IME_CONNECTION_BIND_FLAGS;
124     static {
125         if (android.view.inputmethod.Flags.lowerImeOomImportance()) {
126             IME_CONNECTION_BIND_FLAGS = Context.BIND_AUTO_CREATE
127                     | Context.BIND_ALMOST_PERCEPTIBLE
128                     | Context.BIND_IMPORTANT_BACKGROUND
129                     | Context.BIND_SCHEDULE_LIKE_TOP_APP;
130         } else {
131             IME_CONNECTION_BIND_FLAGS = Context.BIND_AUTO_CREATE
132                     | Context.BIND_NOT_VISIBLE
133                     | Context.BIND_NOT_FOREGROUND
134                     | Context.BIND_IMPORTANT_BACKGROUND
135                     | Context.BIND_SCHEDULE_LIKE_TOP_APP;
136         }
137     }
138 
139     private final int mImeConnectionBindFlags;
140 
141     /**
142      * Binding flags used only while the {@link InputMethodService} is showing window.
143      */
144     @VisibleForTesting
145     static final int IME_VISIBLE_BIND_FLAGS =
146             Context.BIND_AUTO_CREATE
147                     | Context.BIND_TREAT_LIKE_ACTIVITY
148                     | Context.BIND_FOREGROUND_SERVICE
149                     | Context.BIND_INCLUDE_CAPABILITIES
150                     | Context.BIND_SHOWING_UI;
151 
InputMethodBindingController(@serIdInt int userId, @NonNull InputMethodManagerService service)152     InputMethodBindingController(@UserIdInt int userId,
153             @NonNull InputMethodManagerService service) {
154         this(userId, service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
155     }
156 
InputMethodBindingController(@serIdInt int userId, @NonNull InputMethodManagerService service, int imeConnectionBindFlags, CountDownLatch latchForTesting)157     InputMethodBindingController(@UserIdInt int userId,
158             @NonNull InputMethodManagerService service, int imeConnectionBindFlags,
159             CountDownLatch latchForTesting) {
160         mUserId = userId;
161         mService = service;
162         mContext = mService.mContext;
163         mAutofillController = new AutofillSuggestionsController(this);
164         mPackageManagerInternal = mService.mPackageManagerInternal;
165         mWindowManagerInternal = mService.mWindowManagerInternal;
166         mImeConnectionBindFlags = imeConnectionBindFlags;
167         mLatchForTesting = latchForTesting;
168     }
169 
170     /**
171      * Time that we last initiated a bind to the input method, to determine
172      * if we should try to disconnect and reconnect to it.
173      */
174     @GuardedBy("ImfLock.class")
getLastBindTime()175     long getLastBindTime() {
176         return mLastBindTime;
177     }
178 
179     /**
180      * Set to true if our ServiceConnection is currently actively bound to
181      * a service (whether or not we have gotten its IBinder back yet).
182      */
183     @GuardedBy("ImfLock.class")
hasMainConnection()184     boolean hasMainConnection() {
185         return mHasMainConnection;
186     }
187 
188     /**
189      * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
190      * connected to or in the process of connecting to.
191      *
192      * <p>This can be {@code null} when no input method is connected.</p>
193      *
194      * @see #getSelectedMethodId()
195      */
196     @GuardedBy("ImfLock.class")
197     @Nullable
getCurId()198     String getCurId() {
199         return mCurId;
200     }
201 
202     /**
203      * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
204      * This is to be synchronized with the secure settings keyed with
205      * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD}.
206      *
207      * <p>This can be transiently {@code null} when the system is re-initializing input method
208      * settings, e.g., the system locale is just changed.</p>
209      *
210      * <p>Note that {@link #getCurId()} is used to track which IME is being connected to
211      * {@link com.android.server.inputmethod.InputMethodManagerService}.</p>
212      *
213      * @see #getCurId()
214      */
215     @GuardedBy("ImfLock.class")
216     @Nullable
getSelectedMethodId()217     String getSelectedMethodId() {
218         return mSelectedMethodId;
219     }
220 
221     @GuardedBy("ImfLock.class")
setSelectedMethodId(@ullable String selectedMethodId)222     void setSelectedMethodId(@Nullable String selectedMethodId) {
223         mSelectedMethodId = selectedMethodId;
224     }
225 
226     /**
227      * Returns {@link InputMethodInfo} that is queried from {@link #getSelectedMethodId()}.
228      *
229      * @return {@link InputMethodInfo} whose IME ID is the same as {@link #getSelectedMethodId()}.
230      *         {@code null} otherwise
231      */
232     @GuardedBy("ImfLock.class")
233     @Nullable
getSelectedMethod()234     InputMethodInfo getSelectedMethod() {
235         return InputMethodSettingsRepository.get(mUserId).getMethodMap().get(mSelectedMethodId);
236     }
237 
238     /**
239      * The token we have made for the currently active input method, to
240      * identify it in the future.
241      */
242     @GuardedBy("ImfLock.class")
243     @Nullable
getCurToken()244     IBinder getCurToken() {
245         return mCurToken;
246     }
247 
248     /**
249      * The current {@link InputMethodSubtype} of the current input method.
250      *
251      * @return the current {@link InputMethodSubtype} of the current input method. {@code null}
252      *         means that there is no {@link InputMethodSubtype} currently selected
253      */
254     @GuardedBy("ImfLock.class")
255     @Nullable
getCurrentSubtype()256     InputMethodSubtype getCurrentSubtype() {
257         return mCurrentSubtype;
258     }
259 
260     /**
261      * Sets the current {@link InputMethodSubtype} of the current input method.
262      *
263      * @param currentSubtype the current {@link InputMethodSubtype} of the current input method
264      */
265     @GuardedBy("ImfLock.class")
setCurrentSubtype(@ullable InputMethodSubtype currentSubtype)266     void setCurrentSubtype(@Nullable InputMethodSubtype currentSubtype) {
267         mCurrentSubtype = currentSubtype;
268     }
269 
270     /**
271      * Returns the displayId associated with {@link #getCurToken()}.
272      *
273      * @return the displayId associated with {@link #getCurToken()}. {@link Display#INVALID_DISPLAY}
274      *         while {@link #getCurToken()} returns {@code null}
275      */
276     @GuardedBy("ImfLock.class")
getCurTokenDisplayId()277     int getCurTokenDisplayId() {
278         return mCurTokenDisplayId;
279     }
280 
281     /**
282      * The Intent used to connect to the current input method.
283      */
284     @GuardedBy("ImfLock.class")
285     @Nullable
getCurIntent()286     Intent getCurIntent() {
287         return mCurIntent;
288     }
289 
290     /**
291      * The current binding sequence number, incremented every time there is
292      * a new bind performed.
293      */
294     @GuardedBy("ImfLock.class")
getSequenceNumber()295     int getSequenceNumber() {
296         return mCurSeq;
297     }
298 
299     /**
300      * Increase the current binding sequence number by one.
301      * Reset to 1 on overflow.
302      */
303     @GuardedBy("ImfLock.class")
advanceSequenceNumber()304     void advanceSequenceNumber() {
305         mCurSeq += 1;
306         if (mCurSeq <= 0) {
307             mCurSeq = 1;
308         }
309     }
310 
311     /**
312      * If non-null, this is the input method service we are currently connected
313      * to.
314      */
315     @GuardedBy("ImfLock.class")
316     @Nullable
getCurMethod()317     IInputMethodInvoker getCurMethod() {
318         return mCurMethod;
319     }
320 
321     /**
322      * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}.
323      */
324     @GuardedBy("ImfLock.class")
getCurMethodUid()325     int getCurMethodUid() {
326         return mCurMethodUid;
327     }
328 
329     /**
330      * Indicates whether {@link #mVisibleConnection} is currently in use.
331      */
332     @GuardedBy("ImfLock.class")
isVisibleBound()333     boolean isVisibleBound() {
334         return mVisibleBound;
335     }
336 
337     /**
338      * Returns {@code true} if current IME supports Stylus Handwriting.
339      */
340     @GuardedBy("ImfLock.class")
supportsStylusHandwriting()341     boolean supportsStylusHandwriting() {
342         return mSupportsStylusHw;
343     }
344 
345     /** Returns whether the current IME supports connectionless stylus handwriting sessions. */
346     @GuardedBy("ImfLock.class")
supportsConnectionlessStylusHandwriting()347     boolean supportsConnectionlessStylusHandwriting() {
348         return mSupportsConnectionlessStylusHw;
349     }
350 
351     /**
352      * Used to bring IME service up to visible adjustment while it is being shown.
353      */
354     @GuardedBy("ImfLock.class")
355     private final ServiceConnection mVisibleConnection = new ServiceConnection() {
356         @Override public void onBindingDied(ComponentName name) {
357             synchronized (ImfLock.class) {
358                 mAutofillController.invalidateAutofillSession();
359                 if (isVisibleBound()) {
360                     unbindVisibleConnection();
361                 }
362             }
363         }
364 
365         @Override public void onServiceConnected(ComponentName name, IBinder service) {
366         }
367 
368         @Override public void onServiceDisconnected(ComponentName name) {
369             synchronized (ImfLock.class) {
370                 mAutofillController.invalidateAutofillSession();
371             }
372         }
373     };
374 
375     /**
376      * Used to bind the IME while it is not currently being shown.
377      */
378     @GuardedBy("ImfLock.class")
379     private final ServiceConnection mMainConnection = new ServiceConnection() {
380         @Override
381         public void onServiceConnected(ComponentName name, IBinder service) {
382             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected");
383             synchronized (ImfLock.class) {
384                 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
385                     mCurMethod = IInputMethodInvoker.create(IInputMethod.Stub.asInterface(service));
386                     updateCurrentMethodUid();
387                     if (mCurToken == null) {
388                         Slog.w(TAG, "Service connected without a token!");
389                         unbindCurrentMethod();
390                         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
391                         return;
392                     }
393                     if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
394                     final InputMethodInfo info =
395                             InputMethodSettingsRepository.get(mUserId).getMethodMap().get(
396                                     mSelectedMethodId);
397                     boolean supportsStylusHwChanged =
398                             mSupportsStylusHw != info.supportsStylusHandwriting();
399                     mSupportsStylusHw = info.supportsStylusHandwriting();
400                     if (supportsStylusHwChanged) {
401                         InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
402                     }
403                     boolean supportsConnectionlessStylusHwChanged =
404                             mSupportsConnectionlessStylusHw
405                                     != info.supportsConnectionlessStylusHandwriting();
406                     if (supportsConnectionlessStylusHwChanged) {
407                         mSupportsConnectionlessStylusHw =
408                                 info.supportsConnectionlessStylusHandwriting();
409                         InputMethodManager
410                                 .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
411                     }
412                     mService.initializeImeLocked(mCurMethod, mCurToken,
413                             InputMethodBindingController.this);
414                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
415                     mService.reRequestCurrentClientSessionLocked(mUserId);
416                     mAutofillController.performOnCreateInlineSuggestionsRequest();
417                 }
418 
419                 // reset Handwriting event receiver.
420                 // always call this as it handles changes in mSupportsStylusHw. It is a noop
421                 // if unchanged.
422                 mService.scheduleResetStylusHandwriting();
423             }
424             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
425 
426             if (mLatchForTesting != null) {
427                 mLatchForTesting.countDown(); // Notify the finish to tests
428             }
429         }
430 
431         @GuardedBy("ImfLock.class")
432         private void updateCurrentMethodUid() {
433             final String curMethodPackage = mCurIntent.getComponent().getPackageName();
434             final int curMethodUid = mPackageManagerInternal.getPackageUid(
435                     curMethodPackage, 0 /* flags */, mUserId);
436             if (curMethodUid < 0) {
437                 Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
438                 mCurMethodUid = Process.INVALID_UID;
439             } else {
440                 mCurMethodUid = curMethodUid;
441             }
442         }
443 
444         @Override
445         public void onServiceDisconnected(@NonNull ComponentName name) {
446             // Note that mContext.unbindService(this) does not trigger this.  Hence if we are
447             // here the
448             // disconnection is not intended by IMMS (e.g. triggered because the current IMS
449             // crashed),
450             // which is irregular but can eventually happen for everyone just by continuing
451             // using the
452             // device.  Thus it is important to make sure that all the internal states are
453             // properly
454             // refreshed when this method is called back.  Running
455             //    adb install -r <APK that implements the current IME>
456             // would be a good way to trigger such a situation.
457             synchronized (ImfLock.class) {
458                 if (DEBUG) {
459                     Slog.v(TAG, "Service disconnected: " + name + " mCurIntent=" + mCurIntent);
460                 }
461                 if (mCurMethod != null && mCurIntent != null
462                         && name.equals(mCurIntent.getComponent())) {
463                     // We consider this to be a new bind attempt, since the system
464                     // should now try to restart the service for us.
465                     mLastBindTime = SystemClock.uptimeMillis();
466                     clearCurMethodAndSessions();
467                     final var userData = mService.getUserData(mUserId);
468                     userData.mVisibilityStateComputer.setInputShown(false);
469                     mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME, mUserId);
470                 }
471             }
472         }
473     };
474 
475     @GuardedBy("ImfLock.class")
invalidateAutofillSession()476     void invalidateAutofillSession() {
477         mAutofillController.invalidateAutofillSession();
478     }
479 
480     @GuardedBy("ImfLock.class")
onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback callback, boolean touchExplorationEnabled)481     void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
482             InlineSuggestionsRequestCallback callback, boolean touchExplorationEnabled) {
483         mAutofillController.onCreateInlineSuggestionsRequest(requestInfo, callback,
484                 touchExplorationEnabled);
485     }
486 
487     @GuardedBy("ImfLock.class")
488     @Nullable
getCurHostInputToken()489     IBinder getCurHostInputToken() {
490         return mAutofillController.getCurHostInputToken();
491     }
492 
493     @GuardedBy("ImfLock.class")
unbindCurrentMethod()494     void unbindCurrentMethod() {
495         if (isVisibleBound()) {
496             unbindVisibleConnection();
497         }
498 
499         if (hasMainConnection()) {
500             unbindMainConnection();
501         }
502 
503         if (getCurToken() != null) {
504             mService.resetSystemUiLocked(this);
505             removeCurrentToken();
506             mAutofillController.onResetSystemUi();
507         }
508 
509         mCurId = null;
510         clearCurMethodAndSessions();
511     }
512 
513     @GuardedBy("ImfLock.class")
clearCurMethodAndSessions()514     private void clearCurMethodAndSessions() {
515         mService.clearClientSessionsLocked(this);
516         mCurMethod = null;
517         mCurMethodUid = Process.INVALID_UID;
518     }
519 
520     @GuardedBy("ImfLock.class")
removeCurrentToken()521     private void removeCurrentToken() {
522         if (DEBUG) {
523             Slog.v(TAG,
524                     "Removing window token: " + mCurToken + " for display: " + mCurTokenDisplayId);
525         }
526         mWindowManagerInternal.removeWindowToken(mCurToken, true /* removeWindows */,
527                 false /* animateExit */, mCurTokenDisplayId);
528         mCurToken = null;
529         mCurTokenDisplayId = INVALID_DISPLAY;
530     }
531 
532     @GuardedBy("ImfLock.class")
533     @NonNull
bindCurrentMethod()534     InputBindResult bindCurrentMethod() {
535         if (mSelectedMethodId == null) {
536             Slog.e(TAG, "mSelectedMethodId is null!");
537             return InputBindResult.NO_IME;
538         }
539 
540         InputMethodInfo info = InputMethodSettingsRepository.get(mUserId).getMethodMap().get(
541                 mSelectedMethodId);
542         if (info == null) {
543             throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
544         }
545 
546         mCurIntent = createImeBindingIntent(info.getComponent());
547 
548         if (bindCurrentInputMethodServiceMainConnection()) {
549             mCurId = info.getId();
550             mLastBindTime = SystemClock.uptimeMillis();
551 
552             mCurToken = new Binder();
553             mCurTokenDisplayId = mDisplayIdToShowIme;
554             if (DEBUG) {
555                 Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
556                         + mDisplayIdToShowIme);
557             }
558             mWindowManagerInternal.addWindowToken(mCurToken,
559                     WindowManager.LayoutParams.TYPE_INPUT_METHOD,
560                     mDisplayIdToShowIme, null /* options */);
561             return new InputBindResult(
562                     InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
563                     null, null, null, mCurId, mCurSeq, false);
564         }
565 
566         Slog.w(InputMethodManagerService.TAG,
567                 "Failure connecting to input method service: " + mCurIntent);
568         mCurIntent = null;
569         return InputBindResult.IME_NOT_CONNECTED;
570     }
571 
572     @NonNull
createImeBindingIntent(ComponentName component)573     private Intent createImeBindingIntent(ComponentName component) {
574         Intent intent = new Intent(InputMethod.SERVICE_INTERFACE);
575         intent.setComponent(component);
576         intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
577                 com.android.internal.R.string.input_method_binding_label);
578         var options = ActivityOptions.makeBasic()
579                 .setPendingIntentCreatorBackgroundActivityStartMode(
580                         MODE_BACKGROUND_ACTIVITY_START_DENIED);
581         intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
582                 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
583                 PendingIntent.FLAG_IMMUTABLE, options.toBundle()));
584         return intent;
585     }
586 
587     @GuardedBy("ImfLock.class")
unbindMainConnection()588     private void unbindMainConnection() {
589         mContext.unbindService(mMainConnection);
590         mHasMainConnection = false;
591     }
592 
593     @GuardedBy("ImfLock.class")
unbindVisibleConnection()594     void unbindVisibleConnection() {
595         mContext.unbindService(mVisibleConnection);
596         mVisibleBound = false;
597     }
598 
599     @GuardedBy("ImfLock.class")
bindCurrentInputMethodService(ServiceConnection conn, int flags)600     private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
601         if (mCurIntent == null || conn == null) {
602             Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
603             return false;
604         }
605         return mContext.bindServiceAsUser(mCurIntent, conn, flags, new UserHandle(mUserId));
606     }
607 
608     @GuardedBy("ImfLock.class")
bindCurrentInputMethodServiceMainConnection()609     private boolean bindCurrentInputMethodServiceMainConnection() {
610         mHasMainConnection = bindCurrentInputMethodService(mMainConnection,
611                 mImeConnectionBindFlags);
612         return mHasMainConnection;
613     }
614 
615     /**
616      * Bind the IME so that it can be shown.
617      *
618      * <p>
619      * Performs a rebind if no binding is achieved in {@link #TIME_TO_RECONNECT} milliseconds.
620      */
621     @GuardedBy("ImfLock.class")
setCurrentMethodVisible()622     void setCurrentMethodVisible() {
623         if (mCurMethod != null) {
624             if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
625             if (hasMainConnection() && !isVisibleBound()) {
626                 mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
627                         IME_VISIBLE_BIND_FLAGS);
628             }
629             return;
630         }
631 
632         // No IME is currently connected. Reestablish the main connection.
633         if (!hasMainConnection()) {
634             if (DEBUG) {
635                 Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
636             }
637             bindCurrentMethod();
638             return;
639         }
640 
641         long bindingDuration = SystemClock.uptimeMillis() - mLastBindTime;
642         if (bindingDuration >= TIME_TO_RECONNECT) {
643             // The client has asked to have the input method shown, but
644             // we have been sitting here too long with a connection to the
645             // service and no interface received, so let's disconnect/connect
646             // to try to prod things along.
647             EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
648                     bindingDuration, 1);
649             Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisible()");
650             unbindMainConnection();
651             bindCurrentInputMethodServiceMainConnection();
652         } else {
653             if (DEBUG) {
654                 Slog.d(TAG, "Can't show input: connection = " + mHasMainConnection + ", time = "
655                         + (TIME_TO_RECONNECT - bindingDuration));
656             }
657         }
658     }
659 
660     /**
661      * Remove the binding needed for the IME to be shown.
662      */
663     @GuardedBy("ImfLock.class")
setCurrentMethodNotVisible()664     void setCurrentMethodNotVisible() {
665         if (isVisibleBound()) {
666             unbindVisibleConnection();
667         }
668     }
669 
670     /**
671      * Returns the current {@link InputMethodSubtype}.
672      *
673      * <p>Also this method has had questionable behaviors:</p>
674      * <ul>
675      *     <li>Calling this method can update {@link #mCurrentSubtype}.</li>
676      *     <li>This method may return {@link #mCurrentSubtype} as-is, even if it does not belong to
677      *     the current IME.</li>
678      * </ul>
679      * <p>TODO(b/347083680): Address above issues.</p>
680      */
681     @GuardedBy("ImfLock.class")
682     @Nullable
getCurrentInputMethodSubtype()683     InputMethodSubtype getCurrentInputMethodSubtype() {
684         final var selectedMethodId = getSelectedMethodId();
685         if (selectedMethodId == null) {
686             return null;
687         }
688         final InputMethodSettings settings = InputMethodSettingsRepository.get(mUserId);
689         final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
690         if (imi == null || imi.getSubtypeCount() == 0) {
691             return null;
692         }
693         final var subtype = SubtypeUtils.getCurrentInputMethodSubtype(imi, settings,
694                 mCurrentSubtype);
695         mCurrentSubtype = subtype;
696         return subtype;
697     }
698 
699 
700     @GuardedBy("ImfLock.class")
setDisplayIdToShowIme(int displayId)701     void setDisplayIdToShowIme(int displayId) {
702         mDisplayIdToShowIme = displayId;
703     }
704 
705     @GuardedBy("ImfLock.class")
getDisplayIdToShowIme()706     int getDisplayIdToShowIme() {
707         return mDisplayIdToShowIme;
708     }
709 
710     @GuardedBy("ImfLock.class")
setDeviceIdToShowIme(int deviceId)711     void setDeviceIdToShowIme(int deviceId) {
712         mDeviceIdToShowIme = deviceId;
713     }
714 
715     @GuardedBy("ImfLock.class")
getDeviceIdToShowIme()716     int getDeviceIdToShowIme() {
717         return mDeviceIdToShowIme;
718     }
719 
720     @UserIdInt
getUserId()721     int getUserId() {
722         return mUserId;
723     }
724 
725     @GuardedBy("ImfLock.class")
setImeWindowVis(@meWindowVisibility int imeWindowVis)726     void setImeWindowVis(@ImeWindowVisibility int imeWindowVis) {
727         mImeWindowVis = imeWindowVis;
728     }
729 
730     @ImeWindowVisibility
731     @GuardedBy("ImfLock.class")
getImeWindowVis()732     int getImeWindowVis() {
733         return mImeWindowVis;
734     }
735 
736     @BackDispositionMode
737     @GuardedBy("ImfLock.class")
getBackDisposition()738     int getBackDisposition() {
739         return mBackDisposition;
740     }
741 
742     @GuardedBy("ImfLock.class")
setBackDisposition(@ackDispositionMode int backDisposition)743     void setBackDisposition(@BackDispositionMode int backDisposition) {
744         mBackDisposition = backDisposition;
745     }
746 
747     @GuardedBy("ImfLock.class")
dump(@onNull PrintWriter pw, @NonNull String prefix)748     void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
749         pw.println(prefix + "mSelectedMethodId=" + mSelectedMethodId);
750         pw.println(prefix + "mCurrentSubtype=" + mCurrentSubtype);
751         pw.println(prefix + "mCurSeq=" + mCurSeq);
752         pw.println(prefix + "mCurId=" + mCurId);
753         pw.println(prefix + "mHasMainConnection=" + mHasMainConnection);
754         pw.println(prefix + "mVisibleBound=" + mVisibleBound);
755         pw.println(prefix + "mCurToken=" + mCurToken);
756         pw.println(prefix + "mCurTokenDisplayId=" + mCurTokenDisplayId);
757         pw.println(prefix + "mCurHostInputToken=" + getCurHostInputToken());
758         pw.println(prefix + "mCurIntent=" + mCurIntent);
759         pw.println(prefix + "mCurMethod=" + mCurMethod);
760         pw.println(prefix + "mImeWindowVis=" + mImeWindowVis);
761         pw.println(prefix + "mBackDisposition=" + mBackDisposition);
762         pw.println(prefix + "mDisplayIdToShowIme=" + mDisplayIdToShowIme);
763         pw.println(prefix + "mDeviceIdToShowIme=" + mDeviceIdToShowIme);
764         pw.println(prefix + "mSupportsStylusHw=" + mSupportsStylusHw);
765         pw.println(prefix + "mSupportsConnectionlessStylusHw=" + mSupportsConnectionlessStylusHw);
766     }
767 }
768