Support independent seamless rotation with shell transition
With shell transition, the seamless windows will be unrotated with
the start transaction of transition. And restore the transformation
when the windows are drawn in new rotation after the start transaction.
Though the final decision of animation style is up to shell side,
it is fine to use seamless style for all target windows. Because if
seamless rotation is rejected, it usually falls back to use a
screenshot based animation. So if the target windows are not drawn
too slow, it has no difference to the normal rotation animation.
Just at least the condition in AsyncRotationController is more
aggressive to adopt seamless style.
The example of normal rotation:
Assume that StatusBar=S, NavigationBar=N,
ScreenDecorOverlay=O1, ScreenDecorOverlayBottom=O2
1. Transition is requested.
2. Select fade style for S and N.
Select seamless style for O1 and O2.
3. Setup start transaction.
Prepare alpha 0 for S and N.
Prepare unrotate for O1 and O2.
4. O1 is redrawn, capture its draw transaction.
5. Shell receives onTransitionReady and applies start transaction.
6. AsyncRotationController receives the transaction committed callback.
Restore the unrotate with the captured draw transaction.
7. O2 is redrawn, restore the unrotate directly.
8. S and N are redrawn, apply fade-in animation.
9. The rotation animation finishes.
For seamless rotation, just S and N also use seamless style.
Bug: 214324186
Test: atest TransitionTests#testDisplayRotationChange
TransitionTests#testAppTransitionWithRotationChange
Test: adb shell setprop persist.debug.shell_transit 1; reboot
- Rotate display with a simple activity.
- Rotate display with a seamless activity, e.g. camera.
- Rotate display with a seamless activity and app overlays.
- Start a landscape activity from a portrait activity.
Change-Id: I1b35d280c1ef6d6c5fadc990ed5528d496d77d57
This commit is contained in:
@@ -16,10 +16,16 @@
|
|||||||
|
|
||||||
package com.android.server.wm;
|
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 static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
import android.os.HandlerExecutor;
|
import android.os.HandlerExecutor;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
|
import android.util.Slog;
|
||||||
import android.view.SurfaceControl;
|
import android.view.SurfaceControl;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.AlphaAnimation;
|
import android.view.animation.AlphaAnimation;
|
||||||
@@ -28,136 +34,197 @@ import android.view.animation.AnimationUtils;
|
|||||||
|
|
||||||
import com.android.internal.R;
|
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
|
* Controller to handle the appearance of non-activity windows which can update asynchronously when
|
||||||
* both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
|
* the display rotation is changing. This is an optimization to reduce the latency to start screen
|
||||||
* the windows until they are drawn with the new rotation.
|
* 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;
|
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. */
|
/** If non-null, it usually indicates that there will be a screen rotation animation. */
|
||||||
private final Runnable mTimeoutRunnable;
|
private Runnable mTimeoutRunnable;
|
||||||
private final WindowToken mNavBarToken;
|
/** 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;
|
private Runnable mOnShowRunnable;
|
||||||
|
|
||||||
/** Whether to use constant zero alpha animation. */
|
/** Whether to use constant zero alpha animation. */
|
||||||
private boolean mHideImmediately;
|
private boolean mHideImmediately;
|
||||||
|
|
||||||
/** Whether this controller is triggered from shell transition with type CHANGE. */
|
/** The case of legacy transition. */
|
||||||
private final boolean mIsChangeTransition;
|
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). */
|
/** Whether the start transaction of the transition is committed (by shell). */
|
||||||
private boolean mIsStartTransactionCommitted;
|
private boolean mIsStartTransactionCommitted;
|
||||||
|
|
||||||
/** The list to store the drawn tokens before the rotation animation starts. */
|
private SeamlessRotator mRotator;
|
||||||
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 final int mOriginalRotation;
|
private final int mOriginalRotation;
|
||||||
private final boolean mHasScreenRotationAnimation;
|
private final boolean mHasScreenRotationAnimation;
|
||||||
|
|
||||||
public AsyncRotationController(DisplayContent displayContent) {
|
AsyncRotationController(DisplayContent displayContent) {
|
||||||
super(displayContent);
|
super(displayContent);
|
||||||
mService = displayContent.mWmService;
|
mService = displayContent.mWmService;
|
||||||
mOriginalRotation = displayContent.getWindowConfiguration().getRotation();
|
mOriginalRotation = displayContent.getWindowConfiguration().getRotation();
|
||||||
final int transitionType =
|
final int transitionType =
|
||||||
displayContent.mTransitionController.getCollectingTransitionType();
|
displayContent.mTransitionController.getCollectingTransitionType();
|
||||||
mIsChangeTransition = transitionType == WindowManager.TRANSIT_CHANGE;
|
if (transitionType == WindowManager.TRANSIT_CHANGE) {
|
||||||
// Only CHANGE type (rotation animation) needs to wait for the start transaction.
|
final DisplayRotation dr = displayContent.getDisplayRotation();
|
||||||
mIsStartTransactionCommitted = !mIsChangeTransition;
|
final WindowState w = displayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow();
|
||||||
mTimeoutRunnable = displayContent.inTransition() ? () -> {
|
// A rough condition to check whether it may be seamless style. Though the final
|
||||||
synchronized (mService.mGlobalLock) {
|
// decision in shell may be different, it is fine because the jump cut can be covered
|
||||||
displayContent.finishAsyncRotationIfPossible();
|
// by a screenshot if shell falls back to use normal rotation animation.
|
||||||
mService.mWindowPlacerLocked.performSurfacePlacement();
|
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 =
|
mHasScreenRotationAnimation =
|
||||||
displayContent.getRotationAnimation() != null || mIsChangeTransition;
|
displayContent.getRotationAnimation() != null || mTransitionOp == OP_CHANGE;
|
||||||
if (mHasScreenRotationAnimation) {
|
if (mHasScreenRotationAnimation) {
|
||||||
// Hide the windows immediately because screen should have been covered by screenshot.
|
// Hide the windows immediately because screen should have been covered by screenshot.
|
||||||
mHideImmediately = true;
|
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
|
// 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
|
// 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.
|
// 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--) {
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; 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);
|
final WindowToken token = mTargetWindowTokens.keyAt(i);
|
||||||
for (int j = token.getChildCount() - 1; j >= 0; j--) {
|
for (int j = token.getChildCount() - 1; j >= 0; j--) {
|
||||||
token.getChildAt(j).applyWithNextDraw(t -> {});
|
token.getChildAt(j).applyWithNextDraw(t -> {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/** Assigns the operation for the window tokens which can update rotation asynchronously. */
|
||||||
@Override
|
@Override
|
||||||
public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
|
public void accept(WindowState w) {
|
||||||
if (show) {
|
if (w.mActivityRecord != null || !w.mHasSurface || w.mIsWallpaper || w.mIsImWindow
|
||||||
// The previous animation leash will be dropped when preparing fade-in animation, so
|
|| w.mAttrs.type == TYPE_NOTIFICATION_SHADE) {
|
||||||
// simply remove it without restoring the transformation.
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
super.fadeWindowToken(show, windowToken, animationType);
|
mTargetWindowTokens.put(w.mToken, new Operation(action));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Applies show animation on the previously hidden window tokens. */
|
final int action = mTransitionOp == OP_CHANGE_MAY_SEAMLESS || w.mForceSeamlesslyRotate
|
||||||
void show() {
|
? Operation.ACTION_SEAMLESS : Operation.ACTION_FADE;
|
||||||
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
mTargetWindowTokens.put(w.mToken, new Operation(action));
|
||||||
final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
|
}
|
||||||
|
|
||||||
|
/** 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);
|
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();
|
mTargetWindowTokens.clear();
|
||||||
mPendingShowTokens = null;
|
|
||||||
if (mTimeoutRunnable != null) {
|
if (mTimeoutRunnable != null) {
|
||||||
mService.mH.removeCallbacks(mTimeoutRunnable);
|
mService.mH.removeCallbacks(mTimeoutRunnable);
|
||||||
}
|
}
|
||||||
@@ -168,47 +235,80 @@ public class AsyncRotationController extends FadeAnimationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if all target windows are shown. It only takes effects if this
|
* Notifies that the window is ready in new rotation. Returns {@code true} if all target
|
||||||
* controller is created for normal rotation.
|
* windows have completed their rotation operations.
|
||||||
*/
|
*/
|
||||||
boolean show(WindowToken token) {
|
boolean completeRotation(WindowToken token) {
|
||||||
if (!isTargetToken(token)) return false;
|
|
||||||
if (!mIsStartTransactionCommitted) {
|
if (!mIsStartTransactionCommitted) {
|
||||||
// The fade-in animation should only start after the screenshot layer is shown by shell.
|
final Operation op = mTargetWindowTokens.get(token);
|
||||||
// Otherwise the window will be blinking before the rotation animation starts. So store
|
// The animation or draw transaction should only start after the start transaction is
|
||||||
// to a pending list and animate them until the transaction is committed.
|
// applied by shell (e.g. show screenshot layer). Otherwise the window will be blinking
|
||||||
if (mPendingShowTokens == null) {
|
// before the rotation animation starts. So store to a pending list and animate them
|
||||||
mPendingShowTokens = new ArrayList<>();
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
if (!mHasScreenRotationAnimation && token.mTransitionController.inTransition()) {
|
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().
|
// Defer showing to onTransitionFinished().
|
||||||
|
if (DEBUG) Slog.d(TAG, "Defer completion " + token.getTopChild());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// If the timeout runnable is null (fixed rotation), the case will be handled by show().
|
}
|
||||||
if (mTimeoutRunnable != null) {
|
if (!isTargetToken(token)) return false;
|
||||||
fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
|
if (mHasScreenRotationAnimation || mTransitionOp != OP_LEGACY) {
|
||||||
|
if (DEBUG) Slog.d(TAG, "Complete directly " + token.getTopChild());
|
||||||
|
finishOp(token);
|
||||||
if (mTargetWindowTokens.isEmpty()) {
|
if (mTargetWindowTokens.isEmpty()) {
|
||||||
mService.mH.removeCallbacks(mTimeoutRunnable);
|
if (mTimeoutRunnable != null) mService.mH.removeCallbacks(mTimeoutRunnable);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The case (legacy fixed rotation) will be handled by completeAll() when all seamless
|
||||||
|
// windows are done.
|
||||||
return false;
|
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--) {
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
||||||
final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
|
final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
|
||||||
|
final Operation op = mTargetWindowTokens.valueAt(i);
|
||||||
|
if (op.mAction == Operation.ACTION_FADE) {
|
||||||
fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
|
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 (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();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (mTimeoutRunnable != null) {
|
|
||||||
mService.mH.postDelayed(mTimeoutRunnable,
|
mService.mH.postDelayed(mTimeoutRunnable,
|
||||||
WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
|
WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Hides the window immediately until it is drawn in new rotation. */
|
/** Hides the window immediately until it is drawn in new rotation. */
|
||||||
void hideImmediately(WindowToken windowToken) {
|
void hideImmediately(WindowToken windowToken) {
|
||||||
@@ -218,9 +318,11 @@ public class AsyncRotationController extends FadeAnimationController {
|
|||||||
mHideImmediately = original;
|
mHideImmediately = original;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns {@code true} if the window is handled by this controller. */
|
/** Returns {@code true} if the window will rotate independently. */
|
||||||
boolean isHandledToken(WindowToken token) {
|
boolean isAsync(WindowState w) {
|
||||||
return token == mNavBarToken || isTargetToken(token);
|
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. */
|
/** 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.
|
* animation in rotated display.
|
||||||
*/
|
*/
|
||||||
boolean shouldFreezeInsetsPosition(WindowState w) {
|
boolean shouldFreezeInsetsPosition(WindowState w) {
|
||||||
return !mHasScreenRotationAnimation && w.mTransitionController.inTransition()
|
return mTransitionOp == OP_APP_SWITCH && w.mTransitionController.inTransition()
|
||||||
&& isTargetToken(w.mToken);
|
&& isTargetToken(w.mToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,54 +348,70 @@ public class AsyncRotationController extends FadeAnimationController {
|
|||||||
* transition starts. And associate transaction callback to consume pending animations.
|
* transition starts. And associate transaction callback to consume pending animations.
|
||||||
*/
|
*/
|
||||||
void setupStartTransaction(SurfaceControl.Transaction t) {
|
void setupStartTransaction(SurfaceControl.Transaction t) {
|
||||||
if (!mIsChangeTransition) {
|
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
|
// 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
|
// fade out in previous rotation while display has rotated to the new rotation, so
|
||||||
// their leashes are unrotated with the start transaction.
|
// their leashes are transformed with the start transaction.
|
||||||
final SeamlessRotator rotator = new SeamlessRotator(mOriginalRotation,
|
if (mRotator == null) {
|
||||||
|
mRotator = new SeamlessRotator(mOriginalRotation,
|
||||||
mDisplayContent.getWindowConfiguration().getRotation(),
|
mDisplayContent.getWindowConfiguration().getRotation(),
|
||||||
mDisplayContent.getDisplayInfo(),
|
mDisplayContent.getDisplayInfo(),
|
||||||
false /* applyFixedTransformationHint */);
|
false /* applyFixedTransformationHint */);
|
||||||
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
}
|
||||||
final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
|
mRotator.applyTransform(t, leash);
|
||||||
if (leash != null && leash.isValid()) {
|
if (DEBUG) {
|
||||||
rotator.applyTransform(t, leash);
|
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
|
// 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
|
// 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.
|
// committed, the fade-in animation can run with screen rotation animation.
|
||||||
t.addTransactionCommittedListener(new HandlerExecutor(mService.mH), () -> {
|
t.addTransactionCommittedListener(new HandlerExecutor(mService.mH), () -> {
|
||||||
synchronized (mService.mGlobalLock) {
|
synchronized (mService.mGlobalLock) {
|
||||||
|
if (DEBUG) Slog.d(TAG, "Start transaction is committed");
|
||||||
mIsStartTransactionCommitted = true;
|
mIsStartTransactionCommitted = true;
|
||||||
if (mPendingShowTokens == null) return;
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
||||||
for (int i = mPendingShowTokens.size() - 1; i >= 0; i--) {
|
if (mTargetWindowTokens.valueAt(i).mIsCompletionPending) {
|
||||||
mDisplayContent.finishAsyncRotation(mPendingShowTokens.get(i));
|
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() {
|
void onTransitionFinished() {
|
||||||
if (mIsChangeTransition) {
|
if (mTransitionOp == OP_CHANGE) {
|
||||||
// With screen rotation animation, the windows are always faded in when they are drawn.
|
// 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.
|
// Because if they are drawn fast enough, the fade animation should not be observable.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (DEBUG) Slog.d(TAG, "onTransitionFinished " + mTargetWindowTokens);
|
||||||
// For other transition types, the fade-in animation runs after the transition to make the
|
// For other transition types, the fade-in animation runs after the transition to make the
|
||||||
// transition animation (e.g. launch activity) look cleaner.
|
// transition animation (e.g. launch activity) look cleaner.
|
||||||
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
||||||
final WindowToken token = mTargetWindowTokens.keyAt(i);
|
final WindowToken token = mTargetWindowTokens.keyAt(i);
|
||||||
|
if (!token.isVisible()) {
|
||||||
|
mDisplayContent.finishAsyncRotation(token);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (int j = token.getChildCount() - 1; j >= 0; j--) {
|
for (int j = token.getChildCount() - 1; j >= 0; j--) {
|
||||||
// Only fade in the drawn windows. If the remaining windows are drawn later,
|
// Only fade in the drawn windows. If the remaining windows are drawn later,
|
||||||
// show(WindowToken) will be called to fade in them.
|
// show(WindowToken) will be called to fade in them.
|
||||||
@@ -303,26 +421,36 @@ 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) {
|
boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) {
|
||||||
if (mIsChangeTransition || !isTargetToken(w.mToken)) return false;
|
if (mTransitionOp == OP_LEGACY || postDrawTransaction == null
|
||||||
if (postDrawTransaction != null && w.mTransitionController.inTransition()) {
|
|| !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;
|
|
||||||
}
|
|
||||||
mDisplayContent.finishAsyncRotation(w.mToken);
|
|
||||||
return false;
|
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
|
@Override
|
||||||
public Animation getFadeInAnimation() {
|
public Animation getFadeInAnimation() {
|
||||||
@@ -338,26 +466,36 @@ public class AsyncRotationController extends FadeAnimationController {
|
|||||||
if (mHideImmediately) {
|
if (mHideImmediately) {
|
||||||
// For change transition, the hide transaction needs to be applied with sync transaction
|
// For change transition, the hide transaction needs to be applied with sync transaction
|
||||||
// (setupStartTransaction). So keep alpha 1 just to get the animation leash.
|
// (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 new AlphaAnimation(alpha /* fromAlpha */, alpha /* toAlpha */);
|
||||||
}
|
}
|
||||||
return super.getFadeOutAnimation();
|
return super.getFadeOutAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/** The operation to control the rotation appearance associated with window token. */
|
||||||
protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
|
private static class Operation {
|
||||||
boolean show, WindowToken windowToken) {
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
return new FadeAnimationAdapter(animationSpec, windowToken.getSurfaceAnimationRunner(),
|
@IntDef(value = { ACTION_SEAMLESS, ACTION_FADE })
|
||||||
show, windowToken) {
|
@interface Action {}
|
||||||
@Override
|
|
||||||
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
|
static final int ACTION_SEAMLESS = 1;
|
||||||
int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
|
static final int ACTION_FADE = 2;
|
||||||
// The fade cycle is done when showing, so only need to store the leash when hiding.
|
final @Action int mAction;
|
||||||
if (!show) {
|
/** The leash of window token. It can be animation leash or the token itself. */
|
||||||
mTargetWindowTokens.put(mToken, animationLeash);
|
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;
|
||||||
}
|
}
|
||||||
super.startAnimation(animationLeash, t, type, finishCallback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1880,7 +1880,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
|||||||
}
|
}
|
||||||
if (mAsyncRotationController == null) {
|
if (mAsyncRotationController == null) {
|
||||||
mAsyncRotationController = new AsyncRotationController(this);
|
mAsyncRotationController = new AsyncRotationController(this);
|
||||||
mAsyncRotationController.hide();
|
mAsyncRotationController.start();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1890,7 +1890,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
|||||||
void finishAsyncRotationIfPossible() {
|
void finishAsyncRotationIfPossible() {
|
||||||
final AsyncRotationController controller = mAsyncRotationController;
|
final AsyncRotationController controller = mAsyncRotationController;
|
||||||
if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
|
if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
|
||||||
controller.show();
|
controller.completeAll();
|
||||||
mAsyncRotationController = null;
|
mAsyncRotationController = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1898,19 +1898,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
|||||||
/** Shows the given window which may be hidden for screen rotation. */
|
/** Shows the given window which may be hidden for screen rotation. */
|
||||||
void finishAsyncRotation(WindowToken windowToken) {
|
void finishAsyncRotation(WindowToken windowToken) {
|
||||||
final AsyncRotationController controller = mAsyncRotationController;
|
final AsyncRotationController controller = mAsyncRotationController;
|
||||||
if (controller != null && controller.show(windowToken)) {
|
if (controller != null && controller.completeRotation(windowToken)) {
|
||||||
mAsyncRotationController = null;
|
mAsyncRotationController = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns {@code true} if the screen rotation animation needs to wait for the window. */
|
/** Returns {@code true} if the screen rotation animation needs to wait for the window. */
|
||||||
boolean shouldSyncRotationChange(WindowState w) {
|
boolean shouldSyncRotationChange(WindowState w) {
|
||||||
if (w.mForceSeamlesslyRotate) {
|
|
||||||
// The window should look no different before and after rotation.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final AsyncRotationController controller = mAsyncRotationController;
|
final AsyncRotationController controller = mAsyncRotationController;
|
||||||
return controller == null || !controller.isHandledToken(w.mToken);
|
return controller == null || !controller.isAsync(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
|
void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
|
||||||
|
|||||||
@@ -675,18 +675,7 @@ public class DisplayRotation {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the upside down rotation we don't rotate seamlessly as the navigation bar moves
|
if (!canRotateSeamlessly(oldRotation, newRotation)) {
|
||||||
// position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
|
|
||||||
// will not enter the reverse portrait orientation, so actually the orientation won't change
|
|
||||||
// at all.
|
|
||||||
if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the navigation bar can't change sides, then it will jump when we change orientations
|
|
||||||
// and we don't rotate seamlessly - unless that is allowed, eg. with gesture navigation
|
|
||||||
// where the navbar is low-profile enough that this isn't very noticeable.
|
|
||||||
if (!mAllowSeamlessRotationDespiteNavBarMoving && !mDisplayPolicy.navigationBarCanMove()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,6 +702,20 @@ public class DisplayRotation {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean canRotateSeamlessly(int oldRotation, int newRotation) {
|
||||||
|
// For the upside down rotation we don't rotate seamlessly as the navigation bar moves
|
||||||
|
// position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
|
||||||
|
// will not enter the reverse portrait orientation, so actually the orientation won't change
|
||||||
|
// at all.
|
||||||
|
if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If the navigation bar can't change sides, then it will jump when we change orientations
|
||||||
|
// and we don't rotate seamlessly - unless that is allowed, eg. with gesture navigation
|
||||||
|
// where the navbar is low-profile enough that this isn't very noticeable.
|
||||||
|
return mAllowSeamlessRotationDespiteNavBarMoving || mDisplayPolicy.navigationBarCanMove();
|
||||||
|
}
|
||||||
|
|
||||||
void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
|
void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
|
||||||
if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
|
if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
|||||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||||
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
|
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
|
||||||
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
|
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
|
||||||
|
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
|
||||||
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
|
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
|
||||||
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
|
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
|
||||||
import static android.view.WindowManager.TRANSIT_CLOSE;
|
import static android.view.WindowManager.TRANSIT_CLOSE;
|
||||||
@@ -504,7 +505,12 @@ public class TransitionTests extends WindowTestsBase {
|
|||||||
final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
|
final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
|
||||||
final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
|
final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
|
||||||
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
|
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
|
||||||
final WindowState[] windows = { statusBar, navBar, ime };
|
final WindowToken decorToken = new WindowToken.Builder(mWm, mock(IBinder.class),
|
||||||
|
TYPE_NAVIGATION_BAR_PANEL).setDisplayContent(mDisplayContent)
|
||||||
|
.setRoundedCornerOverlay(true).build();
|
||||||
|
final WindowState screenDecor =
|
||||||
|
createWindow(null, decorToken.windowType, decorToken, "screenDecor");
|
||||||
|
final WindowState[] windows = { statusBar, navBar, ime, screenDecor };
|
||||||
makeWindowVisible(windows);
|
makeWindowVisible(windows);
|
||||||
mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
|
mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
|
||||||
mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
|
mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
|
||||||
@@ -523,26 +529,27 @@ public class TransitionTests extends WindowTestsBase {
|
|||||||
player.startTransition();
|
player.startTransition();
|
||||||
|
|
||||||
assertFalse(statusBar.mToken.inTransition());
|
assertFalse(statusBar.mToken.inTransition());
|
||||||
|
assertFalse(decorToken.inTransition());
|
||||||
assertTrue(ime.mToken.inTransition());
|
assertTrue(ime.mToken.inTransition());
|
||||||
assertTrue(task.inTransition());
|
assertTrue(task.inTransition());
|
||||||
|
assertTrue(asyncRotationController.isTargetToken(decorToken));
|
||||||
|
|
||||||
|
screenDecor.setOrientationChanging(false);
|
||||||
// Status bar finishes drawing before the start transaction. Its fade-in animation will be
|
// Status bar finishes drawing before the start transaction. Its fade-in animation will be
|
||||||
// executed until the transaction is committed, so it is still in target tokens.
|
// executed until the transaction is committed, so it is still in target tokens.
|
||||||
statusBar.setOrientationChanging(false);
|
statusBar.setOrientationChanging(false);
|
||||||
assertTrue(asyncRotationController.isTargetToken(statusBar.mToken));
|
assertTrue(asyncRotationController.isTargetToken(statusBar.mToken));
|
||||||
|
|
||||||
final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
|
final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
|
||||||
final ArgumentCaptor<SurfaceControl.TransactionCommittedListener> listenerCaptor =
|
final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
|
||||||
ArgumentCaptor.forClass(SurfaceControl.TransactionCommittedListener.class);
|
onRotationTransactionReady(player, startTransaction);
|
||||||
player.onTransactionReady(startTransaction);
|
|
||||||
|
|
||||||
verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
|
|
||||||
// The transaction is committed, so fade-in animation for status bar is consumed.
|
// The transaction is committed, so fade-in animation for status bar is consumed.
|
||||||
listenerCaptor.getValue().onTransactionCommitted();
|
transactionCommittedListener.onTransactionCommitted();
|
||||||
assertFalse(asyncRotationController.isTargetToken(statusBar.mToken));
|
assertFalse(asyncRotationController.isTargetToken(statusBar.mToken));
|
||||||
|
|
||||||
// Status bar finishes drawing after the start transaction, so its fade-in animation can
|
// Navigation bar finishes drawing after the start transaction, so its fade-in animation
|
||||||
// execute directly.
|
// can execute directly.
|
||||||
navBar.setOrientationChanging(false);
|
navBar.setOrientationChanging(false);
|
||||||
assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
|
assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
|
||||||
assertNull(mDisplayContent.getAsyncRotationController());
|
assertNull(mDisplayContent.getAsyncRotationController());
|
||||||
@@ -577,7 +584,8 @@ public class TransitionTests extends WindowTestsBase {
|
|||||||
final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
|
final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
|
||||||
final SurfaceControl leash = statusBar.mToken.getAnimationLeash();
|
final SurfaceControl leash = statusBar.mToken.getAnimationLeash();
|
||||||
doReturn(true).when(leash).isValid();
|
doReturn(true).when(leash).isValid();
|
||||||
player.onTransactionReady(startTransaction);
|
final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
|
||||||
|
onRotationTransactionReady(player, startTransaction);
|
||||||
// The leash should be unrotated.
|
// The leash should be unrotated.
|
||||||
verify(startTransaction).setMatrix(eq(leash), any(), any());
|
verify(startTransaction).setMatrix(eq(leash), any(), any());
|
||||||
|
|
||||||
@@ -588,6 +596,8 @@ public class TransitionTests extends WindowTestsBase {
|
|||||||
mock(SurfaceControl.Transaction.class);
|
mock(SurfaceControl.Transaction.class);
|
||||||
final boolean layoutNeeded = statusBar.finishDrawing(postDrawTransaction);
|
final boolean layoutNeeded = statusBar.finishDrawing(postDrawTransaction);
|
||||||
assertFalse(layoutNeeded);
|
assertFalse(layoutNeeded);
|
||||||
|
|
||||||
|
transactionCommittedListener.onTransactionCommitted();
|
||||||
player.finish();
|
player.finish();
|
||||||
// The controller should capture the draw transaction and merge it when preparing to run
|
// The controller should capture the draw transaction and merge it when preparing to run
|
||||||
// fade-in animation.
|
// fade-in animation.
|
||||||
@@ -750,4 +760,13 @@ public class TransitionTests extends WindowTestsBase {
|
|||||||
changes.put(curr, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
|
changes.put(curr, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static SurfaceControl.TransactionCommittedListener onRotationTransactionReady(
|
||||||
|
TestTransitionPlayer player, SurfaceControl.Transaction startTransaction) {
|
||||||
|
final ArgumentCaptor<SurfaceControl.TransactionCommittedListener> listenerCaptor =
|
||||||
|
ArgumentCaptor.forClass(SurfaceControl.TransactionCommittedListener.class);
|
||||||
|
player.onTransactionReady(startTransaction);
|
||||||
|
verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
|
||||||
|
return listenerCaptor.getValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user