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.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.function.Consumer; 39 40 /** 41 * Controller to handle the appearance of non-activity windows which can update asynchronously when 42 * the display rotation is changing. This is an optimization to reduce the latency to start screen 43 * rotation or app transition animation. 44 * <pre>The appearance: 45 * - Open app with rotation change: the target windows are faded out with open transition, and then 46 * faded in after the transition when the windows are drawn with new rotation. 47 * - Normal rotation: the target windows are hidden by a parent leash with zero alpha after the 48 * screenshot layer is shown, and will be faded in when they are drawn with new rotation. 49 * - Seamless rotation: Only shell transition uses this controller in this case. The target windows 50 * will be requested to use sync transaction individually. Their window token will rotate to old 51 * rotation. After the start transaction of transition is applied and the window is drawn in new 52 * rotation, the old rotation transformation will be removed with applying the sync transaction. 53 * </pre> 54 * For the windows which are forced to be seamless (e.g. screen decor overlay), the case is the 55 * same as above mentioned seamless rotation (only shell). Just the appearance may be mixed, e.g. 56 * 2 windows FADE and 2 windows SEAMLESS in normal rotation or app transition. And 4 (all) windows 57 * SEAMLESS in seamless rotation. 58 */ 59 class AsyncRotationController extends FadeAnimationController implements Consumer<WindowState> { 60 private static final String TAG = "AsyncRotation"; 61 private static final boolean DEBUG = false; 62 63 private final WindowManagerService mService; 64 /** The map of async windows to the operations of rotation appearance. */ 65 private final ArrayMap<WindowToken, Operation> mTargetWindowTokens = new ArrayMap<>(); 66 /** If non-null, it usually indicates that there will be a screen rotation animation. */ 67 private Runnable mTimeoutRunnable; 68 /** Non-null to indicate that the navigation bar is always handled by legacy seamless. */ 69 private WindowToken mNavBarToken; 70 71 /** A runnable which gets called when the {@link #completeAll()} is called. */ 72 private Runnable mOnShowRunnable; 73 74 /** Whether to use constant zero alpha animation. */ 75 private boolean mHideImmediately; 76 77 /** The case of legacy transition. */ 78 private static final int OP_LEGACY = 0; 79 /** It is usually OPEN/CLOSE/TO_FRONT/TO_BACK. */ 80 private static final int OP_APP_SWITCH = 1; 81 /** The normal display change transition which should have a screen rotation animation. */ 82 private static final int OP_CHANGE = 2; 83 /** The app requests seamless and the display supports. But the decision is still in shell. */ 84 private static final int OP_CHANGE_MAY_SEAMLESS = 3; 85 86 @Retention(RetentionPolicy.SOURCE) 87 @IntDef(value = { OP_LEGACY, OP_APP_SWITCH, OP_CHANGE, OP_CHANGE_MAY_SEAMLESS }) 88 @interface TransitionOp {} 89 90 /** Non-zero if this controller is triggered by shell transition. */ 91 private final @TransitionOp int mTransitionOp; 92 93 /** Whether the start transaction of the transition is committed (by shell). */ 94 private boolean mIsStartTransactionCommitted; 95 96 /** Whether the target windows have been requested to sync their draw transactions. */ 97 private boolean mIsSyncDrawRequested; 98 99 private SeamlessRotator mRotator; 100 101 private final int mOriginalRotation; 102 private final boolean mHasScreenRotationAnimation; 103 AsyncRotationController(DisplayContent displayContent)104 AsyncRotationController(DisplayContent displayContent) { 105 super(displayContent); 106 mService = displayContent.mWmService; 107 mOriginalRotation = displayContent.getWindowConfiguration().getRotation(); 108 final int transitionType = 109 displayContent.mTransitionController.getCollectingTransitionType(); 110 if (transitionType == WindowManager.TRANSIT_CHANGE) { 111 final DisplayRotation dr = displayContent.getDisplayRotation(); 112 final WindowState w = displayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow(); 113 // A rough condition to check whether it may be seamless style. Though the final 114 // decision in shell may be different, it is fine because the jump cut can be covered 115 // by a screenshot if shell falls back to use normal rotation animation. 116 if (w != null && w.mAttrs.rotationAnimation == ROTATION_ANIMATION_SEAMLESS 117 && w.getTask() != null 118 && dr.canRotateSeamlessly(mOriginalRotation, dr.getRotation())) { 119 mTransitionOp = OP_CHANGE_MAY_SEAMLESS; 120 } else { 121 mTransitionOp = OP_CHANGE; 122 } 123 } else if (displayContent.mTransitionController.isShellTransitionsEnabled()) { 124 mTransitionOp = OP_APP_SWITCH; 125 } else { 126 mTransitionOp = OP_LEGACY; 127 } 128 129 // Although OP_CHANGE_MAY_SEAMLESS may still play screen rotation animation because shell 130 // decides not to perform seamless rotation, it only affects whether to use fade animation 131 // when the windows are drawn. If the windows are not too slow (after rotation animation is 132 // done) to be drawn, the visual result can still look smooth. 133 mHasScreenRotationAnimation = 134 displayContent.getRotationAnimation() != null || mTransitionOp == OP_CHANGE; 135 if (mHasScreenRotationAnimation) { 136 // Hide the windows immediately because screen should have been covered by screenshot. 137 mHideImmediately = true; 138 } 139 140 // Collect the windows which can rotate asynchronously without blocking the display. 141 displayContent.forAllWindows(this, true /* traverseTopToBottom */); 142 143 // Legacy animation doesn't need to wait for the start transaction. 144 if (mTransitionOp == OP_LEGACY) { 145 mIsStartTransactionCommitted = true; 146 } else if (displayContent.mTransitionController.isCollecting(displayContent)) { 147 keepAppearanceInPreviousRotation(); 148 } 149 } 150 151 /** Assigns the operation for the window tokens which can update rotation asynchronously. */ 152 @Override accept(WindowState w)153 public void accept(WindowState w) { 154 if (!w.mHasSurface || !canBeAsync(w.mToken)) { 155 return; 156 } 157 if (mTransitionOp == OP_LEGACY && w.mForceSeamlesslyRotate) { 158 // Legacy transition already handles seamlessly windows. 159 return; 160 } 161 if (w.mAttrs.type == TYPE_NAVIGATION_BAR) { 162 int action = Operation.ACTION_FADE; 163 final boolean navigationBarCanMove = 164 mDisplayContent.getDisplayPolicy().navigationBarCanMove(); 165 if (mTransitionOp == OP_LEGACY) { 166 mNavBarToken = w.mToken; 167 // Do not animate movable navigation bar (e.g. 3-buttons mode). 168 if (navigationBarCanMove) return; 169 // Or when the navigation bar is currently controlled by recents animation. 170 final RecentsAnimationController recents = mService.getRecentsAnimationController(); 171 if (recents != null && recents.isNavigationBarAttachedToApp()) { 172 return; 173 } 174 } else if (navigationBarCanMove || mTransitionOp == OP_CHANGE_MAY_SEAMLESS) { 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 (mTargetWindowTokens.valueAt(i).canDrawBeforeStartTransaction()) { 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 /** Lets the window fit in new rotation naturally. */ finishOp(WindowToken windowToken)221 private void finishOp(WindowToken windowToken) { 222 final Operation op = mTargetWindowTokens.remove(windowToken); 223 if (op == null) return; 224 if (op.mDrawTransaction != null) { 225 // Unblock the window to show its latest content. 226 mDisplayContent.getPendingTransaction().merge(op.mDrawTransaction); 227 op.mDrawTransaction = null; 228 if (DEBUG) Slog.d(TAG, "finishOp merge transaction " + windowToken.getTopChild()); 229 } 230 if (op.mAction == Operation.ACTION_FADE) { 231 if (DEBUG) Slog.d(TAG, "finishOp fade-in " + windowToken.getTopChild()); 232 // The previous animation leash will be dropped when preparing fade-in animation, so 233 // simply apply new animation without restoring the transformation. 234 fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM); 235 } else if (op.mAction == Operation.ACTION_SEAMLESS && mRotator != null 236 && op.mLeash != null && op.mLeash.isValid()) { 237 if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild()); 238 mRotator.setIdentityMatrix(mDisplayContent.getPendingTransaction(), op.mLeash); 239 } 240 } 241 242 /** 243 * Completes all operations such as applying fade-in animation on the previously hidden window 244 * tokens. This is called if all windows are ready in new rotation or timed out. 245 */ completeAll()246 void completeAll() { 247 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 248 finishOp(mTargetWindowTokens.keyAt(i)); 249 } 250 mTargetWindowTokens.clear(); 251 if (mTimeoutRunnable != null) { 252 mService.mH.removeCallbacks(mTimeoutRunnable); 253 } 254 if (mOnShowRunnable != null) { 255 mOnShowRunnable.run(); 256 mOnShowRunnable = null; 257 } 258 } 259 260 /** 261 * Notifies that the window is ready in new rotation. Returns {@code true} if all target 262 * windows have completed their rotation operations. 263 */ completeRotation(WindowToken token)264 boolean completeRotation(WindowToken token) { 265 if (!mIsStartTransactionCommitted) { 266 final Operation op = mTargetWindowTokens.get(token); 267 // The animation or draw transaction should only start after the start transaction is 268 // applied by shell (e.g. show screenshot layer). Otherwise the window will be blinking 269 // before the rotation animation starts. So store to a pending list and animate them 270 // until the transaction is committed. 271 if (op != null) { 272 if (DEBUG) Slog.d(TAG, "Complete set pending " + token.getTopChild()); 273 op.mIsCompletionPending = true; 274 } 275 return false; 276 } 277 if (mTransitionOp == OP_APP_SWITCH && token.mTransitionController.inTransition()) { 278 final Operation op = mTargetWindowTokens.get(token); 279 if (op != null && op.mAction == Operation.ACTION_FADE) { 280 // Defer showing to onTransitionFinished(). 281 if (DEBUG) Slog.d(TAG, "Defer completion " + token.getTopChild()); 282 return false; 283 } 284 } 285 if (!isTargetToken(token)) return false; 286 if (mHasScreenRotationAnimation || mTransitionOp != OP_LEGACY) { 287 if (DEBUG) Slog.d(TAG, "Complete directly " + token.getTopChild()); 288 finishOp(token); 289 if (mTargetWindowTokens.isEmpty()) { 290 if (mTimeoutRunnable != null) mService.mH.removeCallbacks(mTimeoutRunnable); 291 return true; 292 } 293 } 294 // The case (legacy fixed rotation) will be handled by completeAll() when all seamless 295 // windows are done. 296 return false; 297 } 298 299 /** 300 * Prepares the corresponding operations (e.g. hide animation) for the window tokens which may 301 * be seamlessly rotated later. 302 */ start()303 void start() { 304 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 305 final WindowToken windowToken = mTargetWindowTokens.keyAt(i); 306 final Operation op = mTargetWindowTokens.valueAt(i); 307 if (op.mAction == Operation.ACTION_FADE) { 308 fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM); 309 op.mLeash = windowToken.getAnimationLeash(); 310 if (DEBUG) Slog.d(TAG, "Start fade-out " + windowToken.getTopChild()); 311 } else if (op.mAction == Operation.ACTION_SEAMLESS) { 312 op.mLeash = windowToken.mSurfaceControl; 313 if (DEBUG) Slog.d(TAG, "Start seamless " + windowToken.getTopChild()); 314 } 315 } 316 if (mHasScreenRotationAnimation) { 317 scheduleTimeout(); 318 } 319 } 320 scheduleTimeout()321 private void scheduleTimeout() { 322 if (mTimeoutRunnable == null) { 323 mTimeoutRunnable = () -> { 324 synchronized (mService.mGlobalLock) { 325 Slog.i(TAG, "Async rotation timeout: " + mTargetWindowTokens); 326 mIsStartTransactionCommitted = true; 327 mDisplayContent.finishAsyncRotationIfPossible(); 328 mService.mWindowPlacerLocked.performSurfacePlacement(); 329 } 330 }; 331 } 332 mService.mH.postDelayed(mTimeoutRunnable, 333 WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION); 334 } 335 336 /** Hides the window immediately until it is drawn in new rotation. */ hideImmediately(WindowToken windowToken)337 void hideImmediately(WindowToken windowToken) { 338 final boolean original = mHideImmediately; 339 mHideImmediately = true; 340 final Operation op = new Operation(Operation.ACTION_FADE); 341 mTargetWindowTokens.put(windowToken, op); 342 fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM); 343 op.mLeash = windowToken.getAnimationLeash(); 344 mHideImmediately = original; 345 if (DEBUG) Slog.d(TAG, "hideImmediately " + windowToken.getTopChild()); 346 } 347 348 /** Returns {@code true} if the window will rotate independently. */ isAsync(WindowState w)349 boolean isAsync(WindowState w) { 350 return w.mToken == mNavBarToken 351 || (w.mForceSeamlesslyRotate && mTransitionOp == OP_LEGACY) 352 || isTargetToken(w.mToken); 353 } 354 355 /** Returns {@code true} if the controller will run fade animations on the window. */ isTargetToken(WindowToken token)356 boolean isTargetToken(WindowToken token) { 357 return mTargetWindowTokens.containsKey(token); 358 } 359 360 /** 361 * Whether the insets animation leash should use previous position when running fade animation 362 * or seamless transformation in a rotated display. 363 */ shouldFreezeInsetsPosition(WindowState w)364 boolean shouldFreezeInsetsPosition(WindowState w) { 365 if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) { 366 // Expect a screenshot layer has covered the screen, so it is fine to let client side 367 // insets animation runner update the position directly. 368 return false; 369 } 370 return mTransitionOp != OP_LEGACY && !mIsStartTransactionCommitted 371 && isTargetToken(w.mToken); 372 } 373 374 /** 375 * Returns the transaction which will be applied after the window redraws in new rotation. 376 * This is used to update the position of insets animation leash synchronously. 377 */ getDrawTransaction(WindowToken token)378 SurfaceControl.Transaction getDrawTransaction(WindowToken token) { 379 if (mTransitionOp == OP_LEGACY) { 380 // Legacy transition uses startSeamlessRotation and finishSeamlessRotation of 381 // InsetsSourceProvider. 382 return null; 383 } 384 final Operation op = mTargetWindowTokens.get(token); 385 if (op != null) { 386 if (op.mDrawTransaction == null) { 387 op.mDrawTransaction = new SurfaceControl.Transaction(); 388 } 389 return op.mDrawTransaction; 390 } 391 return null; 392 } 393 setOnShowRunnable(Runnable onShowRunnable)394 void setOnShowRunnable(Runnable onShowRunnable) { 395 mOnShowRunnable = onShowRunnable; 396 } 397 398 /** 399 * Puts initial operation of leash to the transaction which will be executed when the 400 * transition starts. And associate transaction callback to consume pending animations. 401 */ setupStartTransaction(SurfaceControl.Transaction t)402 void setupStartTransaction(SurfaceControl.Transaction t) { 403 if (mIsStartTransactionCommitted) return; 404 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 405 final Operation op = mTargetWindowTokens.valueAt(i); 406 final SurfaceControl leash = op.mLeash; 407 if (leash == null || !leash.isValid()) continue; 408 if (mHasScreenRotationAnimation && op.mAction == Operation.ACTION_FADE) { 409 // Hide the windows immediately because a screenshot layer should cover the screen. 410 t.setAlpha(leash, 0f); 411 if (DEBUG) { 412 Slog.d(TAG, "Setup alpha0 " + mTargetWindowTokens.keyAt(i).getTopChild()); 413 } 414 } else { 415 // Take OPEN/CLOSE transition type as the example, the non-activity windows need to 416 // fade out in previous rotation while display has rotated to the new rotation, so 417 // their leashes are transformed with the start transaction. 418 if (mRotator == null) { 419 mRotator = new SeamlessRotator(mOriginalRotation, 420 mDisplayContent.getWindowConfiguration().getRotation(), 421 mDisplayContent.getDisplayInfo(), 422 false /* applyFixedTransformationHint */); 423 } 424 mRotator.applyTransform(t, leash); 425 if (DEBUG) { 426 Slog.d(TAG, "Setup unrotate " + mTargetWindowTokens.keyAt(i).getTopChild()); 427 } 428 } 429 } 430 431 // If there are windows have redrawn in new rotation but the start transaction has not 432 // been applied yet, the fade-in animation will be deferred. So once the transaction is 433 // committed, the fade-in animation can run with screen rotation animation. 434 t.addTransactionCommittedListener(new HandlerExecutor(mService.mH), () -> { 435 synchronized (mService.mGlobalLock) { 436 if (DEBUG) Slog.d(TAG, "Start transaction is committed"); 437 mIsStartTransactionCommitted = true; 438 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 439 if (mTargetWindowTokens.valueAt(i).mIsCompletionPending) { 440 if (DEBUG) { 441 Slog.d(TAG, "Continue pending completion " 442 + mTargetWindowTokens.keyAt(i).getTopChild()); 443 } 444 mDisplayContent.finishAsyncRotation(mTargetWindowTokens.keyAt(i)); 445 } 446 } 447 } 448 }); 449 } 450 451 /** Called when the transition by shell is done. */ onTransitionFinished()452 void onTransitionFinished() { 453 if (mTransitionOp == OP_CHANGE) { 454 // With screen rotation animation, the windows are always faded in when they are drawn. 455 // Because if they are drawn fast enough, the fade animation should not be observable. 456 return; 457 } 458 if (DEBUG) Slog.d(TAG, "onTransitionFinished " + mTargetWindowTokens); 459 // For other transition types, the fade-in animation runs after the transition to make the 460 // transition animation (e.g. launch activity) look cleaner. 461 for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { 462 final WindowToken token = mTargetWindowTokens.keyAt(i); 463 if (!token.isVisible()) { 464 mDisplayContent.finishAsyncRotation(token); 465 continue; 466 } 467 for (int j = token.getChildCount() - 1; j >= 0; j--) { 468 // Only fade in the drawn windows. If the remaining windows are drawn later, 469 // show(WindowToken) will be called to fade in them. 470 if (token.getChildAt(j).isDrawFinishedLw()) { 471 mDisplayContent.finishAsyncRotation(token); 472 break; 473 } 474 } 475 } 476 if (!mTargetWindowTokens.isEmpty()) { 477 scheduleTimeout(); 478 } 479 } 480 481 /** 482 * Captures the post draw transaction if the window should keep its appearance in previous 483 * rotation when running transition. Returns {@code true} if the draw transaction is handled 484 * by this controller. 485 */ handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction)486 boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) { 487 if (mTransitionOp == OP_LEGACY || postDrawTransaction == null || !mIsSyncDrawRequested) { 488 return false; 489 } 490 final Operation op = mTargetWindowTokens.get(w.mToken); 491 if (op == null || op.canDrawBeforeStartTransaction()) return false; 492 if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w); 493 if (op.mDrawTransaction == null) { 494 if (w.isClientLocal()) { 495 // Use a new transaction to merge the draw transaction of local window because the 496 // same instance will be cleared (Transaction#clear()) after reporting draw. 497 op.mDrawTransaction = mService.mTransactionFactory.get(); 498 op.mDrawTransaction.merge(postDrawTransaction); 499 } else { 500 // The transaction read from parcel (the client is in a different process) is 501 // already a copy, so just reference it directly. 502 op.mDrawTransaction = postDrawTransaction; 503 } 504 } else { 505 op.mDrawTransaction.merge(postDrawTransaction); 506 } 507 mDisplayContent.finishAsyncRotation(w.mToken); 508 return true; 509 } 510 511 @Override getFadeInAnimation()512 public Animation getFadeInAnimation() { 513 if (mHasScreenRotationAnimation) { 514 // Use a shorter animation so it is easier to align with screen rotation animation. 515 return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter); 516 } 517 return super.getFadeInAnimation(); 518 } 519 520 @Override getFadeOutAnimation()521 public Animation getFadeOutAnimation() { 522 if (mHideImmediately) { 523 // For change transition, the hide transaction needs to be applied with sync transaction 524 // (setupStartTransaction). So keep alpha 1 just to get the animation leash. 525 final float alpha = mTransitionOp == OP_CHANGE ? 1 : 0; 526 return new AlphaAnimation(alpha /* fromAlpha */, alpha /* toAlpha */); 527 } 528 return super.getFadeOutAnimation(); 529 } 530 531 /** The operation to control the rotation appearance associated with window token. */ 532 private static class Operation { 533 @Retention(RetentionPolicy.SOURCE) 534 @IntDef(value = { ACTION_SEAMLESS, ACTION_FADE }) 535 @interface Action {} 536 537 static final int ACTION_SEAMLESS = 1; 538 static final int ACTION_FADE = 2; 539 final @Action int mAction; 540 /** The leash of window token. It can be animation leash or the token itself. */ 541 SurfaceControl mLeash; 542 /** Whether the window is drawn before the transition starts. */ 543 boolean mIsCompletionPending; 544 545 /** 546 * The sync transaction of the target window. It is used when the display has rotated but 547 * the window needs to show in previous rotation. The transaction will be applied after the 548 * the start transaction of transition, so there won't be a flickering such as the window 549 * has redrawn during fading out. 550 */ 551 SurfaceControl.Transaction mDrawTransaction; 552 Operation(@ction int action)553 Operation(@Action int action) { 554 mAction = action; 555 } 556 557 /** 558 * Returns {@code true} if the corresponding window can draw its latest content before the 559 * start transaction of rotation transition is applied. 560 */ canDrawBeforeStartTransaction()561 boolean canDrawBeforeStartTransaction() { 562 return TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST 563 && mAction != ACTION_SEAMLESS; 564 } 565 } 566 } 567