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