1 /* 2 * Copyright (C) 2019 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.wm; 18 19 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 20 import static android.view.InsetsSource.ID_IME; 21 22 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IME; 23 import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; 24 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; 25 import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME_IDENTIFIER; 26 import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER; 27 import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS; 28 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.graphics.Rect; 32 import android.os.Trace; 33 import android.util.Slog; 34 import android.util.proto.ProtoOutputStream; 35 import android.view.InsetsSource; 36 import android.view.InsetsSourceConsumer; 37 import android.view.InsetsSourceControl; 38 import android.view.WindowInsets; 39 import android.view.inputmethod.Flags; 40 import android.view.inputmethod.ImeTracker; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.inputmethod.SoftInputShowHideReason; 44 import com.android.internal.protolog.ProtoLog; 45 46 import java.io.PrintWriter; 47 48 /** 49 * Controller for IME inset source on the server. It's called provider as it provides the 50 * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}. 51 */ 52 final class ImeInsetsSourceProvider extends InsetsSourceProvider { 53 54 private static final String TAG = ImeInsetsSourceProvider.class.getSimpleName(); 55 56 /** The token tracking the show IME request, non-null only while a show request is pending. */ 57 @Nullable 58 private ImeTracker.Token mStatsToken; 59 /** The target that requested to show the IME, non-null only while a show request is pending. */ 60 @Nullable 61 private InsetsControlTarget mImeRequester; 62 /** @see #isImeShowing() */ 63 private boolean mImeShowing; 64 /** The latest received insets source. */ 65 private final InsetsSource mLastSource = new InsetsSource(ID_IME, WindowInsets.Type.ime()); 66 67 /** @see #setFrozen(boolean) */ 68 private boolean mFrozen; 69 70 /** 71 * The server visibility of the source provider's window container. This is out of sync with 72 * {@link InsetsSourceProvider#mServerVisible} while {@link #mFrozen} is {@code true}. 73 * 74 * @see #setServerVisible 75 */ 76 private boolean mServerVisible; 77 78 /** 79 * When the IME is not ready, it has givenInsetsPending. However, this could happen again, 80 * after it became serverVisible. This flag indicates is used to determine if it is 81 * readyForDispatching 82 */ 83 private boolean mGivenInsetsReady = false; 84 85 /** 86 * The last state of the windowContainer. This is used to reset server visibility, in case of 87 * the IME (temporarily) redrawing (e.g. during a rotation), to dispatch the control with 88 * leash again after it has finished drawing. 89 */ 90 private boolean mLastDrawn = false; 91 ImeInsetsSourceProvider(@onNull InsetsSource source, @NonNull InsetsStateController stateController, @NonNull DisplayContent displayContent)92 ImeInsetsSourceProvider(@NonNull InsetsSource source, 93 @NonNull InsetsStateController stateController, 94 @NonNull DisplayContent displayContent) { 95 super(source, stateController, displayContent); 96 } 97 98 @Override onPostLayout()99 void onPostLayout() { 100 boolean wasServerVisible = mServerVisible; 101 super.onPostLayout(); 102 103 if (android.view.inputmethod.Flags.refactorInsetsController()) { 104 final WindowState ws = 105 mWindowContainer != null ? mWindowContainer.asWindowState() : null; 106 final boolean givenInsetsPending = ws != null && ws.mGivenInsetsPending; 107 mLastDrawn = ws != null && ws.isDrawn(); 108 109 // isLeashReadyForDispatching (used to dispatch the leash of the control) is 110 // depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here 111 // again, so that the control with leash can be eventually dispatched 112 if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending 113 && mControlTarget != null) { 114 ProtoLog.d(WM_DEBUG_IME, 115 "onPostLayout: IME control ready to be dispatched, controlTarget=%s", 116 mControlTarget); 117 mGivenInsetsReady = true; 118 ImeTracker.forLogging().onProgress(mStatsToken, 119 ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED); 120 mStateController.notifyControlChanged(mControlTarget, this); 121 setImeShowing(true); 122 } else if (wasServerVisible && isServerVisible() && mGivenInsetsReady 123 && givenInsetsPending) { 124 // If the server visibility didn't change (still visible), and mGivenInsetsReady 125 // is set, we won't call into notifyControlChanged. Therefore, we can reset the 126 // statsToken, if available. 127 ProtoLog.w(WM_DEBUG_IME, "onPostLayout cancel statsToken, controlTarget=%s", 128 mControlTarget); 129 ImeTracker.forLogging().onCancelled(mStatsToken, 130 ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED); 131 mStatsToken = null; 132 } else if (wasServerVisible && !isServerVisible()) { 133 ProtoLog.d(WM_DEBUG_IME, 134 "onPostLayout: setImeShowing(false) was: %s, controlTarget=%s", 135 isImeShowing(), mControlTarget); 136 setImeShowing(false); 137 } 138 } 139 } 140 141 @Nullable getAndClearStatsToken()142 ImeTracker.Token getAndClearStatsToken() { 143 if (android.view.inputmethod.Flags.refactorInsetsController()) { 144 ImeTracker.Token statsToken = mStatsToken; 145 mStatsToken = null; 146 return statsToken; 147 } else { 148 return null; 149 } 150 } 151 152 @Override isLeashReadyForDispatching()153 protected boolean isLeashReadyForDispatching() { 154 if (android.view.inputmethod.Flags.refactorInsetsController()) { 155 // We should only dispatch the leash, if the following conditions are fulfilled: 156 // 1. parent isLeashReadyForDispatching, 2. mGivenInsetsReady (means there are no 157 // givenInsetsPending), 3. the IME surface is drawn, 4. either the IME is 158 // serverVisible (the unfrozen state) 159 final WindowState ws = 160 mWindowContainer != null ? mWindowContainer.asWindowState() : null; 161 final boolean isDrawn = ws != null && ws.isDrawn(); 162 return super.isLeashReadyForDispatching() 163 && isServerVisible() && isDrawn && mGivenInsetsReady; 164 } else { 165 return super.isLeashReadyForDispatching(); 166 } 167 } 168 169 /** 170 * This is used to determine the desired serverVisibility state. For the IME, just having a 171 * window state that would be visible by policy is not enough. 172 */ 173 @Override isSurfaceVisible()174 protected boolean isSurfaceVisible() { 175 final boolean isSurfaceVisible = super.isSurfaceVisible(); 176 if (android.view.inputmethod.Flags.refactorInsetsController()) { 177 final WindowState windowState = mWindowContainer.asWindowState(); 178 if (mControl != null && windowState != null) { 179 final boolean isDrawn = windowState.isDrawn(); 180 if (!isServerVisible() && isSurfaceVisible) { 181 // In case the IME becomes visible, we need to check if it is already drawn and 182 // does not have given insets pending. If it's not yet drawn, we do not set 183 // server visibility 184 return isDrawn && !windowState.mGivenInsetsPending; 185 } else if (mLastDrawn && !isDrawn) { 186 // If the IME was drawn before, but is not drawn anymore, we need to reset 187 // server visibility, which will also reset {@link 188 // ImeInsetsSourceProvider#mGivenInsetsReady}. Otherwise, the new control 189 // with leash won't be dispatched after the surface has redrawn. 190 return false; 191 } 192 } 193 } 194 return isSurfaceVisible; 195 } 196 197 198 @Nullable 199 @Override getControl(InsetsControlTarget target)200 InsetsSourceControl getControl(InsetsControlTarget target) { 201 final InsetsSourceControl control = super.getControl(target); 202 if (control != null && target != null && target.getWindow() != null) { 203 final WindowState targetWin = target.getWindow(); 204 final Task task = targetWin.getTask(); 205 // If the control target changes during the app transition with the task snapshot 206 // starting window and the IME snapshot is visible, in case not have duplicated IME 207 // showing animation during transitioning, use a flag to inform IME source control to 208 // skip showing animation once. 209 StartingData startingData = null; 210 if (task != null) { 211 startingData = targetWin.mActivityRecord.mStartingData; 212 if (startingData == null) { 213 final WindowState startingWin = task.topStartingWindow(); 214 if (startingWin != null) { 215 startingData = startingWin.mStartingData; 216 } 217 } 218 } 219 control.setSkipAnimationOnce(startingData != null && startingData.hasImeSurface()); 220 } 221 if (android.view.inputmethod.Flags.refactorInsetsController()) { 222 if (control != null && control.getLeash() != null) { 223 ImeTracker.Token statsToken = getAndClearStatsToken(); 224 if (statsToken == null) { 225 ProtoLog.w(WM_DEBUG_IME, 226 "IME getControl without statsToken (check previous request!). " 227 + "Start new request"); 228 // TODO(b/353463205) remove this later after fixing the race of two requests 229 // that cancel each other (cf. b/383466954#comment19). 230 statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, 231 ImeTracker.ORIGIN_SERVER, SoftInputShowHideReason.CONTROLS_CHANGED, 232 false /* fromUser */); 233 } 234 ImeTracker.forLogging().onProgress(statsToken, 235 ImeTracker.PHASE_WM_GET_CONTROL_WITH_LEASH); 236 control.setImeStatsToken(statsToken); 237 } 238 } 239 return control; 240 } 241 242 @Override setClientVisible(boolean clientVisible)243 void setClientVisible(boolean clientVisible) { 244 final boolean wasClientVisible = isClientVisible(); 245 super.setClientVisible(clientVisible); 246 // The layer of ImePlaceholder needs to be updated on a higher z-order for 247 // non-activity window (For activity window, IME is already on top of it). 248 if (!wasClientVisible && isClientVisible()) { 249 final InsetsControlTarget imeControlTarget = getControlTarget(); 250 if (imeControlTarget != null && imeControlTarget.getWindow() != null 251 && imeControlTarget.getWindow().mActivityRecord == null) { 252 mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */); 253 } 254 } 255 } 256 257 @Override setServerVisible(boolean serverVisible)258 void setServerVisible(boolean serverVisible) { 259 if (mServerVisible != serverVisible) { 260 mServerVisible = serverVisible; 261 // reset the leash if the server visibility becomes hidden 262 if (android.view.inputmethod.Flags.refactorInsetsController()) { 263 if (!serverVisible && !mFrozen) { 264 mGivenInsetsReady = false; 265 updateControlForTarget(mControlTarget, true /* force */, null /* statsToken */); 266 } 267 } 268 } 269 if (!mFrozen) { 270 super.setServerVisible(serverVisible); 271 } 272 } 273 274 /** 275 * Freeze IME insets source state when required. 276 * 277 * <p>When setting {@param frozen} as {@code true}, the IME insets provider will freeze the 278 * current IME insets state and pending the IME insets state update until setting 279 * {@param frozen} as {@code false}.</p> 280 */ setFrozen(boolean frozen)281 void setFrozen(boolean frozen) { 282 if (mFrozen == frozen) { 283 return; 284 } 285 mFrozen = frozen; 286 if (!frozen) { 287 // Unfreeze and process the pending IME insets states. 288 super.setServerVisible(mServerVisible); 289 } 290 } 291 292 @Override updateSourceFrame(Rect frame)293 void updateSourceFrame(Rect frame) { 294 super.updateSourceFrame(frame); 295 onSourceChanged(); 296 } 297 298 @Override updateVisibility()299 protected void updateVisibility() { 300 boolean oldVisibility = mSource.isVisible(); 301 super.updateVisibility(); 302 if (Flags.refactorInsetsController()) { 303 if (mSource.isVisible() && !oldVisibility && mControlTarget != null) { 304 reportImeDrawnForOrganizerIfNeeded(mControlTarget); 305 } 306 } 307 onSourceChanged(); 308 } 309 310 @Override updateControlForTarget(@ullable InsetsControlTarget target, boolean force, @NonNull ImeTracker.Token statsToken)311 void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force, 312 @NonNull ImeTracker.Token statsToken) { 313 if (target != null && target.getWindow() != null) { 314 // ime control target could be a different window. 315 // Refer WindowState#getImeControlTarget(). 316 target = target.getWindow().getImeControlTarget(); 317 } 318 // TODO(b/353463205) make sure that the statsToken of all callers is non-null (currently 319 // not the case) 320 super.updateControlForTarget(target, force, statsToken); 321 if (Flags.refactorInsetsController()) { 322 // TODO(b/353463205) investigate if we should fail the statsToken, or if it's only 323 // temporary null. 324 if (target != null && target == mControlTarget) { 325 // If insets target is not available (e.g. RemoteInsetsControlTarget), use current 326 // IME input target to update IME request state. For example, switch from a task 327 // with showing IME to a split-screen task without showing IME. 328 InputTarget imeInputTarget = mDisplayContent.getImeInputTarget(); 329 if (imeInputTarget != target && imeInputTarget != null 330 && imeInputTarget.isRequestedVisible(WindowInsets.Type.ime()) 331 != target.isRequestedVisible(WindowInsets.Type.ime())) { 332 // Only update the controlTarget, if it has a different requested visibility 333 // than the imeInputTarget. Otherwise, updateClientVisibility won't invoke 334 // the listener, as nothing changed. 335 reportImeInputTargetStateToControlTarget(imeInputTarget, target, 336 statsToken); 337 } else { 338 invokeOnImeRequestedChangedListener(target, statsToken); 339 } 340 } else { 341 ProtoLog.w(WM_DEBUG_IME, 342 "Not invoking onImeRequestedChangedListener, target=%s, current " 343 + "controlTarget=%s", 344 target, mControlTarget); 345 } 346 } 347 } 348 349 // TODO(b/353463205) change statsToken to be NonNull, after the flag is permanently enabled 350 @Override updateClientVisibility(InsetsTarget caller, @Nullable ImeTracker.Token statsToken)351 protected boolean updateClientVisibility(InsetsTarget caller, 352 @Nullable ImeTracker.Token statsToken) { 353 InsetsControlTarget controlTarget = getControlTarget(); 354 if (caller != controlTarget) { 355 if (Flags.refactorInsetsController()) { 356 if (isImeInputTarget(caller)) { 357 reportImeInputTargetStateToControlTarget(caller, controlTarget, statsToken); 358 } else { 359 ProtoLog.w(WM_DEBUG_IME, 360 "Tried to update client visibility for non-IME input target %s " 361 + "(current target: %s, IME requested: %s)", caller, 362 mDisplayContent.getImeInputTarget(), 363 caller.isRequestedVisible(WindowInsets.Type.ime())); 364 ImeTracker.forLogging().onFailed(statsToken, 365 ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY); 366 } 367 } 368 return false; 369 } 370 boolean changed = super.updateClientVisibility(caller, statsToken); 371 if (!Flags.refactorInsetsController() && caller instanceof InsetsControlTarget) { 372 if (changed && caller.isRequestedVisible(mSource.getType())) { 373 reportImeDrawnForOrganizerIfNeeded((InsetsControlTarget) caller); 374 } 375 } 376 if (Flags.refactorInsetsController()) { 377 if (changed) { 378 ImeTracker.forLogging().onProgress(statsToken, 379 ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY); 380 invokeOnImeRequestedChangedListener(controlTarget, statsToken); 381 } else { 382 // TODO(b/353463205) check cancelled / failed 383 ImeTracker.forLogging().onCancelled(statsToken, 384 ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY); 385 } 386 } 387 return changed; 388 } 389 onInputTargetChanged(InputTarget target)390 void onInputTargetChanged(InputTarget target) { 391 if (Flags.refactorInsetsController() && target != null) { 392 InsetsControlTarget imeControlTarget = getControlTarget(); 393 if (target != imeControlTarget) { 394 // TODO(b/353463205): check if fromUser=false is correct here 395 boolean imeVisible = target.isRequestedVisible(WindowInsets.Type.ime()); 396 ImeTracker.Token statsToken = ImeTracker.forLogging().onStart( 397 imeVisible ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, 398 ImeTracker.ORIGIN_SERVER, 399 imeVisible ? SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED 400 : SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED, 401 false /* fromUser */); 402 reportImeInputTargetStateToControlTarget(target, imeControlTarget, 403 statsToken); 404 } 405 } 406 } 407 reportImeInputTargetStateToControlTarget(@onNull InsetsTarget imeInsetsTarget, InsetsControlTarget controlTarget, @NonNull ImeTracker.Token statsToken)408 private void reportImeInputTargetStateToControlTarget(@NonNull InsetsTarget imeInsetsTarget, 409 InsetsControlTarget controlTarget, @NonNull ImeTracker.Token statsToken) { 410 // In case of the multi window mode, update the requestedVisibleTypes from 411 // the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController. 412 // Then, trigger onRequestedVisibleTypesChanged for the controlTarget with 413 // its new requested visibility for the IME 414 boolean imeVisible = imeInsetsTarget.isRequestedVisible(WindowInsets.Type.ime()); 415 if (controlTarget != null) { 416 ImeTracker.forLogging().onProgress(statsToken, 417 ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); 418 controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken); 419 } else if (imeInsetsTarget instanceof InsetsControlTarget) { 420 // In case of a virtual display that cannot show the IME, the 421 // controlTarget will be null here, as no controlTarget was set yet. In 422 // that case, proceed similar to the multi window mode (fallback = 423 // RemoteInsetsControlTarget of the default display) 424 controlTarget = mDisplayContent.getImeHostOrFallback( 425 ((InsetsControlTarget) imeInsetsTarget).getWindow()); 426 427 if (controlTarget != null && controlTarget != imeInsetsTarget) { 428 ImeTracker.forLogging().onProgress(statsToken, 429 ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); 430 controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken); 431 // not all virtual displays have an ImeInsetsSourceProvider, so it is not 432 // guaranteed that the IME will be started when the control target reports its 433 // requested visibility back. Thus, invoking the listener here. 434 invokeOnImeRequestedChangedListener((InsetsControlTarget) imeInsetsTarget, 435 statsToken); 436 } else { 437 ImeTracker.forLogging().onFailed(statsToken, 438 ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); 439 } 440 } 441 } 442 443 // TODO(b/353463205) check callers to see if we can make statsToken @NonNull invokeOnImeRequestedChangedListener(InsetsControlTarget controlTarget, @Nullable ImeTracker.Token statsToken)444 private void invokeOnImeRequestedChangedListener(InsetsControlTarget controlTarget, 445 @Nullable ImeTracker.Token statsToken) { 446 final var imeListener = mDisplayContent.mWmService.mOnImeRequestedChangedListener; 447 if (imeListener != null) { 448 if (controlTarget != null) { 449 final boolean imeAnimating = Flags.reportAnimatingInsetsTypes() 450 && (controlTarget.getAnimatingTypes() & WindowInsets.Type.ime()) != 0; 451 final boolean imeVisible = 452 controlTarget.isRequestedVisible(WindowInsets.Type.ime()) || imeAnimating; 453 final var finalStatsToken = statsToken != null ? statsToken 454 : ImeTracker.forLogging().onStart( 455 imeVisible ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, 456 ImeTracker.ORIGIN_SERVER, 457 SoftInputShowHideReason.IME_REQUESTED_CHANGED_LISTENER, 458 false /* fromUser */); 459 ImeTracker.forLogging().onProgress(finalStatsToken, 460 ImeTracker.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY); 461 mDisplayContent.mWmService.mH.post(() -> { 462 ImeTracker.forLogging().onProgress(finalStatsToken, 463 ImeTracker.PHASE_WM_INVOKING_IME_REQUESTED_LISTENER); 464 imeListener.onImeRequestedChanged(controlTarget.getWindowToken(), imeVisible, 465 finalStatsToken); 466 }); 467 } else { 468 ImeTracker.forLogging().onFailed(statsToken, 469 ImeTracker.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY); 470 } 471 } else { 472 // TODO(b/353463205) We could combine the upper if's and remove the additional phase. 473 ImeTracker.forLogging().onFailed(statsToken, 474 ImeTracker.PHASE_WM_DISPATCH_IME_REQUESTED_CHANGED); 475 } 476 } 477 478 @Override onAnimatingTypesChanged(InsetsControlTarget caller, @Nullable ImeTracker.Token statsToken)479 void onAnimatingTypesChanged(InsetsControlTarget caller, 480 @Nullable ImeTracker.Token statsToken) { 481 if (Flags.reportAnimatingInsetsTypes()) { 482 final InsetsControlTarget controlTarget = getControlTarget(); 483 // If the IME is not being requested anymore and the animation is finished, we need to 484 // invoke the listener, to let IMS eventually know 485 if (caller != null && caller == controlTarget && !caller.isRequestedVisible( 486 WindowInsets.Type.ime()) 487 && (caller.getAnimatingTypes() & WindowInsets.Type.ime()) == 0) { 488 ImeTracker.forLogging().onFailed(statsToken, 489 ImeTracker.PHASE_WM_NOTIFY_HIDE_ANIMATION_FINISHED); 490 invokeOnImeRequestedChangedListener(caller, statsToken); 491 } else { 492 ImeTracker.forLogging().onCancelled(statsToken, 493 ImeTracker.PHASE_WM_NOTIFY_HIDE_ANIMATION_FINISHED); 494 } 495 } 496 } 497 reportImeDrawnForOrganizerIfNeeded(@onNull InsetsControlTarget caller)498 private void reportImeDrawnForOrganizerIfNeeded(@NonNull InsetsControlTarget caller) { 499 final WindowState callerWindow = caller.getWindow(); 500 if (callerWindow == null) { 501 return; 502 } 503 WindowToken imeToken = mWindowContainer.asWindowState() != null 504 ? mWindowContainer.asWindowState().mToken : null; 505 final var rotationController = mDisplayContent.getAsyncRotationController(); 506 if ((rotationController != null && rotationController.isTargetToken(imeToken)) || ( 507 imeToken != null && imeToken.isSelfAnimating(0 /* flags */, 508 SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM))) { 509 // Skip reporting IME drawn state when the control target is in fixed 510 // rotation, AsyncRotationController will report after the animation finished. 511 return; 512 } 513 reportImeDrawnForOrganizer(caller); 514 } 515 reportImeDrawnForOrganizer(@onNull InsetsControlTarget caller)516 private void reportImeDrawnForOrganizer(@NonNull InsetsControlTarget caller) { 517 final WindowState callerWindow = caller.getWindow(); 518 if (callerWindow == null || callerWindow.getTask() == null) { 519 return; 520 } 521 if (callerWindow.getTask().isOrganized()) { 522 mWindowContainer.mWmService.mAtmService.mTaskOrganizerController 523 .reportImeDrawnOnTask(caller.getWindow().getTask()); 524 } 525 } 526 527 /** Report the IME has drawn on the current IME control target for its task organizer */ reportImeDrawnForOrganizer()528 void reportImeDrawnForOrganizer() { 529 final InsetsControlTarget imeControlTarget = getControlTarget(); 530 if (imeControlTarget != null) { 531 reportImeDrawnForOrganizer(imeControlTarget); 532 } 533 } 534 onSourceChanged()535 private void onSourceChanged() { 536 if (mLastSource.equals(mSource)) { 537 return; 538 } 539 mLastSource.set(mSource); 540 mDisplayContent.mWmService.mH.obtainMessage( 541 UPDATE_MULTI_WINDOW_STACKS, mDisplayContent).sendToTarget(); 542 } 543 544 /** 545 * Called from {@link WindowManagerInternal#showImePostLayout} 546 * when {@link android.inputmethodservice.InputMethodService} requests to show IME 547 * on the given control target. 548 * 549 * @param imeTarget the control target on which the IME request is coming from. 550 * @param statsToken the token tracking the current IME request. 551 */ scheduleShowImePostLayout(@onNull InsetsControlTarget imeTarget, @NonNull ImeTracker.Token statsToken)552 void scheduleShowImePostLayout(@NonNull InsetsControlTarget imeTarget, 553 @NonNull ImeTracker.Token statsToken) { 554 if (mImeRequester == null) { 555 // Start tracing only on initial scheduled show IME request, to record end-to-end time. 556 Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); 557 } else { 558 // We already have a scheduled show IME request, cancel the previous statsToken and 559 // continue with the new one. 560 logIsScheduledAndReadyToShowIme(false /* aborted */); 561 ImeTracker.forLogging().onCancelled(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); 562 } 563 final boolean targetChanged = isTargetChangedWithinActivity(imeTarget); 564 mImeRequester = imeTarget; 565 mStatsToken = statsToken; 566 if (targetChanged) { 567 // target changed, check if new target can show IME. 568 ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord"); 569 checkAndStartShowImePostLayout(); 570 // if IME cannot be shown at this time, it is scheduled to be shown. 571 // once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match, 572 // it will be shown. 573 return; 574 } 575 576 ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null 577 ? mImeRequester : mImeRequester.getWindow().getName()); 578 mDisplayContent.mWmService.requestTraversal(); 579 } 580 581 /** 582 * Checks whether there is a previously scheduled show IME request and we are ready to show, 583 * in which case also start handling the request. 584 */ checkAndStartShowImePostLayout()585 void checkAndStartShowImePostLayout() { 586 if (!isScheduledAndReadyToShowIme()) { 587 // This can later become ready, so we don't want to cancel the pending request here. 588 return; 589 } 590 // TODO(b/353463205) check if this is still triggered, as we don't go into STATE_SHOW_IME 591 // (DefaultImeVisibilityApplier) 592 if (android.view.inputmethod.Flags.refactorInsetsController()) { 593 // The IME is drawn, so call into {@link WindowState#notifyInsetsControlChanged} 594 // if we have a leash 595 if (mControl != null && mControl.getLeash() != null 596 && mControlTarget.getWindow() != null 597 && !mControlTarget.getWindow().mGivenInsetsPending) { 598 int displayId = mDisplayContent.getDisplayId(); 599 mControlTarget.notifyInsetsControlChanged(displayId); 600 } 601 return; 602 } 603 604 ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); 605 ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); 606 607 final InsetsControlTarget target = getControlTarget(); 608 609 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", 610 target.getWindow() != null ? target.getWindow().getName() : ""); 611 setImeShowing(true); 612 target.showInsets(WindowInsets.Type.ime(), true /* fromIme */, mStatsToken); 613 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); 614 if (target != mImeRequester) { 615 ProtoLog.w(WM_DEBUG_IME, "showInsets(ime) was requested by different window: %s ", 616 (mImeRequester.getWindow() != null ? mImeRequester.getWindow().getName() : "")); 617 } 618 resetShowImePostLayout(); 619 } 620 621 /** Aborts the previously scheduled show IME request. */ abortShowImePostLayout()622 void abortShowImePostLayout() { 623 if (mImeRequester == null) { 624 return; 625 } 626 ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout"); 627 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); 628 logIsScheduledAndReadyToShowIme(true /* aborted */); 629 ImeTracker.forLogging().onFailed( 630 mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT); 631 resetShowImePostLayout(); 632 } 633 634 /** Resets the state of the previously scheduled show IME request. */ resetShowImePostLayout()635 private void resetShowImePostLayout() { 636 mImeRequester = null; 637 mStatsToken = null; 638 } 639 640 /** Checks whether there is a previously scheduled show IME request and we are ready to show. */ 641 @VisibleForTesting isScheduledAndReadyToShowIme()642 boolean isScheduledAndReadyToShowIme() { 643 // IMMS#mLastImeTargetWindow always considers focused window as 644 // IME target, however DisplayContent#computeImeTarget() can compute 645 // a different IME target. 646 // Refer to WindowManagerService#applyImeVisibility(token, false). 647 // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window 648 // is above the parent, we will consider it as the same target for now. 649 // Also, if imeTarget is closing, it would be considered as outdated target. 650 // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of 651 // actual IME target. 652 if (mImeRequester == null) { 653 // No show IME request previously scheduled. 654 return false; 655 } 656 if (!mServerVisible || mFrozen) { 657 // The window container is not available and considered visible. 658 // If frozen, the server visibility is not set until unfrozen. 659 return false; 660 } 661 if (mWindowContainer == null) { 662 // No window container set. 663 return false; 664 } 665 final WindowState windowState = mWindowContainer.asWindowState(); 666 if (windowState == null) { 667 throw new IllegalArgumentException("IME insets must be provided by a window."); 668 } 669 if (!windowState.isDrawn() || windowState.mGivenInsetsPending) { 670 // The window is not drawn, or it has pending insets. 671 return false; 672 } 673 final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); 674 if (dcTarget == null) { 675 // No IME layering target. 676 return false; 677 } 678 final InsetsControlTarget controlTarget = getControlTarget(); 679 if (controlTarget == null) { 680 // No IME control target. 681 return false; 682 } 683 if (controlTarget != mDisplayContent.getImeTarget(IME_TARGET_CONTROL)) { 684 // The control target does not match the one in DisplayContent. 685 return false; 686 } 687 if (mStateController.hasPendingControls(controlTarget)) { 688 // The control target has pending controls. 689 return false; 690 } 691 if (getLeash(controlTarget) == null) { 692 // The control target has no source control leash (or it is not ready for dispatching). 693 return false; 694 } 695 696 ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeRequester: %s", 697 dcTarget.getWindow().getName(), mImeRequester.getWindow() == null 698 ? mImeRequester : mImeRequester.getWindow().getName()); 699 700 return isImeLayeringTarget(mImeRequester, dcTarget) 701 || isAboveImeLayeringTarget(mImeRequester, dcTarget) 702 || isImeFallbackTarget(mImeRequester) 703 || isImeInputTarget(mImeRequester) 704 || sameAsImeControlTarget(mImeRequester); 705 } 706 707 /** 708 * Sets the statsToken before the IMS was shown/hidden. 709 * @param visible {@code true} to make it visible, false to hide it. 710 * @param statsToken the token tracking the current IME request. 711 */ receiveImeStatsToken(boolean visible, @NonNull ImeTracker.Token statsToken)712 void receiveImeStatsToken(boolean visible, 713 @NonNull ImeTracker.Token statsToken) { 714 if (!android.view.inputmethod.Flags.refactorInsetsController()) { 715 return; 716 } 717 718 if (mStatsToken != null) { 719 // We have an ongoing show request will be cancelled by the newly received show 720 // request (cancelling the initial show) or hide request (aborting the initial show). 721 logIsScheduledAndReadyToShowIme(!visible /* aborted */); 722 } 723 ProtoLog.d(WM_DEBUG_IME, "receiveImeStatsToken: visible=%s", visible); 724 if (visible) { 725 ImeTracker.forLogging().onCancelled( 726 mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT); 727 mStatsToken = statsToken; 728 } else { 729 ImeTracker.forLogging().onFailed( 730 mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT); 731 mStatsToken = null; 732 } 733 } 734 735 /** 736 * Logs the current state that can be checked by {@link #isScheduledAndReadyToShowIme}. 737 * 738 * @param aborted whether the scheduled show IME request was aborted or cancelled. 739 */ logIsScheduledAndReadyToShowIme(boolean aborted)740 private void logIsScheduledAndReadyToShowIme(boolean aborted) { 741 final var windowState = mWindowContainer != null ? mWindowContainer.asWindowState() : null; 742 final var dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); 743 final var controlTarget = getControlTarget(); 744 final var sb = new StringBuilder(); 745 sb.append("showImePostLayout ").append(aborted ? "aborted" : "cancelled"); 746 sb.append(", isScheduledAndReadyToShowIme: ").append(isScheduledAndReadyToShowIme()); 747 sb.append(", mImeRequester: ").append(mImeRequester); 748 sb.append(", serverVisible: ").append(mServerVisible); 749 sb.append(", frozen: ").append(mFrozen); 750 sb.append(", mWindowContainer is: ").append(mWindowContainer != null ? "non-null" : "null"); 751 sb.append(", windowState: ").append(windowState); 752 if (windowState != null) { 753 sb.append(", isDrawn: ").append(windowState.isDrawn()); 754 sb.append(", mGivenInsetsPending: ").append(windowState.mGivenInsetsPending); 755 } 756 sb.append(", dcTarget: ").append(dcTarget); 757 sb.append(", controlTarget: ").append(controlTarget); 758 if (mImeRequester != null && dcTarget != null && controlTarget != null) { 759 sb.append("\n"); 760 sb.append("controlTarget == DisplayContent.controlTarget: "); 761 sb.append(controlTarget == mDisplayContent.getImeTarget(IME_TARGET_CONTROL)); 762 sb.append(", hasPendingControls: "); 763 sb.append(mStateController.hasPendingControls(controlTarget)); 764 final boolean hasLeash = getLeash(controlTarget) != null; 765 sb.append(", leash is: ").append(hasLeash ? "non-null" : "null"); 766 if (!hasLeash) { 767 sb.append(", control is: ").append(mControl != null ? "non-null" : "null"); 768 sb.append(", mIsLeashInitialized: ").append(mIsLeashInitialized); 769 } 770 sb.append(", isImeLayeringTarget: "); 771 sb.append(isImeLayeringTarget(mImeRequester, dcTarget)); 772 sb.append(", isAboveImeLayeringTarget: "); 773 sb.append(isAboveImeLayeringTarget(mImeRequester, dcTarget)); 774 sb.append(", isImeFallbackTarget: "); 775 sb.append(isImeFallbackTarget(mImeRequester)); 776 sb.append(", isImeInputTarget: "); 777 sb.append(isImeInputTarget(mImeRequester)); 778 sb.append(", sameAsImeControlTarget: "); 779 sb.append(sameAsImeControlTarget(mImeRequester)); 780 } 781 Slog.d(TAG, sb.toString()); 782 } 783 784 // --------------------------------------------------------------------------------------- 785 // Methods for checking IME insets target changing state. 786 // isImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)787 private static boolean isImeLayeringTarget(@NonNull InsetsControlTarget target, 788 @NonNull InsetsControlTarget dcTarget) { 789 return !isImeTargetWindowClosing(dcTarget.getWindow()) && target == dcTarget; 790 } 791 isAboveImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)792 private static boolean isAboveImeLayeringTarget(@NonNull InsetsControlTarget target, 793 @NonNull InsetsControlTarget dcTarget) { 794 return target.getWindow() != null 795 && dcTarget.getWindow().getParentWindow() == target 796 && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer; 797 } 798 isImeFallbackTarget(@onNull InsetsControlTarget target)799 private boolean isImeFallbackTarget(@NonNull InsetsControlTarget target) { 800 return target == mDisplayContent.getImeFallback(); 801 } 802 isImeInputTarget(@onNull InsetsTarget target)803 private boolean isImeInputTarget(@NonNull InsetsTarget target) { 804 return target == mDisplayContent.getImeInputTarget(); 805 } 806 sameAsImeControlTarget(@onNull InsetsControlTarget target)807 private boolean sameAsImeControlTarget(@NonNull InsetsControlTarget target) { 808 final InsetsControlTarget controlTarget = getControlTarget(); 809 return controlTarget == target 810 && (target.getWindow() == null || !isImeTargetWindowClosing(target.getWindow())); 811 } 812 isImeTargetWindowClosing(@onNull WindowState win)813 private static boolean isImeTargetWindowClosing(@NonNull WindowState win) { 814 return win.mAnimatingExit || win.mActivityRecord != null 815 && (win.mActivityRecord.isInTransition() 816 && !win.mActivityRecord.isVisibleRequested() 817 || win.mActivityRecord.willCloseOrEnterPip()); 818 } 819 isTargetChangedWithinActivity(@onNull InsetsControlTarget target)820 private boolean isTargetChangedWithinActivity(@NonNull InsetsControlTarget target) { 821 // We don't consider the target out of the activity. 822 if (target.getWindow() == null) { 823 return false; 824 } 825 return mImeRequester != target 826 && mImeRequester != null 827 && mImeRequester.getWindow() != null 828 && mImeRequester.getWindow().mActivityRecord == target.getWindow().mActivityRecord; 829 } 830 // --------------------------------------------------------------------------------------- 831 832 @Override dump(PrintWriter pw, String prefix)833 public void dump(PrintWriter pw, String prefix) { 834 super.dump(pw, prefix); 835 prefix = prefix + " "; 836 pw.print(prefix); 837 pw.print("mImeShowing="); 838 pw.print(mImeShowing); 839 pw.print(" mLastDrawn="); 840 pw.print(mLastDrawn); 841 if (mImeRequester != null) { 842 pw.print(prefix); 843 pw.print("showImePostLayout pending for mImeRequester="); 844 pw.print(mImeRequester); 845 pw.println(); 846 } else { 847 pw.print(prefix); 848 pw.print("showImePostLayout not scheduled, mImeRequester=null"); 849 pw.println(); 850 } 851 pw.println(); 852 } 853 854 @Override dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTracingLogLevel int logLevel)855 void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTracingLogLevel int logLevel) { 856 final long token = proto.start(fieldId); 857 super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel); 858 final WindowState imeRequesterWindow = 859 mImeRequester != null ? mImeRequester.getWindow() : null; 860 if (imeRequesterWindow != null) { 861 imeRequesterWindow.writeIdentifierToProto(proto, IME_TARGET_FROM_IME_IDENTIFIER); 862 } 863 proto.end(token); 864 } 865 866 /** 867 * Sets whether the IME is currently supposed to be showing according to 868 * InputMethodManagerService. 869 */ setImeShowing(boolean imeShowing)870 public void setImeShowing(boolean imeShowing) { 871 mImeShowing = imeShowing; 872 } 873 874 /** 875 * Returns whether the IME is currently supposed to be showing according to 876 * InputMethodManagerService. 877 */ isImeShowing()878 public boolean isImeShowing() { 879 return mImeShowing; 880 } 881 } 882