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