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

Commit fe5078f0 authored by Hongwei Wang's avatar Hongwei Wang
Browse files

Use snapshot in enter content-pip animation

When entering content-pip
- We take a snapshot for the host task within the
  ActivityStarter#startActivityInner function, this is to make sure that
  the snapshot is taken before an app switches to the placeholder content
- We retrieve the snapshot in WM Shell when animating the newly created
  content-pip task, and use that snapshot during the animation
- This works similar to the solid color content overlay when app enters
  PiP without specifying a valid source rect hint. Therefore, we
  abstract the functionality to a dedicated PipContentOverlay class

Video: http://recall/-/aaaaaabFQoRHlzixHdtY/dRSTf7prHc0krPhTKZ0kXx
Bug: 220191201
Test: enter content-pip from ApiDemos app, see also Video
Change-Id: I2420402341a910b44bd8f64e3ad518ebef41bc19
parent e55b0a1f
Loading
Loading
Loading
Loading
+0 −17
Original line number Original line Diff line number Diff line
@@ -34,10 +34,7 @@ import android.graphics.Rect;
import android.os.Build;
import android.os.Build;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
import android.view.DisplayCutout;
import android.view.DisplayCutout;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
import android.window.WindowContainerToken;


import java.lang.annotation.Retention;
import java.lang.annotation.Retention;
@@ -355,20 +352,6 @@ public class TaskInfo {
        return isVisible;
        return isVisible;
    }
    }


    /**
     * @param isLowResolution
     * @return
     * @hide
     */
    public TaskSnapshot getTaskSnapshot(boolean isLowResolution) {
        try {
            return ActivityTaskManager.getService().getTaskSnapshot(taskId, isLowResolution);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e);
            return null;
        }
    }

    /** @hide */
    /** @hide */
    @NonNull
    @NonNull
    @TestApi
    @TestApi
+19 −35
Original line number Original line Diff line number Diff line
@@ -27,13 +27,11 @@ import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.IntDef;
import android.app.TaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.Rect;
import android.view.Choreographer;
import android.view.Choreographer;
import android.view.Surface;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.TaskSnapshot;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -257,7 +255,7 @@ public class PipAnimationController {
                mSurfaceControlTransactionFactory;
                mSurfaceControlTransactionFactory;
        private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
        private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
        private @TransitionDirection int mTransitionDirection;
        private @TransitionDirection int mTransitionDirection;
        protected SurfaceControl mContentOverlay;
        protected PipContentOverlay mContentOverlay;


        private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
        private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
                @AnimationType int animationType,
                @AnimationType int animationType,
@@ -335,43 +333,26 @@ public class PipAnimationController {
            return false;
            return false;
        }
        }


        SurfaceControl getContentOverlay() {
        SurfaceControl getContentOverlayLeash() {
            return mContentOverlay;
            return mContentOverlay == null ? null : mContentOverlay.mLeash;
        }
        }


        PipTransitionAnimator<T> setUseContentOverlay(Context context) {
        void setColorContentOverlay(Context context) {
            final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
            final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
            if (mContentOverlay != null) {
            if (mContentOverlay != null) {
                // remove existing content overlay if there is any.
                mContentOverlay.detach(tx);
                tx.remove(mContentOverlay);
                tx.apply();
            }
            }
            mContentOverlay = new SurfaceControl.Builder(new SurfaceSession())
            mContentOverlay = new PipContentOverlay.PipColorOverlay(context);
                    .setCallsite("PipAnimation")
            mContentOverlay.attach(tx, mLeash);
                    .setName("PipContentOverlay")
                    .setColorLayer()
                    .build();
            tx.show(mContentOverlay);
            tx.setLayer(mContentOverlay, Integer.MAX_VALUE);
            tx.setColor(mContentOverlay, getContentOverlayColor(context));
            tx.setAlpha(mContentOverlay, 0f);
            tx.reparent(mContentOverlay, mLeash);
            tx.apply();
            return this;
        }
        }


        private float[] getContentOverlayColor(Context context) {
        void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
            final TypedArray ta = context.obtainStyledAttributes(new int[] {
            final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
                    android.R.attr.colorBackground });
            if (mContentOverlay != null) {
            try {
                mContentOverlay.detach(tx);
                int colorAccent = ta.getColor(0, 0);
                return new float[] {
                        Color.red(colorAccent) / 255f,
                        Color.green(colorAccent) / 255f,
                        Color.blue(colorAccent) / 255f };
            } finally {
                ta.recycle();
            }
            }
            mContentOverlay = new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint);
            mContentOverlay.attach(tx, mLeash);
        }
        }


        /**
        /**
@@ -575,7 +556,7 @@ public class PipAnimationController {
                    final Rect start = getStartValue();
                    final Rect start = getStartValue();
                    final Rect end = getEndValue();
                    final Rect end = getEndValue();
                    if (mContentOverlay != null) {
                    if (mContentOverlay != null) {
                        tx.setAlpha(mContentOverlay, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
                        mContentOverlay.onAnimationUpdate(tx, fraction);
                    }
                    }
                    if (rotatedEndRect != null) {
                    if (rotatedEndRect != null) {
                        // Animate the bounds in a different orientation. It only happens when
                        // Animate the bounds in a different orientation. It only happens when
@@ -680,7 +661,7 @@ public class PipAnimationController {
                            .round(tx, leash, shouldApplyCornerRadius())
                            .round(tx, leash, shouldApplyCornerRadius())
                            .shadow(tx, leash, shouldApplyShadowRadius());
                            .shadow(tx, leash, shouldApplyShadowRadius());
                    // TODO(b/178632364): this is a work around for the black background when
                    // TODO(b/178632364): this is a work around for the black background when
                    // entering PiP in buttion navigation mode.
                    // entering PiP in button navigation mode.
                    if (isInPipDirection(direction)) {
                    if (isInPipDirection(direction)) {
                        tx.setWindowCrop(leash, getStartValue());
                        tx.setWindowCrop(leash, getStartValue());
                    }
                    }
@@ -704,6 +685,9 @@ public class PipAnimationController {
                    } else {
                    } else {
                        getSurfaceTransactionHelper().crop(tx, leash, destBounds);
                        getSurfaceTransactionHelper().crop(tx, leash, destBounds);
                    }
                    }
                    if (mContentOverlay != null) {
                        mContentOverlay.onAnimationEnd(tx, destBounds);
                    }
                }
                }


                @Override
                @Override
+164 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.pip;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.TaskSnapshot;

/**
 * Represents the content overlay used during the entering PiP animation.
 */
public abstract class PipContentOverlay {
    protected SurfaceControl mLeash;

    /** Attaches the internal {@link #mLeash} to the given parent leash. */
    public abstract void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash);

    /** Detaches the internal {@link #mLeash} from its parent by removing itself. */
    public void detach(SurfaceControl.Transaction tx) {
        if (mLeash != null && mLeash.isValid()) {
            tx.remove(mLeash);
            tx.apply();
        }
    }

    /**
     * Animates the internal {@link #mLeash} by a given fraction.
     * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
     *                 call apply on this transaction, it should be applied on the caller side.
     * @param fraction progress of the animation ranged from 0f to 1f.
     */
    public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction);

    /**
     * Callback when reaches the end of animation on the internal {@link #mLeash}.
     * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
     *                 call apply on this transaction, it should be applied on the caller side.
     * @param destinationBounds {@link Rect} of the final bounds.
     */
    public abstract void onAnimationEnd(SurfaceControl.Transaction atomicTx,
            Rect destinationBounds);

    /** A {@link PipContentOverlay} uses solid color. */
    public static final class PipColorOverlay extends PipContentOverlay {
        private final Context mContext;

        public PipColorOverlay(Context context) {
            mContext = context;
            mLeash = new SurfaceControl.Builder(new SurfaceSession())
                    .setCallsite("PipAnimation")
                    .setName(PipColorOverlay.class.getSimpleName())
                    .setColorLayer()
                    .build();
        }

        @Override
        public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
            tx.show(mLeash);
            tx.setLayer(mLeash, Integer.MAX_VALUE);
            tx.setColor(mLeash, getContentOverlayColor(mContext));
            tx.setAlpha(mLeash, 0f);
            tx.reparent(mLeash, parentLeash);
            tx.apply();
        }

        @Override
        public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) {
            atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
        }

        @Override
        public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
            // Do nothing. Color overlay should be fully opaque by now.
        }

        private float[] getContentOverlayColor(Context context) {
            final TypedArray ta = context.obtainStyledAttributes(new int[] {
                    android.R.attr.colorBackground });
            try {
                int colorAccent = ta.getColor(0, 0);
                return new float[] {
                        Color.red(colorAccent) / 255f,
                        Color.green(colorAccent) / 255f,
                        Color.blue(colorAccent) / 255f };
            } finally {
                ta.recycle();
            }
        }
    }

    /** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */
    public static final class PipSnapshotOverlay extends PipContentOverlay {
        private final TaskSnapshot mSnapshot;
        private final Rect mSourceRectHint;

        private float mTaskSnapshotScaleX;
        private float mTaskSnapshotScaleY;

        public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
            mSnapshot = snapshot;
            mSourceRectHint = new Rect(sourceRectHint);
            mLeash = new SurfaceControl.Builder(new SurfaceSession())
                    .setCallsite("PipAnimation")
                    .setName(PipSnapshotOverlay.class.getSimpleName())
                    .build();
        }

        @Override
        public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
            mTaskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
                    / mSnapshot.getHardwareBuffer().getWidth();
            mTaskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
                    / mSnapshot.getHardwareBuffer().getHeight();
            tx.show(mLeash);
            tx.setLayer(mLeash, Integer.MAX_VALUE);
            tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer());
            tx.setCrop(mLeash, mSourceRectHint);
            tx.setScale(mLeash, mTaskSnapshotScaleX, mTaskSnapshotScaleY);
            tx.reparent(mLeash, parentLeash);
            tx.apply();
        }

        @Override
        public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) {
            // Do nothing. Keep the snapshot till animation ends.
        }

        @Override
        public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
            // Work around to make sure the snapshot overlay is aligned with PiP window before
            // the atomicTx is committed along with the final WindowContainerTransaction.
            final SurfaceControl.Transaction nonAtomicTx = new SurfaceControl.Transaction();
            final float scaleX = (float) destinationBounds.width()
                    / mSnapshot.getHardwareBuffer().getWidth();
            final float scaleY = (float) destinationBounds.height()
                    / mSnapshot.getHardwareBuffer().getHeight();
            final float scale = Math.max(scaleX, scaleY);
            nonAtomicTx.setScale(mLeash, scale, scale);
            nonAtomicTx.setPosition(mLeash,
                    -scale * mSourceRectHint.left / mTaskSnapshotScaleX,
                    -scale * mSourceRectHint.top / mTaskSnapshotScaleY);
            nonAtomicTx.apply();
            atomicTx.remove(mLeash);
        }
    }
}
+20 −8
Original line number Original line Diff line number Diff line
@@ -66,6 +66,7 @@ import android.view.Display;
import android.view.Surface;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl;
import android.window.TaskOrganizer;
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction;


@@ -152,8 +153,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
            final int direction = animator.getTransitionDirection();
            final int direction = animator.getTransitionDirection();
            final int animationType = animator.getAnimationType();
            final int animationType = animator.getAnimationType();
            final Rect destinationBounds = animator.getDestinationBounds();
            final Rect destinationBounds = animator.getDestinationBounds();
            if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
            if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
                fadeOutAndRemoveOverlay(animator.getContentOverlay(),
                fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
                        animator::clearContentOverlay, true /* withStartDelay*/);
                        animator::clearContentOverlay, true /* withStartDelay*/);
            }
            }
            if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
            if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
@@ -186,8 +187,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
        public void onPipAnimationCancel(TaskInfo taskInfo,
        public void onPipAnimationCancel(TaskInfo taskInfo,
                PipAnimationController.PipTransitionAnimator animator) {
                PipAnimationController.PipTransitionAnimator animator) {
            final int direction = animator.getTransitionDirection();
            final int direction = animator.getTransitionDirection();
            if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
            if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
                fadeOutAndRemoveOverlay(animator.getContentOverlay(),
                fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
                        animator::clearContentOverlay, true /* withStartDelay */);
                        animator::clearContentOverlay, true /* withStartDelay */);
            }
            }
            sendOnPipTransitionCancelled(direction);
            sendOnPipTransitionCancelled(direction);
@@ -803,8 +804,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
        final PipAnimationController.PipTransitionAnimator<?> animator =
        final PipAnimationController.PipTransitionAnimator<?> animator =
                mPipAnimationController.getCurrentAnimator();
                mPipAnimationController.getCurrentAnimator();
        if (animator != null) {
        if (animator != null) {
            if (animator.getContentOverlay() != null) {
            if (animator.getContentOverlayLeash() != null) {
                removeContentOverlay(animator.getContentOverlay(), animator::clearContentOverlay);
                removeContentOverlay(animator.getContentOverlayLeash(),
                        animator::clearContentOverlay);
            }
            }
            animator.removeAllUpdateListeners();
            animator.removeAllUpdateListeners();
            animator.removeAllListeners();
            animator.removeAllListeners();
@@ -1486,7 +1488,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
        if (isInPipDirection(direction)) {
        if (isInPipDirection(direction)) {
            // Similar to auto-enter-pip transition, we use content overlay when there is no
            // Similar to auto-enter-pip transition, we use content overlay when there is no
            // source rect hint to enter PiP use bounds animation.
            // source rect hint to enter PiP use bounds animation.
            if (sourceHintRect == null) animator.setUseContentOverlay(mContext);
            if (sourceHintRect == null) {
                animator.setColorContentOverlay(mContext);
            } else {
                final TaskSnapshot snapshot = PipUtils.getTaskSnapshot(
                        mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */);
                if (snapshot != null) {
                    // use the task snapshot during the animation, this is for
                    // launch-into-pip aka. content-pip use case.
                    animator.setSnapshotContentOverlay(snapshot, sourceHintRect);
                }
            }
            // The destination bounds are used for the end rect of animation and the final bounds
            // The destination bounds are used for the end rect of animation and the final bounds
            // after animation finishes. So after the animation is started, the destination bounds
            // after animation finishes. So after the animation is started, the destination bounds
            // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
            // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
@@ -1550,7 +1562,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
     */
     */
    void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback,
    void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback,
            boolean withStartDelay) {
            boolean withStartDelay) {
        if (surface == null) {
        if (surface == null || !surface.isValid()) {
            return;
            return;
        }
        }


+1 −1
Original line number Original line Diff line number Diff line
@@ -709,7 +709,7 @@ public class PipTransition extends PipTransitionController {
            if (sourceHintRect == null) {
            if (sourceHintRect == null) {
                // We use content overlay when there is no source rect hint to enter PiP use bounds
                // We use content overlay when there is no source rect hint to enter PiP use bounds
                // animation.
                // animation.
                animator.setUseContentOverlay(mContext);
                animator.setColorContentOverlay(mContext);
            }
            }
        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
            startTransaction.setAlpha(leash, 0f);
            startTransaction.setAlpha(leash, 0f);
Loading