Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 709cf957 authored by Charles Chen's avatar Charles Chen Committed by Android (Google) Code Review
Browse files

Merge changes I92f1bca1,Ibab51c76 into main

* changes:
  Workaround for the case when task is letterboxed
  Add swipe-PIP-to-home animation support for ...
parents d4eefb49 a37eec77
Loading
Loading
Loading
Loading
+28 −12
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -176,12 +177,12 @@ public class PipAnimationController {
    public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
            Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
            @PipAnimationController.TransitionDirection int direction, float startingAngle,
            @Surface.Rotation int rotationDelta) {
            @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) {
        if (mCurrentAnimator == null) {
            mCurrentAnimator = setupPipTransitionAnimator(
                    PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
                            endBounds, sourceHintRect, direction, 0 /* startingAngle */,
                            rotationDelta));
                            rotationDelta, alwaysAnimateTaskBounds));
        } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
                && mCurrentAnimator.isRunning()) {
            // If we are still animating the fade into pip, then just move the surface and ensure
@@ -197,7 +198,8 @@ public class PipAnimationController {
            mCurrentAnimator.cancel();
            mCurrentAnimator = setupPipTransitionAnimator(
                    PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
                            endBounds, sourceHintRect, direction, startingAngle, rotationDelta));
                            endBounds, sourceHintRect, direction, startingAngle, rotationDelta,
                            alwaysAnimateTaskBounds));
        }
        return mCurrentAnimator;
    }
@@ -585,28 +587,32 @@ public class PipAnimationController {
        static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
                Rect baseValue, Rect startValue, Rect endValue, Rect sourceRectHint,
                @PipAnimationController.TransitionDirection int direction, float startingAngle,
                @Surface.Rotation int rotationDelta) {
                @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) {
            final boolean isOutPipDirection = isOutPipDirection(direction);
            final boolean isInPipDirection = isInPipDirection(direction);
            // Just for simplicity we'll interpolate between the source rect hint insets and empty
            // insets to calculate the window crop
            final Rect initialSourceValue;
            final Rect mainWindowFrame = taskInfo.topActivityMainWindowFrame;
            final boolean hasNonMatchFrame = mainWindowFrame != null;
            final AppCompatTaskInfo compatInfo = taskInfo.appCompatTaskInfo;
            final boolean isSizeCompatOrLetterboxed = compatInfo.isTopActivityInSizeCompat()
                    || compatInfo.isTopActivityLetterboxed();
            // For the animation to swipe PIP to home or restore a PIP task from home, we don't
            // override to the main window frame since we should animate the whole task.
            final boolean shouldUseMainWindowFrame = mainWindowFrame != null
                    && !alwaysAnimateTaskBounds && !isSizeCompatOrLetterboxed;
            final boolean changeOrientation =
                    rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270;
            final Rect baseBounds = new Rect(baseValue);
            final Rect startBounds = new Rect(startValue);
            final Rect endBounds = new Rect(endValue);
            if (isOutPipDirection) {
                // TODO(b/356277166): handle rotation change with activity that provides main window
                //  frame.
                if (hasNonMatchFrame && !changeOrientation) {
                if (shouldUseMainWindowFrame && !changeOrientation) {
                    endBounds.set(mainWindowFrame);
                }
                initialSourceValue = new Rect(endBounds);
            } else if (isInPipDirection) {
                if (hasNonMatchFrame) {
                if (shouldUseMainWindowFrame) {
                    baseBounds.set(mainWindowFrame);
                    if (startValue.equals(baseValue)) {
                        // If the start value is at initial state as in PIP animation, also override
@@ -635,9 +641,19 @@ public class PipAnimationController {
            if (changeOrientation) {
                lastEndRect = new Rect(endBounds);
                rotatedEndRect = new Rect(endBounds);
                // Rotate the end bounds according to the rotation delta because the display will
                // be rotated to the same orientation.
                // TODO(b/375977163): polish the animation to restoring the PIP task back from
                //  swipe-pip-to-home. Ideally we should send the transitionInfo after reparenting
                //  the PIP activity back to the original task.
                if (shouldUseMainWindowFrame) {
                    // If we should animate the main window frame, set it to the rotatedRect
                    // instead. The end bounds reported by transitionInfo is the bounds before
                    // rotation, while main window frame is calculated after the rotation.
                    rotatedEndRect.set(mainWindowFrame);
                } else {
                    // Rotate the end bounds according to the rotation delta because the display
                    // will be rotated to the same orientation.
                    rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
                }
                // Use the rect that has the same orientation as the hint rect.
                initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue;
            } else {
+3 −1
Original line number Diff line number Diff line
@@ -1880,9 +1880,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
                ? mPipBoundsState.getBounds() : currentBounds;
        final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
                && mPipAnimationController.getCurrentAnimator().isRunning();
        // For resize animation, we always animate the whole PIP task bounds.
        final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
                        sourceHintRect, direction, startingAngle, rotationDelta);
                        sourceHintRect, direction, startingAngle, rotationDelta,
                        true /* alwaysAnimateTaskBounds */);
        animator.setTransitionDirection(direction)
                .setPipTransactionHandler(mPipTransactionHandler)
                .setDuration(durationMs);
+9 −6
Original line number Diff line number Diff line
@@ -891,7 +891,8 @@ public class PipTransition extends PipTransitionController {
        final PipAnimationController.PipTransitionAnimator animator =
                mPipAnimationController.getAnimator(taskInfo, pipChange.getLeash(),
                        startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP,
                        0 /* startingAngle */, pipRotateDelta);
                        0 /* startingAngle */, pipRotateDelta,
                        false /* alwaysAnimateTaskBounds */);
        animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
                .setPipAnimationCallback(mPipAnimationCallback)
                .setDuration(mEnterExitAnimationDuration)
@@ -906,7 +907,7 @@ public class PipTransition extends PipTransitionController {
        final PipAnimationController.PipTransitionAnimator animator =
                mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds,
                        endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP,
                        0 /* startingAngle */, rotationDelta);
                        0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */);
        animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
                .setDuration(mEnterExitAnimationDuration);
        if (startTransaction != null) {
@@ -1102,8 +1103,6 @@ public class PipTransition extends PipTransitionController {
        if (taskInfo.pictureInPictureParams != null
                && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                && mPipTransitionState.getInSwipePipToHomeTransition()) {
            // TODO(b/356277166): add support to swipe PIP to home with
            //  non-match parent activity.
            handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
                    sourceHintRect, destinationBounds, taskInfo);
            return;
@@ -1125,7 +1124,7 @@ public class PipTransition extends PipTransitionController {
        if (enterAnimationType == ANIM_TYPE_BOUNDS) {
            animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
                    currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
                    0 /* startingAngle */, rotationDelta);
                    0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */);
            if (sourceHintRect == null) {
                // We use content overlay when there is no source rect hint to enter PiP use bounds
                // animation. We also temporarily disallow app icon overlay and use color overlay
@@ -1248,10 +1247,14 @@ public class PipTransition extends PipTransitionController {
        // to avoid flicker.
        final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets);
        pipTaskInfo.displayCutoutInsets.setEmpty();
        // Always use the task bounds even if the PIP activity doesn't match parent because the app
        // and the whole task will move behind. We should animate the whole task bounds in this
        // case.
        final PipAnimationController.PipTransitionAnimator animator =
                mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
                        destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
                        0 /* startingAngle */, ROTATION_0 /* rotationDelta */)
                        0 /* startingAngle */, ROTATION_0 /* rotationDelta */,
                        true /* alwaysAnimateTaskBounds */)
                        .setPipTransactionHandler(mTransactionConsumer)
                        .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
        // The start state is the end state for swipe-auto-pip.
+171 −7
Original line number Diff line number Diff line
@@ -28,8 +28,11 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
@@ -75,6 +78,7 @@ public class PipAnimationControllerTest extends ShellTestCase {
                .setContainerLayer()
                .setName("FakeLeash")
                .build();
        mTaskInfo.appCompatTaskInfo = mock(AppCompatTaskInfo.class);
    }

    @Test
@@ -93,7 +97,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
        final Rect endValue1 = new Rect(100, 100, 200, 200);
        final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
                        false /* alwaysAnimateTaskBounds */);

        assertEquals("Expect ANIM_TYPE_BOUNDS animation",
                animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -107,14 +112,16 @@ public class PipAnimationControllerTest extends ShellTestCase {
        final Rect endValue2 = new Rect(200, 200, 300, 300);
        final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
                        false /* alwaysAnimateTaskBounds */);
        oldAnimator.setSurfaceControlTransactionFactory(
                MockSurfaceControlHelper::createMockSurfaceControlTransaction);
        oldAnimator.start();

        final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null,
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
                        false /* alwaysAnimateTaskBounds */);

        assertEquals("getAnimator with same type returns same animator",
                oldAnimator, newAnimator);
@@ -145,7 +152,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
        // Fullscreen to PiP.
        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
                        TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90);
                        TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
                        false /* alwaysAnimateTaskBounds */);
        // Apply fraction 1 to compute the end value.
        animator.applySurfaceControlTransaction(mLeash, tx, 1);
        final Rect rotatedEndBounds = new Rect(endBounds);
@@ -157,7 +165,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
        startBounds.set(0, 0, 1000, 500);
        endBounds.set(200, 100, 400, 500);
        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
                endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270);
                endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
                false /* alwaysAnimateTaskBounds */);
        animator.applySurfaceControlTransaction(mLeash, tx, 1);
        rotatedEndBounds.set(endBounds);
        rotateBounds(rotatedEndBounds, startBounds, ROTATION_270);
@@ -165,6 +174,37 @@ public class PipAnimationControllerTest extends ShellTestCase {
        assertEquals("Expect 270 degree rotated bounds", rotatedEndBounds, animator.mCurrentValue);
    }

    @Test
    public void pipTransitionAnimator_rotatedEndValue_overrideMainWindowFrame() {
        final SurfaceControl.Transaction tx = createMockSurfaceControlTransaction();
        final Rect startBounds = new Rect(200, 700, 400, 800);
        final Rect endBounds = new Rect(0, 0, 500, 1000);
        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);

        // Fullscreen task to PiP.
        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
                        TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
                        false /* alwaysAnimateTaskBounds */);
        // Apply fraction 1 to compute the end value.
        animator.applySurfaceControlTransaction(mLeash, tx, 1);

        assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame,
                animator.mCurrentValue);

        // PiP to fullscreen.
        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);
        startBounds.set(0, 0, 1000, 500);
        endBounds.set(200, 100, 400, 500);
        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
                endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
                false /* alwaysAnimateTaskBounds */);
        animator.applySurfaceControlTransaction(mLeash, tx, 1);

        assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame,
                animator.mCurrentValue);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void pipTransitionAnimator_updateEndValue() {
@@ -174,7 +214,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
        final Rect endValue2 = new Rect(200, 200, 300, 300);
        final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
                        false /* alwaysAnimateTaskBounds */);

        animator.updateEndValue(endValue2);

@@ -188,7 +229,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
        final Rect endValue = new Rect(100, 100, 200, 200);
        final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
                        false /* alwaysAnimateTaskBounds */);
        animator.setSurfaceControlTransactionFactory(
                MockSurfaceControlHelper::createMockSurfaceControlTransaction);

@@ -207,4 +249,126 @@ public class PipAnimationControllerTest extends ShellTestCase {
        verify(mPipAnimationCallback).onPipAnimationEnd(eq(mTaskInfo),
                any(SurfaceControl.Transaction.class), eq(animator));
    }

    @Test
    public void pipTransitionAnimator_overrideMainWindowFrame() {
        final Rect baseValue = new Rect(0, 0, 100, 100);
        final Rect startValue = new Rect(0, 0, 100, 100);
        final Rect endValue = new Rect(100, 100, 200, 200);
        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
                        false /* alwaysAnimateTaskBounds */);

        assertEquals("Expect base value is overridden for in-PIP transition",
                mTaskInfo.topActivityMainWindowFrame, animator.getBaseValue());
        assertEquals("Expect start value is overridden for in-PIP transition",
                mTaskInfo.topActivityMainWindowFrame, animator.getStartValue());
        assertEquals("Expect end value is not overridden for in-PIP transition",
                endValue, animator.getEndValue());

        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
                endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
                        false /* alwaysAnimateTaskBounds */);

        assertEquals("Expect base value is not overridden for leave-PIP transition",
                baseValue, animator.getBaseValue());
        assertEquals("Expect start value is not overridden for leave-PIP transition",
                startValue, animator.getStartValue());
        assertEquals("Expect end value is overridden for leave-PIP transition",
                mTaskInfo.topActivityMainWindowFrame, animator.getEndValue());
    }

    @Test
    public void pipTransitionAnimator_animateTaskBounds() {
        final Rect baseValue = new Rect(0, 0, 100, 100);
        final Rect startValue = new Rect(0, 0, 100, 100);
        final Rect endValue = new Rect(100, 100, 200, 200);
        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
                        true /* alwaysAnimateTaskBounds */);

        assertEquals("Expect base value is not overridden for in-PIP transition",
                baseValue, animator.getBaseValue());
        assertEquals("Expect start value is not overridden for in-PIP transition",
                startValue, animator.getStartValue());
        assertEquals("Expect end value is not overridden for in-PIP transition",
                endValue, animator.getEndValue());

        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
                endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
                true /* alwaysAnimateTaskBounds */);

        assertEquals("Expect base value is not overridden for leave-PIP transition",
                baseValue, animator.getBaseValue());
        assertEquals("Expect start value is not overridden for leave-PIP transition",
                startValue, animator.getStartValue());
        assertEquals("Expect end value is not overridden for leave-PIP transition",
                endValue, animator.getEndValue());
    }

    @Test
    public void pipTransitionAnimator_letterboxed_animateTaskBounds() {
        final Rect baseValue = new Rect(0, 0, 100, 100);
        final Rect startValue = new Rect(0, 0, 100, 100);
        final Rect endValue = new Rect(100, 100, 200, 200);
        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
        doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityLetterboxed();
        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
                        false /* alwaysAnimateTaskBounds */);

        assertEquals("Expect base value is not overridden for in-PIP transition",
                baseValue, animator.getBaseValue());
        assertEquals("Expect start value is not overridden for in-PIP transition",
                startValue, animator.getStartValue());
        assertEquals("Expect end value is not overridden for in-PIP transition",
                endValue, animator.getEndValue());

        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
                endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
                false /* alwaysAnimateTaskBounds */);

        assertEquals("Expect base value is not overridden for leave-PIP transition",
                baseValue, animator.getBaseValue());
        assertEquals("Expect start value is not overridden for leave-PIP transition",
                startValue, animator.getStartValue());
        assertEquals("Expect end value is not overridden for leave-PIP transition",
                endValue, animator.getEndValue());
    }

    @Test
    public void pipTransitionAnimator_sizeCompat_animateTaskBounds() {
        final Rect baseValue = new Rect(0, 0, 100, 100);
        final Rect startValue = new Rect(0, 0, 100, 100);
        final Rect endValue = new Rect(100, 100, 200, 200);
        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
        doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityInSizeCompat();
        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
                        false /* alwaysAnimateTaskBounds */);

        assertEquals("Expect base value is not overridden for in-PIP transition",
                baseValue, animator.getBaseValue());
        assertEquals("Expect start value is not overridden for in-PIP transition",
                startValue, animator.getStartValue());
        assertEquals("Expect end value is not overridden for in-PIP transition",
                endValue, animator.getEndValue());

        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
                endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
                false /* alwaysAnimateTaskBounds */);

        assertEquals("Expect base value is not overridden for leave-PIP transition",
                baseValue, animator.getBaseValue());
        assertEquals("Expect start value is not overridden for leave-PIP transition",
                startValue, animator.getStartValue());
        assertEquals("Expect end value is not overridden for leave-PIP transition",
                endValue, animator.getEndValue());
    }
}