• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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