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