From c59a137df26c703356f2ff5ac9a1d6e6362b8842 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Tue, 2 May 2023 15:50:50 +0800 Subject: [PATCH] Polish entering pip from fullscreen with shell transition It was flickering by not considering the source hint rect (offset becomes black area), non-scaled surface, and when applying PiP bounds, the app doesn't draw with bounds change synchronously. Also pass source hint Rect for exiting pip animation so the content won't be always scaled. Bug: 277452732 Test: Swipe an app uses auto-pip with valid source hint. Change-Id: If438fa81159b5a5a2bdb7c993c63c6f9dd514b99 --- .../wm/shell/pip/PipAnimationController.java | 6 +- .../wm/shell/pip/PipTaskOrganizer.java | 1 + .../android/wm/shell/pip/PipTransition.java | 140 ++++++++++++------ 3 files changed, 97 insertions(+), 50 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 1d7e649883594..bbfeb90704db1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -52,7 +52,7 @@ import java.util.Objects; */ public class PipAnimationController { static final float FRACTION_START = 0f; - private static final float FRACTION_END = 1f; + static final float FRACTION_END = 1f; public static final int ANIM_TYPE_BOUNDS = 0; public static final int ANIM_TYPE_ALPHA = 1; @@ -718,7 +718,9 @@ public class PipAnimationController { .round(tx, leash, sourceBounds, bounds) .shadow(tx, leash, shouldApplyShadowRadius()); } - tx.apply(); + if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) { + tx.apply(); + } } private Rect computeInsets(float fraction) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 363d6759f8d06..8709eaba02729 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -828,6 +828,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private void onEndOfSwipePipToHomeTransition() { if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mPipTransitionController.setEnterAnimationType(ANIM_TYPE_BOUNDS); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 046d6fce443ba..db516c0e74f93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -45,7 +45,6 @@ import android.animation.Animator; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.Context; -import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; @@ -109,6 +108,17 @@ public class PipTransition extends PipTransitionController { /** Whether the PIP window has fade out for fixed rotation. */ private boolean mHasFadeOut; + /** Used for setting transform to a transaction from animator. */ + private final PipAnimationController.PipTransactionHandler mTransactionConsumer = + new PipAnimationController.PipTransactionHandler() { + @Override + public boolean handlePipTransaction(SurfaceControl leash, + SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) { + // Only set the operation to transaction but do not apply. + return true; + } + }; + public PipTransition(Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @@ -338,7 +348,7 @@ public class PipTransition extends PipTransitionController { @Override public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, - @Nullable SurfaceControl.Transaction tx) { + @NonNull SurfaceControl.Transaction tx) { final boolean enteringPip = isInPipDirection(direction); if (enteringPip) { mPipTransitionState.setTransitionState(ENTERED_PIP); @@ -348,13 +358,15 @@ public class PipTransition extends PipTransitionController { // (likely a remote like launcher), so don't fire the finish-callback here -- wait until // the exit transition is merged. if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) { + final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); + final boolean hasValidLeash = leash != null && leash.isValid(); WindowContainerTransaction wct = null; if (isOutPipDirection(direction)) { // Only need to reset surface properties. The server-side operations were already // done at the start. But if it is running fixed rotation, there will be a seamless // display transition later. So the last rotation transform needs to be kept to // avoid flickering, and then the display transition will reset the transform. - if (tx != null && !mInFixedRotation) { + if (!mInFixedRotation && mFinishTransaction != null) { mFinishTransaction.merge(tx); } } else { @@ -363,27 +375,36 @@ public class PipTransition extends PipTransitionController { // If we are animating from fullscreen using a bounds animation, then reset the // activity windowing mode, and set the task bounds to the final bounds wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); - wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds); wct.setBounds(taskInfo.token, destinationBounds); } else { wct.setBounds(taskInfo.token, null /* bounds */); } - if (tx != null) { - wct.setBoundsChangeTransaction(taskInfo.token, tx); + // Reset the scale with bounds change synchronously. + if (hasValidLeash) { + mSurfaceTransactionHelper.crop(tx, leash, destinationBounds) + .resetScale(tx, leash, destinationBounds) + .round(tx, leash, true /* applyCornerRadius */); } + wct.setBoundsChangeTransaction(taskInfo.token, tx); } - final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); final int displayRotation = taskInfo.getConfiguration().windowConfiguration .getDisplayRotation(); if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation - && leash != null && leash.isValid()) { + && hasValidLeash) { // Launcher may update the Shelf height during the animation, which will update the // destination bounds. Because this is in fixed rotation, We need to make sure the // finishTransaction is using the updated bounds in the display rotation. + final PipAnimationController.PipTransitionAnimator animator = + mPipAnimationController.getCurrentAnimator(); final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds(); final Rect finishBounds = new Rect(destinationBounds); rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation); - mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds); + if (!finishBounds.equals(animator.getEndValue())) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Destination bounds were changed during animation", TAG); + rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation); + mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds); + } } mFinishTransaction = null; callFinishCallback(wct); @@ -665,9 +686,11 @@ public class PipTransition extends PipTransitionController { private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, final Rect baseBounds, final Rect startBounds, final Rect endBounds, final int rotationDelta) { + final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( + taskInfo.pictureInPictureParams, endBounds); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds, - endBounds, null /* sourceHintRect */, TRANSITION_DIRECTION_LEAVE_PIP, + endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, rotationDelta); animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) .setPipAnimationCallback(mPipAnimationCallback) @@ -800,10 +823,6 @@ public class PipTransition extends PipTransitionController { computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, destinationBounds, sourceHintRect); } - // Set corner radius for entering pip. - mSurfaceTransactionHelper - .crop(finishTransaction, leash, destinationBounds) - .round(finishTransaction, leash, true /* applyCornerRadius */); if (!mPipOrganizer.shouldAttachMenuEarly()) { mTransitions.getMainExecutor().executeDelayed( () -> mPipMenuController.attach(leash), 0); @@ -812,41 +831,11 @@ public class PipTransition extends PipTransitionController { if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { - final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; - startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9]) - .setPosition(leash, destinationBounds.left, destinationBounds.top) - .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()); - if (swipePipToHomeOverlay != null) { - // Launcher fade in the overlay on top of the fullscreen Task. It is possible we - // reparent the PIP activity to a new PIP task (in case there are other activities - // in the original Task), so we should also reparent the overlay to the PIP task. - startTransaction.reparent(swipePipToHomeOverlay, leash) - .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE); - mPipOrganizer.mSwipePipToHomeOverlay = null; - } - startTransaction.apply(); - if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { - // For fixed rotation, set the destination bounds to the new rotation coordinates - // at the end. - destinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); - } - mPipBoundsState.setBounds(destinationBounds); - onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */); - sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); - if (swipePipToHomeOverlay != null) { - mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay, - null /* callback */, false /* withStartDelay */); - } - mPipTransitionState.setInSwipePipToHomeTransition(false); + handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash, + sourceHintRect, destinationBounds, rotationDelta, taskInfo); return; } - if (rotationDelta != Surface.ROTATION_0) { - Matrix tmpTransform = new Matrix(); - tmpTransform.postRotate(rotationDelta); - startTransaction.setMatrix(leash, tmpTransform, new float[9]); - } - final int enterAnimationType = mEnterAnimationType; if (enterAnimationType == ANIM_TYPE_ALPHA) { startTransaction.setAlpha(leash, 0f); @@ -880,11 +869,13 @@ public class PipTransition extends PipTransitionController { } else if (enterAnimationType == ANIM_TYPE_ALPHA) { animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); + mSurfaceTransactionHelper + .crop(finishTransaction, leash, destinationBounds) + .round(finishTransaction, leash, true /* applyCornerRadius */); } else { throw new RuntimeException("Unrecognized animation type: " + enterAnimationType); } animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) - .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { @@ -893,7 +884,15 @@ public class PipTransition extends PipTransitionController { // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation. animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); } - animator.start(); + // Keep the last appearance when finishing the transition. The transform will be reset when + // setting bounds. + animator.setPipTransactionHandler(mTransactionConsumer).applySurfaceControlTransaction( + leash, finishTransaction, PipAnimationController.FRACTION_END); + // Remove the workaround after fixing ClosePipBySwipingDownTest that detects the shadow + // as unexpected visible. + finishTransaction.setShadowRadius(leash, 0); + // Start to animate enter PiP. + animator.setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()).start(); } /** Computes destination bounds in old rotation and updates source hint rect if available. */ @@ -915,6 +914,51 @@ public class PipTransition extends PipTransitionController { } } + private void handleSwipePipToHomeTransition( + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect, + @NonNull Rect destinationBounds, int rotationDelta, + @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) { + final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; + if (swipePipToHomeOverlay != null) { + // Launcher fade in the overlay on top of the fullscreen Task. It is possible we + // reparent the PIP activity to a new PIP task (in case there are other activities + // in the original Task), so we should also reparent the overlay to the PIP task. + startTransaction.reparent(swipePipToHomeOverlay, leash) + .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE); + mPipOrganizer.mSwipePipToHomeOverlay = null; + } + + Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds(); + if (!Transitions.SHELL_TRANSITIONS_ROTATION && rotationDelta % 2 == 1) { + // PipController#startSwipePipToHome has updated the display layout to new rotation, + // so flip the source bounds to match the same orientation. + sourceBounds = new Rect(0, 0, sourceBounds.height(), sourceBounds.width()); + } + final PipAnimationController.PipTransitionAnimator animator = + mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds, + destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, + 0 /* startingAngle */, 0 /* rotationDelta */) + .setPipTransactionHandler(mTransactionConsumer) + .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP); + // The start state is the end state for swipe-auto-pip. + startTransaction.merge(finishTransaction); + animator.applySurfaceControlTransaction(leash, startTransaction, + PipAnimationController.FRACTION_END); + startTransaction.apply(); + + mPipBoundsState.setBounds(destinationBounds); + final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + onFinishResize(pipTaskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); + if (swipePipToHomeOverlay != null) { + mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay, + null /* callback */, false /* withStartDelay */); + } + mPipTransitionState.setInSwipePipToHomeTransition(false); + } + private void startExitToSplitAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction,