|
|
|
|
@@ -16,10 +16,16 @@
|
|
|
|
|
|
|
|
|
|
package com.android.server.wm;
|
|
|
|
|
|
|
|
|
|
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
|
|
|
|
|
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
|
|
|
|
|
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
|
|
|
|
|
|
|
|
|
|
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
|
|
|
|
|
|
|
|
|
|
import android.annotation.IntDef;
|
|
|
|
|
import android.os.HandlerExecutor;
|
|
|
|
|
import android.util.ArrayMap;
|
|
|
|
|
import android.util.Slog;
|
|
|
|
|
import android.view.SurfaceControl;
|
|
|
|
|
import android.view.WindowManager;
|
|
|
|
|
import android.view.animation.AlphaAnimation;
|
|
|
|
|
@@ -28,136 +34,197 @@ import android.view.animation.AnimationUtils;
|
|
|
|
|
|
|
|
|
|
import com.android.internal.R;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.lang.annotation.Retention;
|
|
|
|
|
import java.lang.annotation.RetentionPolicy;
|
|
|
|
|
import java.util.function.Consumer;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Controller to fade out and in windows when the display is changing rotation. It can be used for
|
|
|
|
|
* both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
|
|
|
|
|
* the windows until they are drawn with the new rotation.
|
|
|
|
|
* Controller to handle the appearance of non-activity windows which can update asynchronously when
|
|
|
|
|
* the display rotation is changing. This is an optimization to reduce the latency to start screen
|
|
|
|
|
* rotation or app transition animation.
|
|
|
|
|
* <pre>The appearance:
|
|
|
|
|
* - Open app with rotation change: the target windows are faded out with open transition, and then
|
|
|
|
|
* faded in after the transition when the windows are drawn with new rotation.
|
|
|
|
|
* - Normal rotation: the target windows are hidden by a parent leash with zero alpha after the
|
|
|
|
|
* screenshot layer is shown, and will be faded in when they are drawn with new rotation.
|
|
|
|
|
* - Seamless rotation: Only shell transition uses this controller in this case. The target windows
|
|
|
|
|
* will be requested to use sync transaction individually. Their window token will rotate to old
|
|
|
|
|
* rotation. After the start transaction of transition is applied and the window is drawn in new
|
|
|
|
|
* rotation, the old rotation transformation will be removed with applying the sync transaction.
|
|
|
|
|
* </pre>
|
|
|
|
|
* For the windows which are forced to be seamless (e.g. screen decor overlay), the case is the
|
|
|
|
|
* same as above mentioned seamless rotation (only shell). Just the appearance may be mixed, e.g.
|
|
|
|
|
* 2 windows FADE and 2 windows SEAMLESS in normal rotation or app transition. And 4 (all) windows
|
|
|
|
|
* SEAMLESS in seamless rotation.
|
|
|
|
|
*/
|
|
|
|
|
public class AsyncRotationController extends FadeAnimationController {
|
|
|
|
|
class AsyncRotationController extends FadeAnimationController implements Consumer<WindowState> {
|
|
|
|
|
private static final String TAG = "AsyncRotation";
|
|
|
|
|
private static final boolean DEBUG = false;
|
|
|
|
|
|
|
|
|
|
/** The map of window token to its animation leash. */
|
|
|
|
|
private final ArrayMap<WindowToken, SurfaceControl> mTargetWindowTokens = new ArrayMap<>();
|
|
|
|
|
private final WindowManagerService mService;
|
|
|
|
|
/** The map of async windows to the operations of rotation appearance. */
|
|
|
|
|
private final ArrayMap<WindowToken, Operation> mTargetWindowTokens = new ArrayMap<>();
|
|
|
|
|
/** If non-null, it usually indicates that there will be a screen rotation animation. */
|
|
|
|
|
private final Runnable mTimeoutRunnable;
|
|
|
|
|
private final WindowToken mNavBarToken;
|
|
|
|
|
private Runnable mTimeoutRunnable;
|
|
|
|
|
/** Non-null to indicate that the navigation bar is always handled by legacy seamless. */
|
|
|
|
|
private WindowToken mNavBarToken;
|
|
|
|
|
|
|
|
|
|
/** A runnable which gets called when the {@link #show()} is called. */
|
|
|
|
|
/** A runnable which gets called when the {@link #completeAll()} is called. */
|
|
|
|
|
private Runnable mOnShowRunnable;
|
|
|
|
|
|
|
|
|
|
/** Whether to use constant zero alpha animation. */
|
|
|
|
|
private boolean mHideImmediately;
|
|
|
|
|
|
|
|
|
|
/** Whether this controller is triggered from shell transition with type CHANGE. */
|
|
|
|
|
private final boolean mIsChangeTransition;
|
|
|
|
|
/** The case of legacy transition. */
|
|
|
|
|
private static final int OP_LEGACY = 0;
|
|
|
|
|
/** It is usually OPEN/CLOSE/TO_FRONT/TO_BACK. */
|
|
|
|
|
private static final int OP_APP_SWITCH = 1;
|
|
|
|
|
/** The normal display change transition which should have a screen rotation animation. */
|
|
|
|
|
private static final int OP_CHANGE = 2;
|
|
|
|
|
/** The app requests seamless and the display supports. But the decision is still in shell. */
|
|
|
|
|
private static final int OP_CHANGE_MAY_SEAMLESS = 3;
|
|
|
|
|
|
|
|
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
|
|
|
@IntDef(value = { OP_LEGACY, OP_APP_SWITCH, OP_CHANGE, OP_CHANGE_MAY_SEAMLESS })
|
|
|
|
|
@interface TransitionOp {}
|
|
|
|
|
|
|
|
|
|
/** Non-zero if this controller is triggered by shell transition. */
|
|
|
|
|
private final @TransitionOp int mTransitionOp;
|
|
|
|
|
|
|
|
|
|
/** Whether the start transaction of the transition is committed (by shell). */
|
|
|
|
|
private boolean mIsStartTransactionCommitted;
|
|
|
|
|
|
|
|
|
|
/** The list to store the drawn tokens before the rotation animation starts. */
|
|
|
|
|
private ArrayList<WindowToken> mPendingShowTokens;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The sync transactions of the target windows. It is used when the display has rotated but
|
|
|
|
|
* the windows need to fade out in previous rotation. These transactions will be applied with
|
|
|
|
|
* fade-in animation, so there won't be a flickering such as the windows have redrawn during
|
|
|
|
|
* fading out.
|
|
|
|
|
*/
|
|
|
|
|
private ArrayMap<WindowState, SurfaceControl.Transaction> mCapturedDrawTransactions;
|
|
|
|
|
private SeamlessRotator mRotator;
|
|
|
|
|
|
|
|
|
|
private final int mOriginalRotation;
|
|
|
|
|
private final boolean mHasScreenRotationAnimation;
|
|
|
|
|
|
|
|
|
|
public AsyncRotationController(DisplayContent displayContent) {
|
|
|
|
|
AsyncRotationController(DisplayContent displayContent) {
|
|
|
|
|
super(displayContent);
|
|
|
|
|
mService = displayContent.mWmService;
|
|
|
|
|
mOriginalRotation = displayContent.getWindowConfiguration().getRotation();
|
|
|
|
|
final int transitionType =
|
|
|
|
|
displayContent.mTransitionController.getCollectingTransitionType();
|
|
|
|
|
mIsChangeTransition = transitionType == WindowManager.TRANSIT_CHANGE;
|
|
|
|
|
// Only CHANGE type (rotation animation) needs to wait for the start transaction.
|
|
|
|
|
mIsStartTransactionCommitted = !mIsChangeTransition;
|
|
|
|
|
mTimeoutRunnable = displayContent.inTransition() ? () -> {
|
|
|
|
|
synchronized (mService.mGlobalLock) {
|
|
|
|
|
displayContent.finishAsyncRotationIfPossible();
|
|
|
|
|
mService.mWindowPlacerLocked.performSurfacePlacement();
|
|
|
|
|
if (transitionType == WindowManager.TRANSIT_CHANGE) {
|
|
|
|
|
final DisplayRotation dr = displayContent.getDisplayRotation();
|
|
|
|
|
final WindowState w = displayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow();
|
|
|
|
|
// A rough condition to check whether it may be seamless style. Though the final
|
|
|
|
|
// decision in shell may be different, it is fine because the jump cut can be covered
|
|
|
|
|
// by a screenshot if shell falls back to use normal rotation animation.
|
|
|
|
|
if (w != null && w.mAttrs.rotationAnimation == ROTATION_ANIMATION_SEAMLESS
|
|
|
|
|
&& w.getTask() != null
|
|
|
|
|
&& dr.canRotateSeamlessly(mOriginalRotation, dr.getRotation())) {
|
|
|
|
|
mTransitionOp = OP_CHANGE_MAY_SEAMLESS;
|
|
|
|
|
} else {
|
|
|
|
|
mTransitionOp = OP_CHANGE;
|
|
|
|
|
}
|
|
|
|
|
} : null;
|
|
|
|
|
} else if (transitionType != WindowManager.TRANSIT_NONE) {
|
|
|
|
|
mTransitionOp = OP_APP_SWITCH;
|
|
|
|
|
} else {
|
|
|
|
|
mTransitionOp = OP_LEGACY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Although OP_CHANGE_MAY_SEAMLESS may still play screen rotation animation because shell
|
|
|
|
|
// decides not to perform seamless rotation, it only affects whether to use fade animation
|
|
|
|
|
// when the windows are drawn. If the windows are not too slow (after rotation animation is
|
|
|
|
|
// done) to be drawn, the visual result can still look smooth.
|
|
|
|
|
mHasScreenRotationAnimation =
|
|
|
|
|
displayContent.getRotationAnimation() != null || mIsChangeTransition;
|
|
|
|
|
displayContent.getRotationAnimation() != null || mTransitionOp == OP_CHANGE;
|
|
|
|
|
if (mHasScreenRotationAnimation) {
|
|
|
|
|
// Hide the windows immediately because screen should have been covered by screenshot.
|
|
|
|
|
mHideImmediately = true;
|
|
|
|
|
}
|
|
|
|
|
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
|
|
|
|
|
final WindowState navigationBar = displayPolicy.getNavigationBar();
|
|
|
|
|
if (navigationBar != null) {
|
|
|
|
|
mNavBarToken = navigationBar.mToken;
|
|
|
|
|
final RecentsAnimationController controller = mService.getRecentsAnimationController();
|
|
|
|
|
final boolean navBarControlledByRecents =
|
|
|
|
|
controller != null && controller.isNavigationBarAttachedToApp();
|
|
|
|
|
// Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation
|
|
|
|
|
// bar is currently controlled by recents animation.
|
|
|
|
|
if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) {
|
|
|
|
|
mTargetWindowTokens.put(mNavBarToken, null);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
mNavBarToken = null;
|
|
|
|
|
}
|
|
|
|
|
// Collect the target windows to fade out. The display won't wait for them to unfreeze.
|
|
|
|
|
final WindowState notificationShade = displayPolicy.getNotificationShade();
|
|
|
|
|
displayContent.forAllWindows(w -> {
|
|
|
|
|
if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate
|
|
|
|
|
&& !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar
|
|
|
|
|
&& w != notificationShade) {
|
|
|
|
|
mTargetWindowTokens.put(w.mToken, null);
|
|
|
|
|
}
|
|
|
|
|
}, true /* traverseTopToBottom */);
|
|
|
|
|
|
|
|
|
|
// Collect the windows which can rotate asynchronously without blocking the display.
|
|
|
|
|
displayContent.forAllWindows(this, true /* traverseTopToBottom */);
|
|
|
|
|
|
|
|
|
|
// Legacy animation doesn't need to wait for the start transaction.
|
|
|
|
|
mIsStartTransactionCommitted = mTransitionOp == OP_LEGACY;
|
|
|
|
|
if (mIsStartTransactionCommitted) return;
|
|
|
|
|
// The transition sync group may be finished earlier because it doesn't wait for these
|
|
|
|
|
// target windows. But the windows still need to use sync transaction to keep the appearance
|
|
|
|
|
// in previous rotation, so request a no-op sync to keep the state.
|
|
|
|
|
if (!mIsChangeTransition && transitionType != WindowManager.TRANSIT_NONE) {
|
|
|
|
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
final WindowToken token = mTargetWindowTokens.keyAt(i);
|
|
|
|
|
for (int j = token.getChildCount() - 1; j >= 0; j--) {
|
|
|
|
|
token.getChildAt(j).applyWithNextDraw(t -> {});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
|
|
|
|
|
if (show) {
|
|
|
|
|
// The previous animation leash will be dropped when preparing fade-in animation, so
|
|
|
|
|
// simply remove it without restoring the transformation.
|
|
|
|
|
mTargetWindowTokens.remove(windowToken);
|
|
|
|
|
if (mCapturedDrawTransactions != null) {
|
|
|
|
|
// Unblock the window to draw its latest content with fade-in animation.
|
|
|
|
|
final SurfaceControl.Transaction t = mDisplayContent.getPendingTransaction();
|
|
|
|
|
for (int i = windowToken.getChildCount() - 1; i >= 0; i--) {
|
|
|
|
|
final SurfaceControl.Transaction drawT =
|
|
|
|
|
mCapturedDrawTransactions.remove(windowToken.getChildAt(i));
|
|
|
|
|
if (drawT != null) {
|
|
|
|
|
t.merge(drawT);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
super.fadeWindowToken(show, windowToken, animationType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Applies show animation on the previously hidden window tokens. */
|
|
|
|
|
void show() {
|
|
|
|
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
|
|
|
|
|
if (mHasScreenRotationAnimation
|
|
|
|
|
&& mTargetWindowTokens.valueAt(i).mAction == Operation.ACTION_FADE) {
|
|
|
|
|
// The windows are hidden (leash is alpha 0) before finishing drawing so it is
|
|
|
|
|
// unnecessary to request sync.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
final WindowToken token = mTargetWindowTokens.keyAt(i);
|
|
|
|
|
for (int j = token.getChildCount() - 1; j >= 0; j--) {
|
|
|
|
|
token.getChildAt(j).applyWithNextDraw(t -> {});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Assigns the operation for the window tokens which can update rotation asynchronously. */
|
|
|
|
|
@Override
|
|
|
|
|
public void accept(WindowState w) {
|
|
|
|
|
if (w.mActivityRecord != null || !w.mHasSurface || w.mIsWallpaper || w.mIsImWindow
|
|
|
|
|
|| w.mAttrs.type == TYPE_NOTIFICATION_SHADE) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (mTransitionOp == OP_LEGACY && w.mForceSeamlesslyRotate) {
|
|
|
|
|
// Legacy transition already handles seamlessly windows.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (w.mAttrs.type == TYPE_NAVIGATION_BAR) {
|
|
|
|
|
int action = Operation.ACTION_FADE;
|
|
|
|
|
final boolean navigationBarCanMove =
|
|
|
|
|
mDisplayContent.getDisplayPolicy().navigationBarCanMove();
|
|
|
|
|
if (mTransitionOp == OP_LEGACY) {
|
|
|
|
|
mNavBarToken = w.mToken;
|
|
|
|
|
// Do not animate movable navigation bar (e.g. 3-buttons mode).
|
|
|
|
|
if (navigationBarCanMove) return;
|
|
|
|
|
// Or when the navigation bar is currently controlled by recents animation.
|
|
|
|
|
final RecentsAnimationController recents = mService.getRecentsAnimationController();
|
|
|
|
|
if (recents != null && recents.isNavigationBarAttachedToApp()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else if (navigationBarCanMove || mTransitionOp == OP_CHANGE_MAY_SEAMLESS) {
|
|
|
|
|
action = Operation.ACTION_SEAMLESS;
|
|
|
|
|
}
|
|
|
|
|
mTargetWindowTokens.put(w.mToken, new Operation(action));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final int action = mTransitionOp == OP_CHANGE_MAY_SEAMLESS || w.mForceSeamlesslyRotate
|
|
|
|
|
? Operation.ACTION_SEAMLESS : Operation.ACTION_FADE;
|
|
|
|
|
mTargetWindowTokens.put(w.mToken, new Operation(action));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Lets the window fit in new rotation naturally. */
|
|
|
|
|
private void finishOp(WindowToken windowToken) {
|
|
|
|
|
final Operation op = mTargetWindowTokens.remove(windowToken);
|
|
|
|
|
if (op == null) return;
|
|
|
|
|
if (op.mCapturedDrawTransaction != null) {
|
|
|
|
|
// Unblock the window to show its latest content.
|
|
|
|
|
mDisplayContent.getPendingTransaction().merge(op.mCapturedDrawTransaction);
|
|
|
|
|
op.mCapturedDrawTransaction = null;
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "finishOp merge transaction " + windowToken.getTopChild());
|
|
|
|
|
}
|
|
|
|
|
if (op.mAction == Operation.ACTION_FADE) {
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "finishOp fade-in " + windowToken.getTopChild());
|
|
|
|
|
// The previous animation leash will be dropped when preparing fade-in animation, so
|
|
|
|
|
// simply apply new animation without restoring the transformation.
|
|
|
|
|
fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
|
|
|
|
|
} else if (op.mAction == Operation.ACTION_SEAMLESS && mRotator != null
|
|
|
|
|
&& op.mLeash != null && op.mLeash.isValid()) {
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild());
|
|
|
|
|
mRotator.setIdentityMatrix(mDisplayContent.getPendingTransaction(), op.mLeash);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Completes all operations such as applying fade-in animation on the previously hidden window
|
|
|
|
|
* tokens. This is called if all windows are ready in new rotation or timed out.
|
|
|
|
|
*/
|
|
|
|
|
void completeAll() {
|
|
|
|
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
finishOp(mTargetWindowTokens.keyAt(i));
|
|
|
|
|
}
|
|
|
|
|
mTargetWindowTokens.clear();
|
|
|
|
|
mPendingShowTokens = null;
|
|
|
|
|
if (mTimeoutRunnable != null) {
|
|
|
|
|
mService.mH.removeCallbacks(mTimeoutRunnable);
|
|
|
|
|
}
|
|
|
|
|
@@ -168,48 +235,81 @@ public class AsyncRotationController extends FadeAnimationController {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns {@code true} if all target windows are shown. It only takes effects if this
|
|
|
|
|
* controller is created for normal rotation.
|
|
|
|
|
* Notifies that the window is ready in new rotation. Returns {@code true} if all target
|
|
|
|
|
* windows have completed their rotation operations.
|
|
|
|
|
*/
|
|
|
|
|
boolean show(WindowToken token) {
|
|
|
|
|
if (!isTargetToken(token)) return false;
|
|
|
|
|
boolean completeRotation(WindowToken token) {
|
|
|
|
|
if (!mIsStartTransactionCommitted) {
|
|
|
|
|
// The fade-in animation should only start after the screenshot layer is shown by shell.
|
|
|
|
|
// Otherwise the window will be blinking before the rotation animation starts. So store
|
|
|
|
|
// to a pending list and animate them until the transaction is committed.
|
|
|
|
|
if (mPendingShowTokens == null) {
|
|
|
|
|
mPendingShowTokens = new ArrayList<>();
|
|
|
|
|
final Operation op = mTargetWindowTokens.get(token);
|
|
|
|
|
// The animation or draw transaction should only start after the start transaction is
|
|
|
|
|
// applied by shell (e.g. show screenshot layer). Otherwise the window will be blinking
|
|
|
|
|
// before the rotation animation starts. So store to a pending list and animate them
|
|
|
|
|
// until the transaction is committed.
|
|
|
|
|
if (op != null) {
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "Complete set pending " + token.getTopChild());
|
|
|
|
|
op.mIsCompletionPending = true;
|
|
|
|
|
}
|
|
|
|
|
mPendingShowTokens.add(token);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!mHasScreenRotationAnimation && token.mTransitionController.inTransition()) {
|
|
|
|
|
// Defer showing to onTransitionFinished().
|
|
|
|
|
return false;
|
|
|
|
|
if (mTransitionOp == OP_APP_SWITCH && token.mTransitionController.inTransition()) {
|
|
|
|
|
final Operation op = mTargetWindowTokens.get(token);
|
|
|
|
|
if (op != null && op.mAction == Operation.ACTION_FADE) {
|
|
|
|
|
// Defer showing to onTransitionFinished().
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "Defer completion " + token.getTopChild());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If the timeout runnable is null (fixed rotation), the case will be handled by show().
|
|
|
|
|
if (mTimeoutRunnable != null) {
|
|
|
|
|
fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
|
|
|
|
|
if (!isTargetToken(token)) return false;
|
|
|
|
|
if (mHasScreenRotationAnimation || mTransitionOp != OP_LEGACY) {
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "Complete directly " + token.getTopChild());
|
|
|
|
|
finishOp(token);
|
|
|
|
|
if (mTargetWindowTokens.isEmpty()) {
|
|
|
|
|
mService.mH.removeCallbacks(mTimeoutRunnable);
|
|
|
|
|
if (mTimeoutRunnable != null) mService.mH.removeCallbacks(mTimeoutRunnable);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// The case (legacy fixed rotation) will be handled by completeAll() when all seamless
|
|
|
|
|
// windows are done.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Applies hide animation on the window tokens which may be seamlessly rotated later. */
|
|
|
|
|
void hide() {
|
|
|
|
|
/**
|
|
|
|
|
* Prepares the corresponding operations (e.g. hide animation) for the window tokens which may
|
|
|
|
|
* be seamlessly rotated later.
|
|
|
|
|
*/
|
|
|
|
|
void start() {
|
|
|
|
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
|
|
|
|
|
fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
|
|
|
|
|
final Operation op = mTargetWindowTokens.valueAt(i);
|
|
|
|
|
if (op.mAction == Operation.ACTION_FADE) {
|
|
|
|
|
fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
|
|
|
|
|
op.mLeash = windowToken.getAnimationLeash();
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "Start fade-out " + windowToken.getTopChild());
|
|
|
|
|
} else if (op.mAction == Operation.ACTION_SEAMLESS) {
|
|
|
|
|
op.mLeash = windowToken.mSurfaceControl;
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "Start seamless " + windowToken.getTopChild());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (mTimeoutRunnable != null) {
|
|
|
|
|
mService.mH.postDelayed(mTimeoutRunnable,
|
|
|
|
|
WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
|
|
|
|
|
if (mHasScreenRotationAnimation) {
|
|
|
|
|
scheduleTimeout();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void scheduleTimeout() {
|
|
|
|
|
if (mTimeoutRunnable == null) {
|
|
|
|
|
mTimeoutRunnable = () -> {
|
|
|
|
|
synchronized (mService.mGlobalLock) {
|
|
|
|
|
Slog.i(TAG, "Async rotation timeout: " + mTargetWindowTokens);
|
|
|
|
|
mIsStartTransactionCommitted = true;
|
|
|
|
|
mDisplayContent.finishAsyncRotationIfPossible();
|
|
|
|
|
mService.mWindowPlacerLocked.performSurfacePlacement();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
mService.mH.postDelayed(mTimeoutRunnable,
|
|
|
|
|
WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Hides the window immediately until it is drawn in new rotation. */
|
|
|
|
|
void hideImmediately(WindowToken windowToken) {
|
|
|
|
|
final boolean original = mHideImmediately;
|
|
|
|
|
@@ -218,9 +318,11 @@ public class AsyncRotationController extends FadeAnimationController {
|
|
|
|
|
mHideImmediately = original;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Returns {@code true} if the window is handled by this controller. */
|
|
|
|
|
boolean isHandledToken(WindowToken token) {
|
|
|
|
|
return token == mNavBarToken || isTargetToken(token);
|
|
|
|
|
/** Returns {@code true} if the window will rotate independently. */
|
|
|
|
|
boolean isAsync(WindowState w) {
|
|
|
|
|
return w.mToken == mNavBarToken
|
|
|
|
|
|| (w.mForceSeamlesslyRotate && mTransitionOp == OP_LEGACY)
|
|
|
|
|
|| isTargetToken(w.mToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Returns {@code true} if the controller will run fade animations on the window. */
|
|
|
|
|
@@ -233,7 +335,7 @@ public class AsyncRotationController extends FadeAnimationController {
|
|
|
|
|
* animation in rotated display.
|
|
|
|
|
*/
|
|
|
|
|
boolean shouldFreezeInsetsPosition(WindowState w) {
|
|
|
|
|
return !mHasScreenRotationAnimation && w.mTransitionController.inTransition()
|
|
|
|
|
return mTransitionOp == OP_APP_SWITCH && w.mTransitionController.inTransition()
|
|
|
|
|
&& isTargetToken(w.mToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -246,54 +348,70 @@ public class AsyncRotationController extends FadeAnimationController {
|
|
|
|
|
* transition starts. And associate transaction callback to consume pending animations.
|
|
|
|
|
*/
|
|
|
|
|
void setupStartTransaction(SurfaceControl.Transaction t) {
|
|
|
|
|
if (!mIsChangeTransition) {
|
|
|
|
|
// Take OPEN/CLOSE transition type as the example, the non-activity windows need to
|
|
|
|
|
// fade out in previous rotation while display has rotated to the new rotation, so
|
|
|
|
|
// their leashes are unrotated with the start transaction.
|
|
|
|
|
final SeamlessRotator rotator = new SeamlessRotator(mOriginalRotation,
|
|
|
|
|
mDisplayContent.getWindowConfiguration().getRotation(),
|
|
|
|
|
mDisplayContent.getDisplayInfo(),
|
|
|
|
|
false /* applyFixedTransformationHint */);
|
|
|
|
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
|
|
|
|
|
if (leash != null && leash.isValid()) {
|
|
|
|
|
rotator.applyTransform(t, leash);
|
|
|
|
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
final Operation op = mTargetWindowTokens.valueAt(i);
|
|
|
|
|
final SurfaceControl leash = op.mLeash;
|
|
|
|
|
if (leash == null || !leash.isValid()) continue;
|
|
|
|
|
if (mHasScreenRotationAnimation && op.mAction == Operation.ACTION_FADE) {
|
|
|
|
|
// Hide the windows immediately because a screenshot layer should cover the screen.
|
|
|
|
|
t.setAlpha(leash, 0f);
|
|
|
|
|
if (DEBUG) {
|
|
|
|
|
Slog.d(TAG, "Setup alpha0 " + mTargetWindowTokens.keyAt(i).getTopChild());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Take OPEN/CLOSE transition type as the example, the non-activity windows need to
|
|
|
|
|
// fade out in previous rotation while display has rotated to the new rotation, so
|
|
|
|
|
// their leashes are transformed with the start transaction.
|
|
|
|
|
if (mRotator == null) {
|
|
|
|
|
mRotator = new SeamlessRotator(mOriginalRotation,
|
|
|
|
|
mDisplayContent.getWindowConfiguration().getRotation(),
|
|
|
|
|
mDisplayContent.getDisplayInfo(),
|
|
|
|
|
false /* applyFixedTransformationHint */);
|
|
|
|
|
}
|
|
|
|
|
mRotator.applyTransform(t, leash);
|
|
|
|
|
if (DEBUG) {
|
|
|
|
|
Slog.d(TAG, "Setup unrotate " + mTargetWindowTokens.keyAt(i).getTopChild());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Hide the windows immediately because a screenshot layer should cover the screen.
|
|
|
|
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
|
|
|
|
|
if (leash != null && leash.isValid()) {
|
|
|
|
|
t.setAlpha(leash, 0f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mIsStartTransactionCommitted) return;
|
|
|
|
|
// If there are windows have redrawn in new rotation but the start transaction has not
|
|
|
|
|
// been applied yet, the fade-in animation will be deferred. So once the transaction is
|
|
|
|
|
// committed, the fade-in animation can run with screen rotation animation.
|
|
|
|
|
t.addTransactionCommittedListener(new HandlerExecutor(mService.mH), () -> {
|
|
|
|
|
synchronized (mService.mGlobalLock) {
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "Start transaction is committed");
|
|
|
|
|
mIsStartTransactionCommitted = true;
|
|
|
|
|
if (mPendingShowTokens == null) return;
|
|
|
|
|
for (int i = mPendingShowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
mDisplayContent.finishAsyncRotation(mPendingShowTokens.get(i));
|
|
|
|
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
if (mTargetWindowTokens.valueAt(i).mIsCompletionPending) {
|
|
|
|
|
if (DEBUG) {
|
|
|
|
|
Slog.d(TAG, "Continue pending completion "
|
|
|
|
|
+ mTargetWindowTokens.keyAt(i).getTopChild());
|
|
|
|
|
}
|
|
|
|
|
mDisplayContent.finishAsyncRotation(mTargetWindowTokens.keyAt(i));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mPendingShowTokens = null;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Called when the transition by shell is done. */
|
|
|
|
|
void onTransitionFinished() {
|
|
|
|
|
if (mIsChangeTransition) {
|
|
|
|
|
if (mTransitionOp == OP_CHANGE) {
|
|
|
|
|
// With screen rotation animation, the windows are always faded in when they are drawn.
|
|
|
|
|
// Because if they are drawn fast enough, the fade animation should not be observable.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "onTransitionFinished " + mTargetWindowTokens);
|
|
|
|
|
// For other transition types, the fade-in animation runs after the transition to make the
|
|
|
|
|
// transition animation (e.g. launch activity) look cleaner.
|
|
|
|
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
final WindowToken token = mTargetWindowTokens.keyAt(i);
|
|
|
|
|
if (!token.isVisible()) {
|
|
|
|
|
mDisplayContent.finishAsyncRotation(token);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
for (int j = token.getChildCount() - 1; j >= 0; j--) {
|
|
|
|
|
// Only fade in the drawn windows. If the remaining windows are drawn later,
|
|
|
|
|
// show(WindowToken) will be called to fade in them.
|
|
|
|
|
@@ -303,25 +421,35 @@ public class AsyncRotationController extends FadeAnimationController {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!mTargetWindowTokens.isEmpty()) {
|
|
|
|
|
scheduleTimeout();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Captures the post draw transaction if the window should update with fade-in animation. */
|
|
|
|
|
/**
|
|
|
|
|
* Captures the post draw transaction if the window should keep its appearance in previous
|
|
|
|
|
* rotation when running transition. Returns {@code true} if the draw transaction is handled
|
|
|
|
|
* by this controller.
|
|
|
|
|
*/
|
|
|
|
|
boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) {
|
|
|
|
|
if (mIsChangeTransition || !isTargetToken(w.mToken)) return false;
|
|
|
|
|
if (postDrawTransaction != null && w.mTransitionController.inTransition()) {
|
|
|
|
|
if (mCapturedDrawTransactions == null) {
|
|
|
|
|
mCapturedDrawTransactions = new ArrayMap<>();
|
|
|
|
|
}
|
|
|
|
|
final SurfaceControl.Transaction t = mCapturedDrawTransactions.get(w);
|
|
|
|
|
if (t == null) {
|
|
|
|
|
mCapturedDrawTransactions.put(w, postDrawTransaction);
|
|
|
|
|
} else {
|
|
|
|
|
t.merge(postDrawTransaction);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
if (mTransitionOp == OP_LEGACY || postDrawTransaction == null
|
|
|
|
|
|| !w.mTransitionController.inTransition()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
mDisplayContent.finishAsyncRotation(w.mToken);
|
|
|
|
|
return false;
|
|
|
|
|
final Operation op = mTargetWindowTokens.get(w.mToken);
|
|
|
|
|
if (op == null) return false;
|
|
|
|
|
final boolean keepUntilTransitionFinish =
|
|
|
|
|
mTransitionOp == OP_APP_SWITCH && op.mAction == Operation.ACTION_FADE;
|
|
|
|
|
final boolean keepUntilStartTransaction =
|
|
|
|
|
!mIsStartTransactionCommitted && op.mAction == Operation.ACTION_SEAMLESS;
|
|
|
|
|
if (!keepUntilTransitionFinish && !keepUntilStartTransaction) return false;
|
|
|
|
|
if (op.mCapturedDrawTransaction == null) {
|
|
|
|
|
op.mCapturedDrawTransaction = postDrawTransaction;
|
|
|
|
|
} else {
|
|
|
|
|
op.mCapturedDrawTransaction.merge(postDrawTransaction);
|
|
|
|
|
}
|
|
|
|
|
if (DEBUG) Slog.d(TAG, "Capture draw transaction " + w);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@@ -338,26 +466,36 @@ public class AsyncRotationController extends FadeAnimationController {
|
|
|
|
|
if (mHideImmediately) {
|
|
|
|
|
// For change transition, the hide transaction needs to be applied with sync transaction
|
|
|
|
|
// (setupStartTransaction). So keep alpha 1 just to get the animation leash.
|
|
|
|
|
final float alpha = mIsChangeTransition ? 1 : 0;
|
|
|
|
|
final float alpha = mTransitionOp == OP_CHANGE ? 1 : 0;
|
|
|
|
|
return new AlphaAnimation(alpha /* fromAlpha */, alpha /* toAlpha */);
|
|
|
|
|
}
|
|
|
|
|
return super.getFadeOutAnimation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
|
|
|
|
|
boolean show, WindowToken windowToken) {
|
|
|
|
|
return new FadeAnimationAdapter(animationSpec, windowToken.getSurfaceAnimationRunner(),
|
|
|
|
|
show, windowToken) {
|
|
|
|
|
@Override
|
|
|
|
|
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
|
|
|
|
|
int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
|
|
|
|
|
// The fade cycle is done when showing, so only need to store the leash when hiding.
|
|
|
|
|
if (!show) {
|
|
|
|
|
mTargetWindowTokens.put(mToken, animationLeash);
|
|
|
|
|
}
|
|
|
|
|
super.startAnimation(animationLeash, t, type, finishCallback);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
/** The operation to control the rotation appearance associated with window token. */
|
|
|
|
|
private static class Operation {
|
|
|
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
|
|
|
@IntDef(value = { ACTION_SEAMLESS, ACTION_FADE })
|
|
|
|
|
@interface Action {}
|
|
|
|
|
|
|
|
|
|
static final int ACTION_SEAMLESS = 1;
|
|
|
|
|
static final int ACTION_FADE = 2;
|
|
|
|
|
final @Action int mAction;
|
|
|
|
|
/** The leash of window token. It can be animation leash or the token itself. */
|
|
|
|
|
SurfaceControl mLeash;
|
|
|
|
|
/** Whether the window is drawn before the transition starts. */
|
|
|
|
|
boolean mIsCompletionPending;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The sync transaction of the target window. It is used when the display has rotated but
|
|
|
|
|
* the window needs to show in previous rotation. The transaction will be applied after the
|
|
|
|
|
* the start transaction of transition, so there won't be a flickering such as the window
|
|
|
|
|
* has redrawn during fading out.
|
|
|
|
|
*/
|
|
|
|
|
SurfaceControl.Transaction mCapturedDrawTransaction;
|
|
|
|
|
|
|
|
|
|
Operation(@Action int action) {
|
|
|
|
|
mAction = action;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|