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

Commit ff56fe99 authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

[PiP2] End overlay animation if user interacts

If source-rect-hint is not valid we run an app icon
overlay fadeout animation after entering PiP.

But with PiP2 using config-at-end transitions we actually
allow users to interact with PiP earlier after entry than in PiP1.
So if PiP is resized or tap-con-to-expand is triggered, overlay's
offset and transform isn't really adjusted to reflect the new follow-up
transitions with new buffer sizes.

So we take the approach of just ending the overlay fadeout animation if
needed when either EXITING_PIP or when we have SCHEDULED_BOUNDS_CHANGE.

Bug: 399512881
Flag: com.android.wm.shell.enable_pip2
Test: enter PiP with wrong src-rect-hint, then tap app icon to reopen
Test: enter PiP with wrong src-rect-hint, then pinch-resize after
Change-Id: Icc0fb90bcac53b7e9e0df293f77dd37c88aadd92
parent 46bc22e8
Loading
Loading
Loading
Loading
+39 −6
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.PictureInPictureParams;
import android.content.Context;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.SystemProperties;
import android.os.SystemProperties;
import android.view.SurfaceControl;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerToken;
@@ -47,7 +48,7 @@ import java.util.function.Supplier;
/**
/**
 * Scheduler for Shell initiated PiP transitions and animations.
 * Scheduler for Shell initiated PiP transitions and animations.
 */
 */
public class PipScheduler {
public class PipScheduler implements PipTransitionState.PipTransitionStateChangedListener {
    private static final String TAG = PipScheduler.class.getSimpleName();
    private static final String TAG = PipScheduler.class.getSimpleName();


    /**
    /**
@@ -71,6 +72,7 @@ public class PipScheduler {
    private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
    private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;


    @Nullable private Runnable mUpdateMovementBoundsRunnable;
    @Nullable private Runnable mUpdateMovementBoundsRunnable;
    @Nullable private PipAlphaAnimator mOverlayFadeoutAnimator;


    private PipAlphaAnimatorSupplier mPipAlphaAnimatorSupplier;
    private PipAlphaAnimatorSupplier mPipAlphaAnimatorSupplier;
    private Supplier<PictureInPictureParams> mPipParamsSupplier;
    private Supplier<PictureInPictureParams> mPipParamsSupplier;
@@ -85,6 +87,7 @@ public class PipScheduler {
        mPipBoundsState = pipBoundsState;
        mPipBoundsState = pipBoundsState;
        mMainExecutor = mainExecutor;
        mMainExecutor = mainExecutor;
        mPipTransitionState = pipTransitionState;
        mPipTransitionState = pipTransitionState;
        mPipTransitionState.addPipTransitionStateChangedListener(this);
        mPipDesktopState = pipDesktopState;
        mPipDesktopState = pipDesktopState;
        mSplitScreenControllerOptional = splitScreenControllerOptional;
        mSplitScreenControllerOptional = splitScreenControllerOptional;


@@ -238,12 +241,16 @@ public class PipScheduler {


    void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash,
    void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash,
            boolean withStartDelay, @NonNull Runnable onAnimationEnd) {
            boolean withStartDelay, @NonNull Runnable onAnimationEnd) {
        PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext, overlayLeash,
        mOverlayFadeoutAnimator = mPipAlphaAnimatorSupplier.get(mContext, overlayLeash,
                null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT);
                null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT);
        animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DURATION_MS);
        mOverlayFadeoutAnimator.setDuration(CONTENT_OVERLAY_FADE_OUT_DURATION_MS);
        animator.setStartDelay(withStartDelay ? EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
        mOverlayFadeoutAnimator.setStartDelay(withStartDelay
        animator.setAnimationEndCallback(onAnimationEnd);
                ? EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
        animator.start();
        mOverlayFadeoutAnimator.setAnimationEndCallback(() -> {
            onAnimationEnd.run();
            mOverlayFadeoutAnimator = null;
        });
        mOverlayFadeoutAnimator.start();
    }
    }


    void setUpdateMovementBoundsRunnable(@Nullable Runnable updateMovementBoundsRunnable) {
    void setUpdateMovementBoundsRunnable(@Nullable Runnable updateMovementBoundsRunnable) {
@@ -289,6 +296,21 @@ public class PipScheduler {
        mSurfaceControlTransactionFactory = factory;
        mSurfaceControlTransactionFactory = factory;
    }
    }


    @Override
    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
            @PipTransitionState.TransitionState int newState,
            @android.annotation.Nullable Bundle extra) {
        switch (newState) {
            case PipTransitionState.EXITING_PIP:
            case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
                if (mOverlayFadeoutAnimator != null && mOverlayFadeoutAnimator.isStarted()) {
                    mOverlayFadeoutAnimator.end();
                    mOverlayFadeoutAnimator = null;
                }
                break;
        }
    }

    @VisibleForTesting
    @VisibleForTesting
    interface PipAlphaAnimatorSupplier {
    interface PipAlphaAnimatorSupplier {
        PipAlphaAnimator get(@NonNull Context context,
        PipAlphaAnimator get(@NonNull Context context,
@@ -303,6 +325,17 @@ public class PipScheduler {
        mPipAlphaAnimatorSupplier = supplier;
        mPipAlphaAnimatorSupplier = supplier;
    }
    }


    @VisibleForTesting
    void setOverlayFadeoutAnimator(@NonNull PipAlphaAnimator animator) {
        mOverlayFadeoutAnimator = animator;
    }

    @VisibleForTesting
    @Nullable
    PipAlphaAnimator getOverlayFadeoutAnimator() {
        return mOverlayFadeoutAnimator;
    }

    void setPipParamsSupplier(@NonNull Supplier<PictureInPictureParams> pipParamsSupplier) {
    void setPipParamsSupplier(@NonNull Supplier<PictureInPictureParams> pipParamsSupplier) {
        mPipParamsSupplier = pipParamsSupplier;
        mPipParamsSupplier = pipParamsSupplier;
    }
    }
+26 −0
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2.phone;
package com.android.wm.shell.pip2.phone;


import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -86,6 +87,7 @@ public class PipSchedulerTest {
    @Mock private SurfaceControl.Transaction mMockTransaction;
    @Mock private SurfaceControl.Transaction mMockTransaction;
    @Mock private PipAlphaAnimator mMockAlphaAnimator;
    @Mock private PipAlphaAnimator mMockAlphaAnimator;
    @Mock private SplitScreenController mMockSplitScreenController;
    @Mock private SplitScreenController mMockSplitScreenController;
    @Mock private SurfaceControl mMockLeash;


    @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
    @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
    @Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
    @Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
@@ -315,6 +317,30 @@ public class PipSchedulerTest {
        verify(mMockAlphaAnimator, never()).start();
        verify(mMockAlphaAnimator, never()).start();
    }
    }


    @Test
    public void onPipTransitionStateChanged_exiting_endAnimation() {
        mPipScheduler.setOverlayFadeoutAnimator(mMockAlphaAnimator);
        when(mMockAlphaAnimator.isStarted()).thenReturn(true);
        mPipScheduler.onPipTransitionStateChanged(PipTransitionState.ENTERED_PIP,
                PipTransitionState.EXITING_PIP, null);

        verify(mMockAlphaAnimator, times(1)).end();
        assertNull("mOverlayFadeoutAnimator should be reset to null",
                mPipScheduler.getOverlayFadeoutAnimator());
    }

    @Test
    public void onPipTransitionStateChanged_scheduledBoundsChange_endAnimation() {
        mPipScheduler.setOverlayFadeoutAnimator(mMockAlphaAnimator);
        when(mMockAlphaAnimator.isStarted()).thenReturn(true);
        mPipScheduler.onPipTransitionStateChanged(PipTransitionState.ENTERED_PIP,
                PipTransitionState.SCHEDULED_BOUNDS_CHANGE, null);

        verify(mMockAlphaAnimator, times(1)).end();
        assertNull("mOverlayFadeoutAnimator should be reset to null",
                mPipScheduler.getOverlayFadeoutAnimator());
    }

    private void setNullPipTaskToken() {
    private void setNullPipTaskToken() {
        when(mMockPipTransitionState.getPipTaskToken()).thenReturn(null);
        when(mMockPipTransitionState.getPipTaskToken()).thenReturn(null);
    }
    }