1 /* 2 * Copyright (C) 2020 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.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; 20 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; 21 22 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM; 23 24 import android.annotation.IntDef; 25 import android.os.HandlerExecutor; 26 import android.util.ArrayMap; 27 import android.util.Slog; 28 import android.view.SurfaceControl; 29 import android.view.WindowManager; 30 import android.view.animation.AlphaAnimation; 31 import android.view.animation.Animation; 32 33 import java.io.PrintWriter; 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 import java.util.function.Consumer; 37 38 /** 39 * Controller to handle the appearance of non-activity windows which can update asynchronously when 40 * the display rotation is changing. This is an optimization to reduce the latency to start screen 41 * rotation or app transition animation. 42 * <pre>The appearance: 43 * - Open app with rotation change: the target windows are faded out with open transition, and then 44 * faded in after the transition when the windows are drawn with new rotation. 45 * - Normal rotation: the target windows are hidden by a parent leash with zero alpha after the 46 * screenshot layer is shown, and will be faded in when they are drawn with new rotation. 47 * - Seamless rotation: Only shell transition uses this controller in this case. The target windows 48 * will be requested to use sync transaction individually. Their window token will rotate to old 49 * rotation. After the start transaction of transition is applied and the window is drawn in new 50 * rotation, the old rotation transformation will be removed with applying the sync transaction. 51 * </pre> 52 * For the windows which are forced to be seamless (e.g. screen decor overlay), the case is the 53 * same as above mentioned seamless rotation (only shell). Just the appearance may be mixed, e.g. 54 * 2 windows FADE and 2 windows SEAMLESS in normal rotation or app transition. And 4 (all) windows 55 * SEAMLESS in seamless rotation. 56 */ 57 class AsyncRotationController extends FadeAnimationController implements Consumer<WindowState> { 58 private static final String TAG = "AsyncRotation"; 59 private static final boolean DEBUG = false; 60 61 private final WindowManagerService mService; 62 /** The map of async windows to the operations of rotation appearance. */ 63 private final ArrayMap<WindowToken, Operation> mTargetWindowTokens = new ArrayMap<>(); 64 /** If non-null, it usually indicates that there will be a screen rotation animation. */ 65 private Runnable mTimeoutRunnable; 66 /** Non-null to indicate that the navigation bar is always handled by legacy seamless. */ 67 private WindowToken mNavBarToken; 68 69 /** A runnable which gets called when the {@link #completeAll()} is called. */ 70 private Runnable mOnShowRunnable; 71 72 /** Whether to use constant zero alpha animation. */ 73 private boolean mHideImmediately; 74 75 /** The case of legacy transition. */ 76 private static final int OP_LEGACY = 0; 77 /** It is usually OPEN/CLOSE/TO_FRONT/TO_BACK. */ 78 private static final int OP_APP_SWITCH = 1; 79 /** The normal display change transition which should have a screen rotation animation. */ 80 private static final int OP_CHANGE = 2; 81 /** The app requests seamless and the display supports. But the decision is still in shell. */ 82 private static final int OP_CHANGE_MAY_SEAMLESS = 3; 83 84 @Retention(RetentionPolicy.SOURCE) 85 @IntDef(value = { OP_LEGACY, OP_APP_SWITCH, OP_CHANGE, OP_CHANGE_MAY_SEAMLESS }) 86 @interface TransitionOp {} 87 88 /** Non-zero if this controller is triggered by shell transition. */ 89 private final @TransitionOp int mTransitionOp; 90 91 /** 92 * Whether {@link #setupStartTransaction} is called when the transition is ready. 93 * If this is never set for {@link #OP_CHANGE}, the display may be changed to original state 94 * before the transition is ready, then this controller should be finished. 95 */ 96 private boolean mIsStartTransactionPrepared; 97 98 /** Whether the start transaction of the transition is committed (by shell). */ 99 private boolean mIsStartTransactionCommitted; 100 101 /** Whether the target windows have been requested to sync their draw transactions. */ 102 private boolean mIsSyncDrawRequested; 103 104 private SeamlessRotator mRotator; 105 106 private int mOriginalRotation; 107 private final boolean mHasScreenRotationAnimation; 108 AsyncRotationController(DisplayContent displayContent)109 AsyncRotationController(DisplayContent displayContent) { 110 super(displayContent); 111 mService = displayContent.mWmService; 112 mOriginalRotation = displayContent.getWindowConfiguration().getRotation(); 113 final int transitionType = 114 displayContent.mTransitionController.getCollectingTransitionType(); 115 if (transitionType == WindowManager.TRANSIT_CHANGE) { 116 final DisplayRotation dr = displayContent.getDisplayRotation(); 117 final WindowState w = displayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow(); 118 // A rough condition to check whether it may be seamless style. Though the final 119 // decision in shell may be different, it is fine because the jump cut can be covered 120 // by a screenshot if shell falls back to use normal rotation animation. 121 if (w != null && w.mAttrs.rotationAnimation == ROTATION_ANIMATION_SEAMLESS 122 && w.getTask() != null 123 && dr.canRotateSeamlessly(mOriginalRotation, dr.getRotation())) { 124 mTransitionOp = OP_CHANGE_MAY_SEAMLESS; 125 } else { 126 mTransitionOp = OP_CHANGE; 127 } 128 } else if (displayContent.mTransitionController.isShellTransitionsEnabled()) { 129 mTransitionOp = OP_APP_SWITCH; 130 } else { 131 mTransitionOp = OP_LEGACY; 132 } 133 134 // Although OP_CHANGE_MAY_SEAMLESS may still play screen rotation animation because shell 135 // decides not to perform seamless rotation, it only affects whether to use fade animation 136 // when the windows are drawn. If the windows are not too slow (after rotation animation is 137 // done) to be drawn, the visual result can still look smooth. 138 mHasScreenRotationAnimation = mTransitionOp == OP_CHANGE; 139 if (mHasScreenRotationAnimation) { 140 // Hide the windows immediately because screen should have been covered by screenshot. 141 mHideImmediately = true; 142 } 143 144 // Collect the windows which can rotate asynchronously without blocking the display. 145 displayContent.forAllWindows(this, true /* traverseTopToBottom */); 146 147 // Legacy animation doesn't need to wait for the start transaction. 148 if (mTransitionOp == OP_LEGACY) { 149 mIsStartTransactionCommitted = true; 150 } else if (displayContent.mTransitionController.isCollecting(displayContent)) { 151 keepAppearanceInPreviousRotation(); 152 } 153 } 154 155 /** Assigns the operation for the window tokens which can update rotation asynchronously. */ 156 @Override accept(WindowState w)157 public void accept(WindowState w) { 158 if (!w.mHasSurface || !canBeAsync(w.mToken)) { 159 return; 160 } 161 if (mTransitionOp == OP_LEGACY && w.mForceSeamlesslyRotate) { 162 // Legacy transition already handles seamlessly windows. 163 return; 164 } 165 if (w.mAttrs.type == TYPE_NAVIGATION_BAR) { 166 int action = Operation.ACTION_FADE; 167 final boolean navigationBarCanMove = 168 mDisplayContent.getDisplayPolicy().navigationBarCanMove(); 169 if (mTransitionOp == OP_LEGACY) { 170 mNavBarToken = w.mToken; 171 // Do not animate movable navigation bar (e.g. 3-buttons mode). 172 if (navigationBarCanMove) return; 173 } else if (navigationBarCanMove || mTransitionOp == OP_CHANGE_MAY_SEAMLESS 174 || mDisplayContent.mTransitionController.mNavigationBarAttachedToApp) { 175 action = Operation.ACTION_SEAMLESS; 176 } 177 mTargetWindowTokens.put(w.mToken, new Operation(action)); 178 return; 179 } 180 181 final int action = mTransitionOp == OP_CHANGE_MAY_SEAMLESS || w.mForceSeamlesslyRotate 182 ? Operation.ACTION_SEAMLESS : Operation.ACTION_FADE; 183 mTargetWindowTokens.put(w.mToken, new Operation(action)); 184 } 185 186 /** Returns {@code true} if the window token can update rotation independently. */ canBeAsync(WindowToken token)187 static boolean canBeAsync(WindowToken token) { 188 final int type = token.windowType; 189 return type > WindowManager.LayoutParams.LAST_APPLICATION_WINDOW 190 && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD 191 && type != WindowManager.LayoutParams.TYPE_WALLPAPER 192 && type != WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; 193 } 194 195 /** 196 * Enables {@link #handleFinishDrawing(WindowState, SurfaceControl.Transaction)} to capture the 197 * draw transactions of the target windows if needed. 198 */ keepAppearanceInPreviousRotation()199 void keepAppearanceInPreviousRotation() { 200 if (mIsSyncDrawRequested) return; 201 // The transition sync group may be finished earlier because it doesn't wait for these 202 // target windows. But the windows still need to use sync transaction to keep the appearance 203 // in previous rotation, so request a no-op sync to keep the state. 204 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 205 if (canDrawBeforeStartTransaction(mTargetWindowTokens.valueAt(i))) { 206 // Expect a screenshot layer will cover the non seamless windows. 207 continue; 208 } 209 final WindowToken token = mTargetWindowTokens.keyAt(i); 210 for (int j = token.getChildCount() - 1; j >= 0; j--) { 211 // TODO(b/234585256): The consumer should be handleFinishDrawing(). 212 token.getChildAt(j).applyWithNextDraw(t -> {}); 213 if (DEBUG) Slog.d(TAG, "Sync draw for " + token.getChildAt(j)); 214 } 215 } 216 mIsSyncDrawRequested = true; 217 if (DEBUG) Slog.d(TAG, "Requested to sync draw transaction"); 218 } 219 220 /** 221 * If an async window is not requested to redraw or its surface is removed, then complete its 222 * operation directly to avoid waiting until timeout. 223 */ updateTargetWindows()224 void updateTargetWindows() { 225 if (mTransitionOp == OP_LEGACY) return; 226 if (!mIsStartTransactionCommitted) { 227 if ((mTimeoutRunnable == null || !mIsStartTransactionPrepared) 228 && !mDisplayContent.hasTopFixedRotationLaunchingApp() 229 && !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) { 230 Slog.d(TAG, "Cancel for no change"); 231 mDisplayContent.finishAsyncRotationIfPossible(); 232 } 233 return; 234 } 235 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 236 final Operation op = mTargetWindowTokens.valueAt(i); 237 if (op.mIsCompletionPending || op.mActions == Operation.ACTION_SEAMLESS) { 238 // Skip completed target. And seamless windows use the signal from blast sync. 239 continue; 240 } 241 final WindowToken token = mTargetWindowTokens.keyAt(i); 242 int readyCount = 0; 243 final int childCount = token.getChildCount(); 244 for (int j = childCount - 1; j >= 0; j--) { 245 final WindowState w = token.getChildAt(j); 246 // If the token no longer contains pending drawn windows, then it is ready. 247 if (w.isDrawn() || !w.mWinAnimator.getShown()) { 248 readyCount++; 249 } 250 } 251 if (readyCount == childCount) { 252 mDisplayContent.finishAsyncRotation(token); 253 } 254 } 255 } 256 257 /** Lets the window fit in new rotation naturally. */ finishOp(WindowToken windowToken)258 private void finishOp(WindowToken windowToken) { 259 final Operation op = mTargetWindowTokens.remove(windowToken); 260 if (op == null) return; 261 if (op.mDrawTransaction != null) { 262 // Unblock the window to show its latest content. 263 windowToken.getSyncTransaction().merge(op.mDrawTransaction); 264 op.mDrawTransaction = null; 265 if (DEBUG) Slog.d(TAG, "finishOp merge transaction " + windowToken.getTopChild()); 266 } 267 if (op.mActions == Operation.ACTION_TOGGLE_IME) { 268 if (DEBUG) Slog.d(TAG, "finishOp fade-in IME " + windowToken.getTopChild()); 269 fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM, 270 (type, anim) -> mDisplayContent.getInsetsStateController() 271 .getImeSourceProvider().reportImeDrawnForOrganizer()); 272 } else if ((op.mActions & Operation.ACTION_FADE) != 0) { 273 if (DEBUG) Slog.d(TAG, "finishOp fade-in " + windowToken.getTopChild()); 274 // The previous animation leash will be dropped when preparing fade-in animation, so 275 // simply apply new animation without restoring the transformation. 276 fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM); 277 } 278 if (op.isValidSeamless()) { 279 if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild()); 280 final SurfaceControl.Transaction t = windowToken.getSyncTransaction(); 281 clearTransform(t, op.mLeash); 282 } 283 // The insets position may be frozen by shouldFreezeInsetsPosition(), so refresh the 284 // position to the latest state when it is ready to show in new rotation. 285 if (isSeamlessTransition()) { 286 for (int i = windowToken.getChildCount() - 1; i >= 0; i--) { 287 final WindowState w = windowToken.getChildAt(i); 288 final InsetsSourceProvider insetsProvider = w.getControllableInsetProvider(); 289 if (insetsProvider != null) { 290 insetsProvider.updateInsetsControlPosition(w); 291 } 292 } 293 } 294 } 295 clearTransform(SurfaceControl.Transaction t, SurfaceControl sc)296 private static void clearTransform(SurfaceControl.Transaction t, SurfaceControl sc) { 297 t.setMatrix(sc, 1, 0, 0, 1); 298 t.setPosition(sc, 0, 0); 299 } 300 301 /** 302 * Completes all operations such as applying fade-in animation on the previously hidden window 303 * tokens. This is called if all windows are ready in new rotation or timed out. 304 */ completeAll()305 void completeAll() { 306 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 307 finishOp(mTargetWindowTokens.keyAt(i)); 308 } 309 mTargetWindowTokens.clear(); 310 onAllCompleted(); 311 } 312 onAllCompleted()313 private void onAllCompleted() { 314 if (DEBUG) Slog.d(TAG, "onAllCompleted"); 315 if (mTimeoutRunnable != null) { 316 mService.mH.removeCallbacks(mTimeoutRunnable); 317 } 318 if (mOnShowRunnable != null) { 319 mOnShowRunnable.run(); 320 mOnShowRunnable = null; 321 } 322 } 323 324 /** 325 * Notifies that the window is ready in new rotation. Returns {@code true} if all target 326 * windows have completed their rotation operations. 327 */ completeRotation(WindowToken token)328 boolean completeRotation(WindowToken token) { 329 if (!mIsStartTransactionCommitted) { 330 final Operation op = mTargetWindowTokens.get(token); 331 // The animation or draw transaction should only start after the start transaction is 332 // applied by shell (e.g. show screenshot layer). Otherwise the window will be blinking 333 // before the rotation animation starts. So store to a pending list and animate them 334 // until the transaction is committed. 335 if (op != null) { 336 if (DEBUG) Slog.d(TAG, "Complete set pending " + token.getTopChild()); 337 op.mIsCompletionPending = true; 338 } 339 return false; 340 } 341 if (mTransitionOp == OP_APP_SWITCH && token.mTransitionController.inTransition()) { 342 final Operation op = mTargetWindowTokens.get(token); 343 if (op != null && op.mActions == Operation.ACTION_FADE) { 344 // Defer showing to onTransitionFinished(). 345 if (DEBUG) Slog.d(TAG, "Defer completion " + token.getTopChild()); 346 return false; 347 } 348 } 349 if (!isTargetToken(token)) return false; 350 if (mHasScreenRotationAnimation || mTransitionOp != OP_LEGACY) { 351 if (DEBUG) Slog.d(TAG, "Complete directly " + token.getTopChild()); 352 finishOp(token); 353 if (mTargetWindowTokens.isEmpty()) { 354 onAllCompleted(); 355 return true; 356 } 357 } 358 // The case (legacy fixed rotation) will be handled by completeAll() when all seamless 359 // windows are done. 360 return false; 361 } 362 363 /** 364 * Prepares the corresponding operations (e.g. hide animation) for the window tokens which may 365 * be seamlessly rotated later. 366 */ start()367 void start() { 368 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 369 final WindowToken windowToken = mTargetWindowTokens.keyAt(i); 370 final Operation op = mTargetWindowTokens.valueAt(i); 371 if ((op.mActions & Operation.ACTION_FADE) != 0 372 || op.mActions == Operation.ACTION_TOGGLE_IME) { 373 fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM); 374 op.mLeash = windowToken.getAnimationLeash(); 375 if (DEBUG) Slog.d(TAG, "Start fade-out " + windowToken.getTopChild()); 376 } else if (op.mActions == Operation.ACTION_SEAMLESS) { 377 op.mLeash = windowToken.mSurfaceControl; 378 if (DEBUG) Slog.d(TAG, "Start seamless " + windowToken.getTopChild()); 379 } 380 } 381 if (mHasScreenRotationAnimation) { 382 scheduleTimeout(); 383 } 384 } 385 386 /** 387 * Re-initialize the states if the current display rotation has changed to a different rotation. 388 * This is mainly for seamless rotation to update the transform based on new rotation. 389 */ updateRotation()390 void updateRotation() { 391 if (mRotator == null) return; 392 final int currentRotation = mDisplayContent.getWindowConfiguration().getRotation(); 393 if (mOriginalRotation == currentRotation) { 394 return; 395 } 396 Slog.d(TAG, "Update original rotation " + currentRotation); 397 mOriginalRotation = currentRotation; 398 mDisplayContent.forAllWindows(w -> { 399 if (w.mForceSeamlesslyRotate && w.mHasSurface 400 && !mTargetWindowTokens.containsKey(w.mToken)) { 401 final Operation op = new Operation(Operation.ACTION_SEAMLESS); 402 op.mLeash = w.mToken.mSurfaceControl; 403 mTargetWindowTokens.put(w.mToken, op); 404 } 405 }, true /* traverseTopToBottom */); 406 mRotator = null; 407 mIsStartTransactionCommitted = false; 408 mIsSyncDrawRequested = false; 409 keepAppearanceInPreviousRotation(); 410 } 411 scheduleTimeout()412 private void scheduleTimeout() { 413 if (mTimeoutRunnable == null) { 414 mTimeoutRunnable = () -> { 415 synchronized (mService.mGlobalLock) { 416 final String reason; 417 if (!mIsStartTransactionCommitted) { 418 if (!mIsStartTransactionPrepared) { 419 reason = "setupStartTransaction is not called"; 420 } else { 421 reason = "start transaction is not committed"; 422 } 423 } else { 424 reason = "unfinished windows " + mTargetWindowTokens; 425 } 426 Slog.i(TAG, "Async rotation timeout: " + reason); 427 if (!mIsStartTransactionCommitted && mIsStartTransactionPrepared) { 428 // The transaction commit timeout will be handled by: 429 // 1. BLASTSyncEngine will notify onTransactionCommitTimeout() and then 430 // apply the start transaction of transition. 431 // 2. The TransactionCommittedListener in setupStartTransaction() will be 432 // notified to finish the operations of mTargetWindowTokens. 433 // 3. The slow remote side will also apply the start transaction which may 434 // contain stale surface transform. 435 // 4. Finally, the slow remote reports transition finished. The cleanup 436 // transaction from step (1) will be applied when finishing transition, 437 // which will recover the stale state from (3). 438 return; 439 } 440 mDisplayContent.finishAsyncRotationIfPossible(); 441 mService.mWindowPlacerLocked.performSurfacePlacement(); 442 } 443 }; 444 } 445 mService.mH.postDelayed(mTimeoutRunnable, 446 WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION); 447 } 448 449 /** Hides the IME window immediately until it is drawn in new rotation. */ hideImeImmediately()450 void hideImeImmediately() { 451 if (mDisplayContent.mInputMethodWindow == null) return; 452 final WindowToken imeWindowToken = mDisplayContent.mInputMethodWindow.mToken; 453 if (isTargetToken(imeWindowToken)) return; 454 hideImmediately(imeWindowToken, Operation.ACTION_TOGGLE_IME); 455 if (DEBUG) Slog.d(TAG, "hideImeImmediately " + imeWindowToken.getTopChild()); 456 } 457 hideImmediately(WindowToken token, @Operation.Action int action)458 private void hideImmediately(WindowToken token, @Operation.Action int action) { 459 final boolean original = mHideImmediately; 460 mHideImmediately = true; 461 final Operation op = new Operation(action); 462 mTargetWindowTokens.put(token, op); 463 fadeWindowToken(false /* show */, token, ANIMATION_TYPE_TOKEN_TRANSFORM); 464 op.mLeash = token.getAnimationLeash(); 465 mHideImmediately = original; 466 } 467 468 /** Returns {@code true} if the window will rotate independently. */ isAsync(WindowState w)469 boolean isAsync(WindowState w) { 470 return w.mToken == mNavBarToken 471 || (w.mForceSeamlesslyRotate && mTransitionOp == OP_LEGACY) 472 || isTargetToken(w.mToken); 473 } 474 475 /** 476 * Returns {@code true} if the rotation transition appearance of the window is currently 477 * managed by this controller. 478 */ isTargetToken(WindowToken token)479 boolean isTargetToken(WindowToken token) { 480 return mTargetWindowTokens.containsKey(token); 481 } 482 483 /** Returns {@code true} if the controller will run fade animations on the window. */ hasFadeOperation(WindowToken token)484 boolean hasFadeOperation(WindowToken token) { 485 final Operation op = mTargetWindowTokens.get(token); 486 return op != null && (op.mActions & Operation.ACTION_FADE) != 0; 487 } 488 489 /** Returns {@code true} if the window is un-rotated to original rotation. */ hasSeamlessOperation(WindowToken token)490 boolean hasSeamlessOperation(WindowToken token) { 491 final Operation op = mTargetWindowTokens.get(token); 492 return op != null && (op.mActions & Operation.ACTION_SEAMLESS) != 0; 493 } 494 495 /** 496 * Whether the insets animation leash should use previous position when running fade animation 497 * or seamless transformation in a rotated display. 498 */ shouldFreezeInsetsPosition(WindowState w)499 boolean shouldFreezeInsetsPosition(WindowState w) { 500 // Non-change transition (OP_APP_SWITCH) and METHOD_BLAST don't use screenshot so the 501 // insets should keep original position before the window is done with new rotation. 502 return mTransitionOp != OP_LEGACY && (isSeamlessTransition() 503 || TransitionController.SYNC_METHOD == BLASTSyncEngine.METHOD_BLAST) 504 && canBeAsync(w.mToken) && isTargetToken(w.mToken); 505 } 506 507 /** Returns true if there won't be a screen rotation animation (screenshot-based). */ isSeamlessTransition()508 private boolean isSeamlessTransition() { 509 return mTransitionOp == OP_APP_SWITCH || mTransitionOp == OP_CHANGE_MAY_SEAMLESS; 510 } 511 512 /** 513 * Returns the transaction which will be applied after the window redraws in new rotation. 514 * This is used to update the position of insets animation leash synchronously. 515 */ getDrawTransaction(WindowToken token)516 SurfaceControl.Transaction getDrawTransaction(WindowToken token) { 517 if (mTransitionOp == OP_LEGACY) { 518 // Legacy transition uses startSeamlessRotation and finishSeamlessRotation of 519 // InsetsSourceProvider. 520 return null; 521 } 522 final Operation op = mTargetWindowTokens.get(token); 523 if (op != null) { 524 if (op.mDrawTransaction == null) { 525 op.mDrawTransaction = new SurfaceControl.Transaction(); 526 } 527 return op.mDrawTransaction; 528 } 529 return null; 530 } 531 setOnShowRunnable(Runnable onShowRunnable)532 void setOnShowRunnable(Runnable onShowRunnable) { 533 mOnShowRunnable = onShowRunnable; 534 } 535 536 /** 537 * Puts initial operation of leash to the transaction which will be executed when the 538 * transition starts. And associate transaction callback to consume pending animations. 539 */ setupStartTransaction(SurfaceControl.Transaction t)540 void setupStartTransaction(SurfaceControl.Transaction t) { 541 if (mIsStartTransactionCommitted) return; 542 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 543 final Operation op = mTargetWindowTokens.valueAt(i); 544 final SurfaceControl leash = op.mLeash; 545 if (leash == null || !leash.isValid()) continue; 546 if (mHasScreenRotationAnimation && op.mActions == Operation.ACTION_FADE) { 547 // Hide the windows immediately because a screenshot layer should cover the screen. 548 t.setAlpha(leash, 0f); 549 if (DEBUG) { 550 Slog.d(TAG, "Setup alpha0 " + mTargetWindowTokens.keyAt(i).getTopChild()); 551 } 552 } else { 553 // Take OPEN/CLOSE transition type as the example, the non-activity windows need to 554 // fade out in previous rotation while display has rotated to the new rotation, so 555 // their leashes are transformed with the start transaction. 556 if (mRotator == null) { 557 mRotator = new SeamlessRotator(mOriginalRotation, 558 mDisplayContent.getWindowConfiguration().getRotation(), 559 mDisplayContent.getDisplayInfo(), 560 false /* applyFixedTransformationHint */); 561 } 562 mRotator.applyTransform(t, leash); 563 if (DEBUG) { 564 Slog.d(TAG, "Setup unrotate " + mTargetWindowTokens.keyAt(i).getTopChild()); 565 } 566 } 567 } 568 569 // If there are windows have redrawn in new rotation but the start transaction has not 570 // been applied yet, the fade-in animation will be deferred. So once the transaction is 571 // committed, the fade-in animation can run with screen rotation animation. 572 t.addTransactionCommittedListener(new HandlerExecutor(mService.mH), () -> { 573 synchronized (mService.mGlobalLock) { 574 if (DEBUG) Slog.d(TAG, "Start transaction is committed"); 575 mIsStartTransactionCommitted = true; 576 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 577 if (mTargetWindowTokens.valueAt(i).mIsCompletionPending) { 578 if (DEBUG) { 579 Slog.d(TAG, "Continue pending completion " 580 + mTargetWindowTokens.keyAt(i).getTopChild()); 581 } 582 mDisplayContent.finishAsyncRotation(mTargetWindowTokens.keyAt(i)); 583 } 584 } 585 } 586 }); 587 mIsStartTransactionPrepared = true; 588 } 589 590 /** Called when the start transition is ready, but it is not applied in time. */ onTransactionCommitTimeout(SurfaceControl.Transaction t)591 void onTransactionCommitTimeout(SurfaceControl.Transaction t) { 592 if (mIsStartTransactionCommitted) return; 593 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 594 final Operation op = mTargetWindowTokens.valueAt(i); 595 op.mIsCompletionPending = true; 596 if (op.isValidSeamless()) { 597 Slog.d(TAG, "Transaction timeout. Clear transform for " 598 + mTargetWindowTokens.keyAt(i).getTopChild()); 599 clearTransform(t, op.mLeash); 600 } 601 } 602 } 603 604 /** Called when the transition by shell is done. */ onTransitionFinished()605 void onTransitionFinished() { 606 if (mTransitionOp == OP_CHANGE) { 607 if (mTargetWindowTokens.isEmpty()) { 608 // If nothing was handled, then complete with the transition. 609 mDisplayContent.finishAsyncRotationIfPossible(); 610 } 611 // With screen rotation animation, the windows are always faded in when they are drawn. 612 // Because if they are drawn fast enough, the fade animation should not be observable. 613 return; 614 } 615 if (DEBUG) Slog.d(TAG, "onTransitionFinished " + mTargetWindowTokens); 616 // For other transition types, the fade-in animation runs after the transition to make the 617 // transition animation (e.g. launch activity) look cleaner. 618 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 619 final WindowToken token = mTargetWindowTokens.keyAt(i); 620 if (!token.isVisible()) { 621 mDisplayContent.finishAsyncRotation(token); 622 continue; 623 } 624 for (int j = token.getChildCount() - 1; j >= 0; j--) { 625 // Only fade in the drawn windows. If the remaining windows are drawn later, 626 // show(WindowToken) will be called to fade in them. 627 if (token.getChildAt(j).isDrawFinishedLw()) { 628 mDisplayContent.finishAsyncRotation(token); 629 break; 630 } 631 } 632 } 633 if (!mTargetWindowTokens.isEmpty()) { 634 scheduleTimeout(); 635 } 636 } 637 638 /** 639 * Captures the post draw transaction if the window should keep its appearance in previous 640 * rotation when running transition. Returns {@code true} if the draw transaction is handled 641 * by this controller. 642 */ handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction)643 boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) { 644 if (mTransitionOp == OP_LEGACY) { 645 return false; 646 } 647 final Operation op = mTargetWindowTokens.get(w.mToken); 648 if (op == null) { 649 // If a window becomes visible after the rotation transition is requested but before 650 // the transition is ready, hide it by an animation leash so it won't be flickering 651 // by drawing the rotated content before applying projection transaction of display. 652 // And it will fade in after the display transition is finished. 653 if (mTransitionOp == OP_APP_SWITCH && !mIsStartTransactionCommitted 654 && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch() 655 && !mService.mAtmService.mBackNavigationController.hasFixedRotationAnimation( 656 mDisplayContent)) { 657 hideImmediately(w.mToken, Operation.ACTION_FADE); 658 if (DEBUG) Slog.d(TAG, "Hide on finishDrawing " + w.mToken.getTopChild()); 659 } 660 return false; 661 } 662 if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w); 663 if (postDrawTransaction == null || !mIsSyncDrawRequested 664 || canDrawBeforeStartTransaction(op)) { 665 mDisplayContent.finishAsyncRotation(w.mToken); 666 return false; 667 } 668 if (op.mDrawTransaction == null) { 669 if (w.isClientLocal()) { 670 // Use a new transaction to merge the draw transaction of local window because the 671 // same instance will be cleared (Transaction#clear()) after reporting draw. 672 op.mDrawTransaction = mService.mTransactionFactory.get(); 673 op.mDrawTransaction.merge(postDrawTransaction); 674 } else { 675 // The transaction read from parcel (the client is in a different process) is 676 // already a copy, so just reference it directly. 677 op.mDrawTransaction = postDrawTransaction; 678 } 679 } else { 680 op.mDrawTransaction.merge(postDrawTransaction); 681 } 682 mDisplayContent.finishAsyncRotation(w.mToken); 683 return true; 684 } 685 686 @Override getFadeInAnimation()687 public Animation getFadeInAnimation() { 688 final Animation anim = super.getFadeInAnimation(); 689 if (mHasScreenRotationAnimation) { 690 // Use a shorter animation so it is easier to align with screen rotation animation. 691 anim.setDuration(getScaledDuration(SHORT_DURATION_MS)); 692 } 693 return anim; 694 } 695 696 @Override getFadeOutAnimation()697 public Animation getFadeOutAnimation() { 698 if (mHideImmediately) { 699 // For change transition, the hide transaction needs to be applied with sync transaction 700 // (setupStartTransaction). So keep alpha 1 just to get the animation leash. 701 final float alpha = mTransitionOp == OP_CHANGE ? 1 : 0; 702 return new AlphaAnimation(alpha /* fromAlpha */, alpha /* toAlpha */); 703 } 704 return super.getFadeOutAnimation(); 705 } 706 707 /** 708 * Returns {@code true} if the corresponding window can draw its latest content before the 709 * start transaction of rotation transition is applied. 710 */ canDrawBeforeStartTransaction(Operation op)711 private boolean canDrawBeforeStartTransaction(Operation op) { 712 return (op.mActions & Operation.ACTION_SEAMLESS) == 0; 713 } 714 dump(PrintWriter pw, String prefix)715 void dump(PrintWriter pw, String prefix) { 716 pw.println(prefix + "AsyncRotationController"); 717 prefix += " "; 718 pw.println(prefix + "mTransitionOp=" + mTransitionOp); 719 pw.println(prefix + "mIsStartTransactionCommitted=" + mIsStartTransactionCommitted); 720 pw.println(prefix + "mIsSyncDrawRequested=" + mIsSyncDrawRequested); 721 pw.println(prefix + "mOriginalRotation=" + mOriginalRotation); 722 pw.println(prefix + "mTargetWindowTokens=" + mTargetWindowTokens); 723 } 724 725 /** The operation to control the rotation appearance associated with window token. */ 726 private static class Operation { 727 @Retention(RetentionPolicy.SOURCE) 728 @IntDef(flag = true, value = { ACTION_SEAMLESS, ACTION_FADE, ACTION_TOGGLE_IME }) 729 @interface Action {} 730 731 static final int ACTION_SEAMLESS = 1; 732 static final int ACTION_FADE = 1 << 1; 733 /** The action to toggle the IME window appearance. It can only be used exclusively. */ 734 static final int ACTION_TOGGLE_IME = 1 << 2; 735 final @Action int mActions; 736 /** The leash of window token. It can be animation leash or the token itself. */ 737 SurfaceControl mLeash; 738 /** Whether the window is drawn before the transition starts. */ 739 boolean mIsCompletionPending; 740 741 /** 742 * The sync transaction of the target window. It is used when the display has rotated but 743 * the window needs to show in previous rotation. The transaction will be applied after the 744 * the start transaction of transition, so there won't be a flickering such as the window 745 * has redrawn during fading out. 746 */ 747 SurfaceControl.Transaction mDrawTransaction; 748 Operation(@ction int actions)749 Operation(@Action int actions) { 750 mActions = actions; 751 } 752 isValidSeamless()753 boolean isValidSeamless() { 754 return (mActions & ACTION_SEAMLESS) != 0 && mLeash != null && mLeash.isValid(); 755 } 756 757 @Override toString()758 public String toString() { 759 return "Operation{a=" + mActions + " pending=" + mIsCompletionPending + '}'; 760 } 761 } 762 } 763