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