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

Commit 43111cf9 authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

[PiP2] Cover draws for non-seamless resize

Cover up the new draws with screenshots after
finishing resizing PiP if the seamless resize
is not enabled. Fade out the screenshot after the
transition is finished.

Bug: 398254469
Flag: com.android.wm.shell.enable_pip2
Test: atest PipSchedulerTest
Test: atest PipTaskListenerTest
Test: pinch to resize a non-seamless resize PiP
Change-Id: If6907344bebc35bb3bab8d4dfc09b61a07ac727e
parent ad27226a
Loading
Loading
Loading
Loading
+53 −0
Original line number Diff line number Diff line
@@ -16,9 +16,11 @@

package com.android.wm.shell.pip2.phone;

import android.app.PictureInPictureParams;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -28,6 +30,7 @@ import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDesktopState;
@@ -39,6 +42,7 @@ import com.android.wm.shell.shared.split.SplitScreenConstants;
import com.android.wm.shell.splitscreen.SplitScreenController;

import java.util.Optional;
import java.util.function.Supplier;

/**
 * Scheduler for Shell initiated PiP transitions and animations.
@@ -46,6 +50,15 @@ import java.util.Optional;
public class PipScheduler {
    private static final String TAG = PipScheduler.class.getSimpleName();

    /**
     * The fixed start delay in ms when fading out the content overlay from bounds animation.
     * The fadeout animation is guaranteed to start after the client has drawn under the new config.
     */
    public static final int EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS =
            SystemProperties.getInt(
                    "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400);
    private static final int CONTENT_OVERLAY_FADE_OUT_DURATION_MS = 500;

    private final Context mContext;
    private final PipBoundsState mPipBoundsState;
    private final ShellExecutor mMainExecutor;
@@ -60,6 +73,7 @@ public class PipScheduler {
    @Nullable private Runnable mUpdateMovementBoundsRunnable;

    private PipAlphaAnimatorSupplier mPipAlphaAnimatorSupplier;
    private Supplier<PictureInPictureParams> mPipParamsSupplier;

    public PipScheduler(Context context,
            PipBoundsState pipBoundsState,
@@ -222,6 +236,16 @@ public class PipScheduler {
        tx.apply();
    }

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

    void setUpdateMovementBoundsRunnable(@Nullable Runnable updateMovementBoundsRunnable) {
        mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
    }
@@ -236,6 +260,25 @@ public class PipScheduler {
        if (mPipBoundsState.getBounds().equals(newBounds)) {
            return;
        }

        // Take a screenshot of PiP and fade it out after resize is finished if seamless resize
        // is off and if the PiP size is changing.
        boolean animateCrossFadeResize = !getPipParams().isSeamlessResizeEnabled()
                && !(mPipBoundsState.getBounds().width() == newBounds.width()
                && mPipBoundsState.getBounds().height() == newBounds.height());
        if (animateCrossFadeResize) {
            final Rect crop = new Rect(newBounds);
            crop.offsetTo(0, 0);
            // Note: Put this at layer=MAX_VALUE-2 since the input consumer for PIP is placed at
            //       MAX_VALUE-1
            final SurfaceControl snapshotSurface = ScreenshotUtils.takeScreenshot(
                    mSurfaceControlTransactionFactory.getTransaction(),
                    mPipTransitionState.getPinnedTaskLeash(), crop, Integer.MAX_VALUE - 2);
            startOverlayFadeoutAnimation(snapshotSurface, false /* withStartDelay */, () -> {
                mSurfaceControlTransactionFactory.getTransaction().remove(snapshotSurface).apply();
            });
        }

        mPipBoundsState.setBounds(newBounds);
        maybeUpdateMovementBounds();
    }
@@ -259,4 +302,14 @@ public class PipScheduler {
    void setPipAlphaAnimatorSupplier(@NonNull PipAlphaAnimatorSupplier supplier) {
        mPipAlphaAnimatorSupplier = supplier;
    }

    void setPipParamsSupplier(@NonNull Supplier<PictureInPictureParams> pipParamsSupplier) {
        mPipParamsSupplier = pipParamsSupplier;
    }

    @NonNull
    private PictureInPictureParams getPipParams() {
        if (mPipParamsSupplier == null) return new PictureInPictureParams.Builder().build();
        return mPipParamsSupplier.get();
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -91,6 +91,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
            });
        }
        mPipResizeAnimatorSupplier = PipResizeAnimator::new;
        mPipScheduler.setPipParamsSupplier(this::getPictureInPictureParams);
    }

    void setPictureInPictureParams(@Nullable PictureInPictureParams params) {
+4 −23
Original line number Diff line number Diff line
@@ -50,7 +50,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemProperties;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -102,15 +101,6 @@ public class PipTransition extends PipTransitionController implements
            "animating_bounds_change_duration";
    static final int BOUNDS_CHANGE_JUMPCUT_DURATION = 0;

    /**
     * The fixed start delay in ms when fading out the content overlay from bounds animation.
     * The fadeout animation is guaranteed to start after the client has drawn under the new config.
     */
    private static final int EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS =
            SystemProperties.getInt(
                    "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400);
    private static final int CONTENT_OVERLAY_FADE_OUT_DURATION_MS = 500;

    //
    // Dependencies
    //
@@ -481,7 +471,8 @@ public class PipTransition extends PipTransitionController implements

        if (swipePipToHomeOverlay != null) {
            // fadeout the overlay if needed.
            startOverlayFadeoutAnimation(swipePipToHomeOverlay, () -> {
            mPipScheduler.startOverlayFadeoutAnimation(swipePipToHomeOverlay,
                    true /* withStartDelay */, () -> {
                SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
                tx.remove(swipePipToHomeOverlay);
                tx.apply();
@@ -542,8 +533,8 @@ public class PipTransition extends PipTransitionController implements
        animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange));
        animator.setAnimationEndCallback(() -> {
            if (animator.getContentOverlayLeash() != null) {
                startOverlayFadeoutAnimation(animator.getContentOverlayLeash(),
                        animator::clearAppIconOverlay);
                mPipScheduler.startOverlayFadeoutAnimation(animator.getContentOverlayLeash(),
                        true /* withStartDelay */, animator::clearAppIconOverlay);
            }
            finishTransition();
        });
@@ -551,16 +542,6 @@ public class PipTransition extends PipTransitionController implements
        return true;
    }

    private void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash,
            @NonNull Runnable onAnimationEnd) {
        PipAlphaAnimator animator = new PipAlphaAnimator(mContext, overlayLeash,
                null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT);
        animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DURATION_MS);
        animator.setStartDelay(EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
        animator.setAnimationEndCallback(onAnimationEnd);
        animator.start();
    }

    private void handleBoundsEnterFixedRotation(TransitionInfo info,
            TransitionInfo.Change outPipTaskChange,
            TransitionInfo.Change outPipActivityChange) {
+26 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static org.mockito.kotlin.VerificationKt.times;
import static org.mockito.kotlin.VerificationKt.verify;

import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Matrix;
@@ -47,6 +48,7 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.util.StubTransaction;

import org.junit.Before;
import org.junit.Test;
@@ -107,6 +109,8 @@ public class PipSchedulerTest {
        mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
        mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, startTx, finishTx, direction) ->
                mMockAlphaAnimator);
        final PictureInPictureParams params = new PictureInPictureParams.Builder().build();
        mPipScheduler.setPipParamsSupplier(() -> params);

        SurfaceControl testLeash = new SurfaceControl.Builder()
                .setContainerLayer()
@@ -289,6 +293,28 @@ public class PipSchedulerTest {
        verify(mMockUpdateMovementBoundsRunnable, times(1)).run();
    }

    @Test
    public void finishResize_nonSeamless_alphaAnimatorStarted() {
        final PictureInPictureParams params =
                new PictureInPictureParams.Builder().setSeamlessResizeEnabled(false).build();
        mPipScheduler.setPipParamsSupplier(() -> params);
        when(mMockFactory.getTransaction()).thenReturn(new StubTransaction());

        mPipScheduler.scheduleFinishResizePip(TEST_BOUNDS);

        verify(mMockAlphaAnimator, times(1)).start();
    }

    @Test
    public void finishResize_seamless_animatorNotStarted() {
        final PictureInPictureParams params =
                new PictureInPictureParams.Builder().setSeamlessResizeEnabled(true).build();
        mPipScheduler.setPipParamsSupplier(() -> params);

        mPipScheduler.scheduleFinishResizePip(TEST_BOUNDS);
        verify(mMockAlphaAnimator, never()).start();
    }

    private void setNullPipTaskToken() {
        when(mMockPipTransitionState.getPipTaskToken()).thenReturn(null);
    }
+2 −0
Original line number Diff line number Diff line
@@ -294,6 +294,8 @@ public class PipTaskListenerTest {
        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
                mMockPipBoundsAlgorithm, mMockShellExecutor);
        clearInvocations(mMockPipScheduler);

        Bundle extras = new Bundle();
        extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false);