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