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

Commit 6e7c4101 authored by Charles Chen's avatar Charles Chen
Browse files

Support PIP animation on non-match parent activity

... with orientation change

Bug: 356277166
Test: atest BottomHalfEnterPipToOtherOrientation
Test: atest BottomHalfSetRequestedOrientationWhilePinned
Flag: com.android.window.flags.better_support_non_match_parent_activity

Change-Id: I0f084ebf1dfce4a868a48e72dfad7699675ad5c8
parent f296d244
Loading
Loading
Loading
Loading
+12 −2
Original line number Diff line number Diff line
@@ -664,10 +664,12 @@ public class PipAnimationController {
                // 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 (shouldUseMainWindowFrame && isOutPipDirection) {
                    // 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.
                    // Note that we only override main window frame for leaving pip animation as
                    // the pip activity should match parent.
                    rotatedEndRect.set(mainWindowFrame);
                } else {
                    // Rotate the end bounds according to the rotation delta because the display
@@ -810,11 +812,19 @@ public class PipAnimationController {
                        }
                    }
                    final Rect sourceBounds = new Rect(initialContainerRect);
                    Rect relativeEndWindowFrame = null;
                    if (isOutPipDirection) {
                        relativeEndWindowFrame = rotatedEndRect;
                    }
                    if (relativeEndWindowFrame != null) {
                        relativeEndWindowFrame.offset(leashOffset.x, leashOffset.y);
                    }
                    sourceBounds.inset(insets);
                    getSurfaceTransactionHelper()
                            .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
                                    insets, degree, x, y, isOutPipDirection,
                                    rotationDelta == ROTATION_270 /* clockwise */)
                                    rotationDelta == ROTATION_270 /* clockwise */,
                                    relativeEndWindowFrame)
                            .round(tx, leash, sourceBounds, bounds)
                            .shadow(tx, leash, shouldApplyShadowRadius());
                    if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) {
+33 −18
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import android.graphics.RectF;
import android.view.Choreographer;
import android.view.SurfaceControl;

import androidx.annotation.Nullable;

import com.android.wm.shell.R;
import com.android.wm.shell.transition.Transitions;

@@ -224,12 +226,17 @@ public class PipSurfaceTransactionHelper {
    /**
     * Operates the rotation according to the given degrees and scale (setMatrix) according to the
     * source bounds and rotated destination bounds. The crop will be the unscaled source bounds.
     *
     * @param relativeEndWindowFrame specified if
     *   {@link android.app.TaskInfo#topActivityMainWindowFrame} is provided. It's only applied for
     *   the animation that {@code isExpanding} PIP to original task.
     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
     */
    public PipSurfaceTransactionHelper rotateAndScaleWithCrop(SurfaceControl.Transaction tx,
            SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets,
    public PipSurfaceTransactionHelper rotateAndScaleWithCrop(
            @NonNull SurfaceControl.Transaction tx, @NonNull SurfaceControl leash,
            @NonNull Rect sourceBounds, @NonNull Rect destinationBounds, @NonNull Rect insets,
            float degrees, float positionX, float positionY, boolean isExpanding,
            boolean clockwise) {
            boolean clockwise, @Nullable Rect relativeEndWindowFrame) {
        mTmpDestinationRect.set(sourceBounds);
        mTmpDestinationRect.inset(insets);
        final int srcW = mTmpDestinationRect.width();
@@ -240,6 +247,13 @@ public class PipSurfaceTransactionHelper {
        // destination are different.
        final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
        final Rect crop = mTmpDestinationRect;
        if (isExpanding && relativeEndWindowFrame != null) {
            // If relative end window frame is provided, it usually means the top activity chooses
            // a customized layout which may not match parent. In this case, we should crop the
            // task surface with the window frame. Note that we don't need to consider the insets
            // because the main window frame excludes the insets.
            crop.set(relativeEndWindowFrame);
        } else {
            crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH
                    : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH);
            // Inverse scale for crop to fit in screen coordinates.
@@ -259,6 +273,7 @@ public class PipSurfaceTransactionHelper {
                    positionY -= insets.left * scale;
                }
            }
        }
        mTmpTransform.setScale(scale, scale);
        mTmpTransform.postRotate(degrees);
        mTmpTransform.postTranslate(positionX, positionY);
+6 −3
Original line number Diff line number Diff line
@@ -844,7 +844,8 @@ public class PipTransition extends PipTransitionController {
            }
            mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction,
                    pipLeash, endBounds, endBounds, new Rect(), degree, x, y,
                    true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */);
                    true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */,
                    null /* relativeEndWindowFrame */);
        } else {
            rotationDelta = Surface.ROTATION_0;
        }
@@ -868,7 +869,9 @@ public class PipTransition extends PipTransitionController {
        // Get the start bounds in new orientation.
        final Rect startBounds = new Rect(pipChange.getStartAbsBounds());
        rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta);
        final Rect endBounds = new Rect(pipChange.getEndAbsBounds());
        final Rect windowFrame = taskInfo.topActivityMainWindowFrame;
        final Rect endBounds = new Rect(windowFrame != null
                ? windowFrame : pipChange.getEndAbsBounds());
        startBounds.offset(-offset.x, -offset.y);
        endBounds.offset(-offset.x, -offset.y);

@@ -888,7 +891,7 @@ public class PipTransition extends PipTransitionController {
        }
        mSurfaceTransactionHelper.rotateAndScaleWithCrop(startTransaction, pipChange.getLeash(),
                endBounds, startBounds, new Rect(), degree, x, y, true /* isExpanding */,
                pipRotateDelta == ROTATION_270 /* clockwise */);
                pipRotateDelta == ROTATION_270 /* clockwise */, null /* relativeEndWindowFrame */);
        startTransaction.apply();
        rotator.cleanUp(finishTransaction);

+17 −1
Original line number Diff line number Diff line
@@ -75,8 +75,8 @@ android_test {
    ],
    static_libs: ["WMShellFlickerTestsBase"],
    test_suites: [
        "device-tests",
        "csuite",
        "device-tests",
    ],
    data: ["trace_config/*"],
}
@@ -117,9 +117,11 @@ test_module_config {
        "com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay",
        "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfAutoEnterPipOnGoToHomeTest",
        "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipOnUserLeaveHintTest",
        "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipToOtherOrientation",
        "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipViaAppUiButtonTest",
        "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaExpandButtonTest",
        "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaIntentTest",
        "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfSetRequestedOrientationWhilePinned",
    ],
    test_suites: ["device-tests"],
}
@@ -308,5 +310,19 @@ test_module_config {
    test_suites: ["device-tests"],
}

test_module_config {
    name: "WMShellFlickerTestsPip-BottomHalfEnterPipToOtherOrientation",
    base: "WMShellFlickerTestsPip",
    include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipToOtherOrientation"],
    test_suites: ["device-tests"],
}

test_module_config {
    name: "WMShellFlickerTestsPip-BottomHalfSetRequestedOrientationWhilePinned",
    base: "WMShellFlickerTestsPip",
    include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfSetRequestedOrientationWhilePinned"],
    test_suites: ["device-tests"],
}

// End breakdowns for WMShellFlickerTestsPip module
////////////////////////////////////////////////////////////////////////////////
+24 −22
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.wm.shell.flicker.pip

import android.app.Activity
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresFlagsDisabled
@@ -38,9 +37,10 @@ import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
import org.junit.Assume
import org.junit.Before
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -72,10 +72,10 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
    private val testApp = FixedOrientationAppHelper(instrumentation)
    private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
    internal val testApp = FixedOrientationAppHelper(instrumentation)
    internal val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
    private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)

    override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -111,33 +111,28 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
        }
    }

    /**
     * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
     * fix a orientation, Tablets instead keep the same orientation and add letterboxes
     */
    @Before
    fun setup() {
        Assume.assumeFalse(tapl.isTablet)
    }

    /**
     * Checks that all parts of the screen are covered at the start and end of the transition
     */
    @Presubmit
    @Test
    fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered()
    fun entireScreenCoveredAtStartAndEnd() {
        assumeFalse(tapl.isTablet)
        flicker.entireScreenCovered()
    }

    /** Checks [pipApp] window remains visible and on top throughout the transition */
    @Presubmit
    @Test
    fun pipAppWindowIsAlwaysOnTop() {
        assumeFalse(tapl.isTablet)
        flicker.assertWm { isAppWindowOnTop(pipApp) }
    }

    /** Checks that [testApp] window is not visible at the start */
    @Presubmit
    @Test
    fun testAppWindowInvisibleOnStart() {
    open fun testAppWindowInvisibleOnStart() {
        flicker.assertWmStart { isAppWindowInvisible(testApp) }
    }

@@ -145,13 +140,15 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
    @Presubmit
    @Test
    fun testAppWindowVisibleOnEnd() {
        assumeFalse(tapl.isTablet)
        flicker.assertWmEnd { isAppWindowVisible(testApp) }
    }

    /** Checks that [testApp] layer is not visible at the start */
    @Presubmit
    @Test
    fun testAppLayerInvisibleOnStart() {
    open fun testAppLayerInvisibleOnStart() {
        assumeFalse(tapl.isTablet)
        flicker.assertLayersStart { isInvisible(testApp) }
    }

@@ -159,6 +156,7 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
    @Presubmit
    @Test
    fun testAppLayerVisibleOnEnd() {
        assumeFalse(tapl.isTablet)
        flicker.assertLayersEnd { isVisible(testApp) }
    }

@@ -168,8 +166,8 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
     */
    @Presubmit
    @Test
    fun pipAppLayerCoversFullScreenOnStart() {
        Assume.assumeFalse(tapl.isTablet)
    open fun pipAppLayerCoversFullScreenOnStart() {
        assumeFalse(tapl.isTablet)
        flicker.assertLayersStart { visibleRegion(pipApp).coversExactly(startingBounds) }
    }

@@ -177,10 +175,11 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
     * Checks that the visible region of [pipApp] covers the full display area at the start of the
     * transition
     */
    @Ignore("TODO(b/356277166): enable the tablet test")
    @Postsubmit
    @Test
    fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() {
        Assume.assumeFalse(tapl.isTablet)
    open fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() {
        assumeTrue(tapl.isTablet)
        flicker.assertLayersStart {
            visibleRegion(pipApp.or(ComponentNameMatcher.LETTERBOX)).coversExactly(startingBounds)
        }
@@ -193,6 +192,7 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
    @Presubmit
    @Test
    fun testAppPlusPipLayerCoversFullScreenOnEnd() {
        assumeFalse(tapl.isTablet)
        flicker.assertLayersEnd {
            val pipRegion = visibleRegion(pipApp).region
            visibleRegion(testApp).plus(pipRegion).coversExactly(endingBounds)
@@ -202,6 +202,7 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
    @Postsubmit
    @Test
    fun menuOverlayMatchesTaskSurface() {
        assumeFalse(tapl.isTablet)
        flicker.assertLayersEnd {
            val pipAppRegion = visibleRegion(pipApp)
            val pipMenuRegion = visibleRegion(ComponentNameMatcher.PIP_MENU_OVERLAY)
@@ -212,6 +213,7 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
    @Presubmit
    @Test
    fun pipLayerRemainInsideVisibleBounds() {
        assumeFalse(tapl.isTablet)
        // during the transition we assert the center point is within the display bounds, since it
        // might go outside of bounds as we resize from landscape fullscreen to destination bounds,
        // and once the animation is over we assert that it's fully within the display bounds, at
Loading