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.os.Trace.TRACE_TAG_WINDOW_MANAGER; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.PendingIntent; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.ServiceConnection; 28 import android.content.pm.PackageManagerInternal; 29 import android.content.res.Resources; 30 import android.inputmethodservice.InputMethodService; 31 import android.os.Binder; 32 import android.os.IBinder; 33 import android.os.Process; 34 import android.os.RemoteException; 35 import android.os.SystemClock; 36 import android.os.Trace; 37 import android.os.UserHandle; 38 import android.provider.Settings; 39 import android.util.ArrayMap; 40 import android.util.EventLog; 41 import android.util.Slog; 42 import android.view.IWindowManager; 43 import android.view.WindowManager; 44 import android.view.inputmethod.InputMethod; 45 import android.view.inputmethod.InputMethodInfo; 46 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.internal.inputmethod.InputBindResult; 49 import com.android.internal.inputmethod.UnbindReason; 50 import com.android.internal.view.IInputMethod; 51 import com.android.server.EventLogTags; 52 import com.android.server.wm.WindowManagerInternal; 53 54 /** 55 * A controller managing the state of the input method binding. 56 */ 57 final class InputMethodBindingController { 58 static final boolean DEBUG = false; 59 private static final String TAG = InputMethodBindingController.class.getSimpleName(); 60 61 /** Time in milliseconds that the IME service has to bind before it is reconnected. */ 62 static final long TIME_TO_RECONNECT = 3 * 1000; 63 64 @NonNull private final InputMethodManagerService mService; 65 @NonNull private final Context mContext; 66 @NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap; 67 @NonNull private final InputMethodUtils.InputMethodSettings mSettings; 68 @NonNull private final PackageManagerInternal mPackageManagerInternal; 69 @NonNull private final IWindowManager mIWindowManager; 70 @NonNull private final WindowManagerInternal mWindowManagerInternal; 71 @NonNull private final Resources mRes; 72 73 @GuardedBy("ImfLock.class") private long mLastBindTime; 74 @GuardedBy("ImfLock.class") private boolean mHasConnection; 75 @GuardedBy("ImfLock.class") @Nullable private String mCurId; 76 @GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId; 77 @GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent; 78 @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod; 79 @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID; 80 @GuardedBy("ImfLock.class") private IBinder mCurToken; 81 @GuardedBy("ImfLock.class") private int mCurSeq; 82 @GuardedBy("ImfLock.class") private boolean mVisibleBound; 83 private boolean mSupportsStylusHw; 84 85 /** 86 * Binding flags for establishing connection to the {@link InputMethodService}. 87 */ 88 private static final int IME_CONNECTION_BIND_FLAGS = 89 Context.BIND_AUTO_CREATE 90 | Context.BIND_NOT_VISIBLE 91 | Context.BIND_NOT_FOREGROUND 92 | Context.BIND_IMPORTANT_BACKGROUND; 93 /** 94 * Binding flags used only while the {@link InputMethodService} is showing window. 95 */ 96 private static final int IME_VISIBLE_BIND_FLAGS = 97 Context.BIND_AUTO_CREATE 98 | Context.BIND_TREAT_LIKE_ACTIVITY 99 | Context.BIND_FOREGROUND_SERVICE 100 | Context.BIND_INCLUDE_CAPABILITIES 101 | Context.BIND_SHOWING_UI 102 | Context.BIND_SCHEDULE_LIKE_TOP_APP; 103 InputMethodBindingController(@onNull InputMethodManagerService service)104 InputMethodBindingController(@NonNull InputMethodManagerService service) { 105 mService = service; 106 mContext = mService.mContext; 107 mMethodMap = mService.mMethodMap; 108 mSettings = mService.mSettings; 109 mPackageManagerInternal = mService.mPackageManagerInternal; 110 mIWindowManager = mService.mIWindowManager; 111 mWindowManagerInternal = mService.mWindowManagerInternal; 112 mRes = mService.mRes; 113 } 114 115 /** 116 * Time that we last initiated a bind to the input method, to determine 117 * if we should try to disconnect and reconnect to it. 118 */ 119 @GuardedBy("ImfLock.class") getLastBindTime()120 long getLastBindTime() { 121 return mLastBindTime; 122 } 123 124 /** 125 * Set to true if our ServiceConnection is currently actively bound to 126 * a service (whether or not we have gotten its IBinder back yet). 127 */ 128 @GuardedBy("ImfLock.class") hasConnection()129 boolean hasConnection() { 130 return mHasConnection; 131 } 132 133 /** 134 * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently 135 * connected to or in the process of connecting to. 136 * 137 * <p>This can be {@code null} when no input method is connected.</p> 138 * 139 * @see #getSelectedMethodId() 140 */ 141 @GuardedBy("ImfLock.class") 142 @Nullable getCurId()143 String getCurId() { 144 return mCurId; 145 } 146 147 /** 148 * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method. 149 * This is to be synchronized with the secure settings keyed with 150 * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD}. 151 * 152 * <p>This can be transiently {@code null} when the system is re-initializing input method 153 * settings, e.g., the system locale is just changed.</p> 154 * 155 * <p>Note that {@link #getCurId()} is used to track which IME is being connected to 156 * {@link com.android.server.inputmethod.InputMethodManagerService}.</p> 157 * 158 * @see #getCurId() 159 */ 160 @GuardedBy("ImfLock.class") 161 @Nullable getSelectedMethodId()162 String getSelectedMethodId() { 163 return mSelectedMethodId; 164 } 165 166 @GuardedBy("ImfLock.class") setSelectedMethodId(@ullable String selectedMethodId)167 void setSelectedMethodId(@Nullable String selectedMethodId) { 168 mSelectedMethodId = selectedMethodId; 169 } 170 171 /** 172 * The token we have made for the currently active input method, to 173 * identify it in the future. 174 */ 175 @GuardedBy("ImfLock.class") getCurToken()176 IBinder getCurToken() { 177 return mCurToken; 178 } 179 180 /** 181 * The Intent used to connect to the current input method. 182 */ 183 @GuardedBy("ImfLock.class") 184 @Nullable getCurIntent()185 Intent getCurIntent() { 186 return mCurIntent; 187 } 188 189 /** 190 * The current binding sequence number, incremented every time there is 191 * a new bind performed. 192 */ 193 @GuardedBy("ImfLock.class") getSequenceNumber()194 int getSequenceNumber() { 195 return mCurSeq; 196 } 197 198 /** 199 * Increase the current binding sequence number by one. 200 * Reset to 1 on overflow. 201 */ 202 @GuardedBy("ImfLock.class") advanceSequenceNumber()203 void advanceSequenceNumber() { 204 mCurSeq += 1; 205 if (mCurSeq <= 0) { 206 mCurSeq = 1; 207 } 208 } 209 210 /** 211 * If non-null, this is the input method service we are currently connected 212 * to. 213 */ 214 @GuardedBy("ImfLock.class") 215 @Nullable getCurMethod()216 IInputMethodInvoker getCurMethod() { 217 return mCurMethod; 218 } 219 220 /** 221 * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}. 222 */ 223 @GuardedBy("ImfLock.class") getCurMethodUid()224 int getCurMethodUid() { 225 return mCurMethodUid; 226 } 227 228 /** 229 * Indicates whether {@link #mVisibleConnection} is currently in use. 230 */ 231 @GuardedBy("ImfLock.class") isVisibleBound()232 boolean isVisibleBound() { 233 return mVisibleBound; 234 } 235 236 /** 237 * Returns {@code true} if current IME supports Stylus Handwriting. 238 */ supportsStylusHandwriting()239 boolean supportsStylusHandwriting() { 240 return mSupportsStylusHw; 241 } 242 243 /** 244 * Used to bring IME service up to visible adjustment while it is being shown. 245 */ 246 @GuardedBy("ImfLock.class") 247 private final ServiceConnection mVisibleConnection = new ServiceConnection() { 248 @Override public void onBindingDied(ComponentName name) { 249 synchronized (ImfLock.class) { 250 mService.invalidateAutofillSessionLocked(); 251 if (mVisibleBound) { 252 unbindVisibleConnection(); 253 } 254 } 255 } 256 257 @Override public void onServiceConnected(ComponentName name, IBinder service) { 258 } 259 260 @Override public void onServiceDisconnected(ComponentName name) { 261 synchronized (ImfLock.class) { 262 mService.invalidateAutofillSessionLocked(); 263 } 264 } 265 }; 266 267 /** 268 * Used to bind the IME while it is not currently being shown. 269 */ 270 @GuardedBy("ImfLock.class") 271 private final ServiceConnection mMainConnection = new ServiceConnection() { 272 @Override 273 public void onServiceConnected(ComponentName name, IBinder service) { 274 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected"); 275 synchronized (ImfLock.class) { 276 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { 277 mCurMethod = IInputMethodInvoker.create(IInputMethod.Stub.asInterface(service)); 278 updateCurrentMethodUid(); 279 if (mCurToken == null) { 280 Slog.w(TAG, "Service connected without a token!"); 281 unbindCurrentMethod(); 282 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 283 return; 284 } 285 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); 286 final InputMethodInfo info = mMethodMap.get(mSelectedMethodId); 287 mSupportsStylusHw = info.supportsStylusHandwriting(); 288 mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges(), 289 mSupportsStylusHw); 290 mService.scheduleNotifyImeUidToAudioService(mCurMethodUid); 291 mService.reRequestCurrentClientSessionLocked(); 292 mService.performOnCreateInlineSuggestionsRequestLocked(); 293 } 294 295 // reset Handwriting event receiver. 296 // always call this as it handles changes in mSupportsStylusHw. It is a noop 297 // if unchanged. 298 mService.scheduleResetStylusHandwriting(); 299 } 300 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 301 } 302 303 @GuardedBy("ImfLock.class") 304 private void updateCurrentMethodUid() { 305 final String curMethodPackage = mCurIntent.getComponent().getPackageName(); 306 final int curMethodUid = mPackageManagerInternal.getPackageUid( 307 curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId()); 308 if (curMethodUid < 0) { 309 Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage); 310 mCurMethodUid = Process.INVALID_UID; 311 } else { 312 mCurMethodUid = curMethodUid; 313 } 314 } 315 316 @Override 317 public void onServiceDisconnected(@NonNull ComponentName name) { 318 // Note that mContext.unbindService(this) does not trigger this. Hence if we are 319 // here the 320 // disconnection is not intended by IMMS (e.g. triggered because the current IMS 321 // crashed), 322 // which is irregular but can eventually happen for everyone just by continuing 323 // using the 324 // device. Thus it is important to make sure that all the internal states are 325 // properly 326 // refreshed when this method is called back. Running 327 // adb install -r <APK that implements the current IME> 328 // would be a good way to trigger such a situation. 329 synchronized (ImfLock.class) { 330 if (DEBUG) { 331 Slog.v(TAG, "Service disconnected: " + name + " mCurIntent=" + mCurIntent); 332 } 333 if (mCurMethod != null && mCurIntent != null 334 && name.equals(mCurIntent.getComponent())) { 335 // We consider this to be a new bind attempt, since the system 336 // should now try to restart the service for us. 337 mLastBindTime = SystemClock.uptimeMillis(); 338 clearCurMethodAndSessions(); 339 mService.clearInputShowRequestLocked(); 340 mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME); 341 } 342 } 343 } 344 }; 345 346 @GuardedBy("ImfLock.class") unbindCurrentMethod()347 void unbindCurrentMethod() { 348 if (mVisibleBound) { 349 unbindVisibleConnection(); 350 } 351 352 if (mHasConnection) { 353 unbindMainConnection(); 354 } 355 356 if (mCurToken != null) { 357 removeCurrentToken(); 358 mService.resetSystemUiLocked(); 359 } 360 361 mCurId = null; 362 clearCurMethodAndSessions(); 363 } 364 365 @GuardedBy("ImfLock.class") clearCurMethodAndSessions()366 private void clearCurMethodAndSessions() { 367 mService.clearClientSessionsLocked(); 368 mCurMethod = null; 369 mCurMethodUid = Process.INVALID_UID; 370 } 371 372 @GuardedBy("ImfLock.class") removeCurrentToken()373 private void removeCurrentToken() { 374 int curTokenDisplayId = mService.getCurTokenDisplayIdLocked(); 375 376 if (DEBUG) { 377 Slog.v(TAG, 378 "Removing window token: " + mCurToken + " for display: " + curTokenDisplayId); 379 } 380 mWindowManagerInternal.removeWindowToken(mCurToken, false /* removeWindows */, 381 false /* animateExit */, curTokenDisplayId); 382 mCurToken = null; 383 } 384 385 @GuardedBy("ImfLock.class") 386 @NonNull bindCurrentMethod()387 InputBindResult bindCurrentMethod() { 388 if (mSelectedMethodId == null) { 389 Slog.e(TAG, "mSelectedMethodId is null!"); 390 return InputBindResult.NO_IME; 391 } 392 393 InputMethodInfo info = mMethodMap.get(mSelectedMethodId); 394 if (info == null) { 395 throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId); 396 } 397 398 mCurIntent = createImeBindingIntent(info.getComponent()); 399 400 if (bindCurrentInputMethodServiceMainConnection()) { 401 mCurId = info.getId(); 402 mLastBindTime = SystemClock.uptimeMillis(); 403 404 addFreshWindowToken(); 405 return new InputBindResult( 406 InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, 407 null, null, null, mCurId, mCurSeq, null, false); 408 } 409 410 Slog.w(InputMethodManagerService.TAG, 411 "Failure connecting to input method service: " + mCurIntent); 412 mCurIntent = null; 413 return InputBindResult.IME_NOT_CONNECTED; 414 } 415 416 @NonNull createImeBindingIntent(ComponentName component)417 private Intent createImeBindingIntent(ComponentName component) { 418 Intent intent = new Intent(InputMethod.SERVICE_INTERFACE); 419 intent.setComponent(component); 420 intent.putExtra(Intent.EXTRA_CLIENT_LABEL, 421 com.android.internal.R.string.input_method_binding_label); 422 intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( 423 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 424 PendingIntent.FLAG_IMMUTABLE)); 425 return intent; 426 } 427 428 @GuardedBy("ImfLock.class") addFreshWindowToken()429 private void addFreshWindowToken() { 430 int displayIdToShowIme = mService.getDisplayIdToShowImeLocked(); 431 mCurToken = new Binder(); 432 433 mService.setCurTokenDisplayIdLocked(displayIdToShowIme); 434 435 try { 436 if (DEBUG) { 437 Slog.v(TAG, "Adding window token: " + mCurToken + " for display: " 438 + displayIdToShowIme); 439 } 440 mIWindowManager.addWindowToken(mCurToken, WindowManager.LayoutParams.TYPE_INPUT_METHOD, 441 displayIdToShowIme, null /* options */); 442 } catch (RemoteException e) { 443 Slog.e(TAG, "Could not add window token " + mCurToken + " for display " 444 + displayIdToShowIme, e); 445 } 446 } 447 448 @GuardedBy("ImfLock.class") unbindMainConnection()449 private void unbindMainConnection() { 450 mContext.unbindService(mMainConnection); 451 mHasConnection = false; 452 } 453 454 @GuardedBy("ImfLock.class") unbindVisibleConnection()455 void unbindVisibleConnection() { 456 mContext.unbindService(mVisibleConnection); 457 mVisibleBound = false; 458 } 459 460 @GuardedBy("ImfLock.class") bindCurrentInputMethodService(ServiceConnection conn, int flags)461 private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) { 462 if (mCurIntent == null || conn == null) { 463 Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn); 464 return false; 465 } 466 return mContext.bindServiceAsUser(mCurIntent, conn, flags, 467 new UserHandle(mSettings.getCurrentUserId())); 468 } 469 470 @GuardedBy("ImfLock.class") bindCurrentInputMethodServiceVisibleConnection()471 private boolean bindCurrentInputMethodServiceVisibleConnection() { 472 mVisibleBound = bindCurrentInputMethodService(mVisibleConnection, 473 IME_VISIBLE_BIND_FLAGS); 474 return mVisibleBound; 475 } 476 477 @GuardedBy("ImfLock.class") bindCurrentInputMethodServiceMainConnection()478 private boolean bindCurrentInputMethodServiceMainConnection() { 479 mHasConnection = bindCurrentInputMethodService(mMainConnection, IME_CONNECTION_BIND_FLAGS); 480 return mHasConnection; 481 } 482 483 /** 484 * Bind the IME so that it can be shown. 485 * 486 * <p> 487 * Performs a rebind if no binding is achieved in {@link #TIME_TO_RECONNECT} milliseconds. 488 */ 489 @GuardedBy("ImfLock.class") setCurrentMethodVisible()490 void setCurrentMethodVisible() { 491 if (mCurMethod != null) { 492 if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken); 493 if (mHasConnection && !mVisibleBound) { 494 bindCurrentInputMethodServiceVisibleConnection(); 495 } 496 return; 497 } 498 499 // No IME is currently connected. Reestablish the main connection. 500 if (!mHasConnection) { 501 if (DEBUG) { 502 Slog.d(TAG, "Cannot show input: no IME bound. Rebinding."); 503 } 504 bindCurrentMethod(); 505 return; 506 } 507 508 long bindingDuration = SystemClock.uptimeMillis() - mLastBindTime; 509 if (bindingDuration >= TIME_TO_RECONNECT) { 510 // The client has asked to have the input method shown, but 511 // we have been sitting here too long with a connection to the 512 // service and no interface received, so let's disconnect/connect 513 // to try to prod things along. 514 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(), 515 bindingDuration, 1); 516 Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisible()"); 517 unbindMainConnection(); 518 bindCurrentInputMethodServiceMainConnection(); 519 } else { 520 if (DEBUG) { 521 Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = " 522 + (TIME_TO_RECONNECT - bindingDuration)); 523 } 524 } 525 } 526 527 /** 528 * Remove the binding needed for the IME to be shown. 529 */ 530 @GuardedBy("ImfLock.class") setCurrentMethodNotVisible()531 void setCurrentMethodNotVisible() { 532 if (mVisibleBound) { 533 unbindVisibleConnection(); 534 } 535 } 536 } 537