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

Commit f3e26616 authored by Ben Lin's avatar Ben Lin Committed by Android (Google) Code Review
Browse files

Merge "Add basic PiP Transition foundation." into sc-dev

parents a397f028 5426e2ae
Loading
Loading
Loading
Loading
+27 −22
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.animation.Animator;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.app.TaskInfo;
import android.graphics.Rect;
import android.view.Choreographer;
import android.view.SurfaceControl;
@@ -99,18 +100,20 @@ public class PipAnimationController {

    @SuppressWarnings("unchecked")
    @VisibleForTesting
    public PipTransitionAnimator getAnimator(SurfaceControl leash,
    public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
            Rect destinationBounds, float alphaStart, float alphaEnd) {
        if (mCurrentAnimator == null) {
            mCurrentAnimator = setupPipTransitionAnimator(
                    PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
                    PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
                            alphaEnd));
        } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
                && mCurrentAnimator.isRunning()) {
            mCurrentAnimator.updateEndValue(alphaEnd);
        } else {
            mCurrentAnimator.cancel();
            mCurrentAnimator = setupPipTransitionAnimator(
                    PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
                    PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
                            alphaEnd));
        }
        return mCurrentAnimator;
    }
@@ -131,13 +134,13 @@ public class PipAnimationController {
     * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed.
     */
    @VisibleForTesting
    public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect baseBounds,
            Rect startBounds, Rect endBounds, Rect sourceHintRect,
    public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
            Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
            @PipAnimationController.TransitionDirection int direction, float startingAngle) {
        if (mCurrentAnimator == null) {
            mCurrentAnimator = setupPipTransitionAnimator(
                    PipTransitionAnimator.ofBounds(leash, startBounds, startBounds, endBounds,
                            sourceHintRect, direction, 0 /* startingAngle */));
                    PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
                            endBounds, sourceHintRect, direction, 0 /* startingAngle */));
        } 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
@@ -152,8 +155,8 @@ public class PipAnimationController {
        } else {
            mCurrentAnimator.cancel();
            mCurrentAnimator = setupPipTransitionAnimator(
                    PipTransitionAnimator.ofBounds(leash, baseBounds, startBounds, endBounds,
                            sourceHintRect, direction, startingAngle));
                    PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
                            endBounds, sourceHintRect, direction, startingAngle));
        }
        return mCurrentAnimator;
    }
@@ -177,18 +180,18 @@ public class PipAnimationController {
        /**
         * Called when PiP animation is started.
         */
        public void onPipAnimationStart(PipTransitionAnimator animator) {}
        public void onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator) {}

        /**
         * Called when PiP animation is ended.
         */
        public void onPipAnimationEnd(SurfaceControl.Transaction tx,
        public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
                PipTransitionAnimator animator) {}

        /**
         * Called when PiP animation is cancelled.
         */
        public void onPipAnimationCancel(PipTransitionAnimator animator) {}
        public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {}
    }

    /**
@@ -198,6 +201,7 @@ public class PipAnimationController {
    public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
            ValueAnimator.AnimatorUpdateListener,
            ValueAnimator.AnimatorListener {
        private final TaskInfo mTaskInfo;
        private final SurfaceControl mLeash;
        private final @AnimationType int mAnimationType;
        private final Rect mDestinationBounds = new Rect();
@@ -213,9 +217,10 @@ public class PipAnimationController {
        private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
        private @TransitionDirection int mTransitionDirection;

        private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType,
                Rect destinationBounds, T baseValue, T startValue, T endValue,
                float startingAngle) {
        private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
                @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue,
                T endValue, float startingAngle) {
            mTaskInfo = taskInfo;
            mLeash = leash;
            mAnimationType = animationType;
            mDestinationBounds.set(destinationBounds);
@@ -234,7 +239,7 @@ public class PipAnimationController {
            mCurrentValue = mStartValue;
            onStartTransaction(mLeash, newSurfaceControlTransaction());
            if (mPipAnimationCallback != null) {
                mPipAnimationCallback.onPipAnimationStart(this);
                mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
            }
        }

@@ -250,14 +255,14 @@ public class PipAnimationController {
            final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
            onEndTransaction(mLeash, tx, mTransitionDirection);
            if (mPipAnimationCallback != null) {
                mPipAnimationCallback.onPipAnimationEnd(tx, this);
                mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
            }
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            if (mPipAnimationCallback != null) {
                mPipAnimationCallback.onPipAnimationCancel(this);
                mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this);
            }
        }

@@ -368,9 +373,9 @@ public class PipAnimationController {
        abstract void applySurfaceControlTransaction(SurfaceControl leash,
                SurfaceControl.Transaction tx, float fraction);

        static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash,
        static PipTransitionAnimator<Float> ofAlpha(TaskInfo taskInfo, SurfaceControl leash,
                Rect destinationBounds, float startValue, float endValue) {
            return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA,
            return new PipTransitionAnimator<Float>(taskInfo, leash, ANIM_TYPE_ALPHA,
                    destinationBounds, startValue, startValue, endValue, 0) {
                @Override
                void applySurfaceControlTransaction(SurfaceControl leash,
@@ -403,7 +408,7 @@ public class PipAnimationController {
            };
        }

        static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
        static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
                Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
                @PipAnimationController.TransitionDirection int direction, float startingAngle) {
            // Just for simplicity we'll interpolate between the source rect hint insets and empty
@@ -427,7 +432,7 @@ public class PipAnimationController {
            final Rect sourceInsets = new Rect(0, 0, 0, 0);

            // construct new Rect instances in case they are recycled
            return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
            return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
                    endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue),
                    startingAngle) {
                private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
+44 −1
Original line number Diff line number Diff line
@@ -19,7 +19,9 @@ package com.android.wm.shell.pip;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;

import android.annotation.NonNull;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
@@ -27,7 +29,6 @@ import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.Size;
import android.util.TypedValue;
import android.view.DisplayInfo;
import android.view.Gravity;

import com.android.wm.shell.common.DisplayLayout;
@@ -142,10 +143,52 @@ public class PipBoundsAlgorithm {
                true /* useCurrentMinEdgeSize */, false /* useCurrentSize */);
    }

    /**
     *
     * Get the smallest/most minimal size allowed.
     */
    public Size getMinimalSize(ActivityInfo activityInfo) {
        if (activityInfo == null || activityInfo.windowLayout == null) {
            return null;
        }
        final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
        // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
        // without minWidth/minHeight
        if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
            return new Size(windowLayout.minWidth, windowLayout.minHeight);
        }
        return null;
    }

    /**
     * Returns the source hint rect if it is valid (if provided and is contained by the current
     * task bounds).
     */
    public static Rect getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds) {
        final Rect sourceHintRect = params != null && params.hasSourceBoundsHint()
                ? params.getSourceRectHint()
                : null;
        if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) {
            return sourceHintRect;
        }
        return null;
    }

    public float getDefaultAspectRatio() {
        return mDefaultAspectRatio;
    }

    /**
     *
     * Give the aspect ratio if the supplied PiP params have one, or else return default.
     */
    public float getAspectRatioOrDefault(
            @android.annotation.Nullable PictureInPictureParams params) {
        return params != null && params.hasSetAspectRatio()
                ? params.getAspectRatio()
                : getDefaultAspectRatio();
    }

    /**
     * @return whether the given {@param aspectRatio} is valid.
     */
+10 −1
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.util.Size;
import android.view.Display;
import android.view.DisplayInfo;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.TriConsumer;
@@ -344,6 +343,16 @@ public final class PipBoundsState {
        }
    }

    /**
     * Initialize states when first entering PiP.
     */
    public void setBoundsStateForEntry(ComponentName componentName, float aspectRatio,
            Size overrideMinSize) {
        setLastPipComponentName(componentName);
        setAspectRatio(aspectRatio);
        setOverrideMinSize(overrideMinSize);
    }

    /** Returns whether the shelf is currently showing. */
    public boolean isShelfShowing() {
        return mIsShelfShowing;
+37 −102

File changed.

Preview size limit exceeded, changes collapsed.

+162 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;

import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

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

/**
 * Implementation of transitions for PiP on phone. Responsible for enter (alpha, bounds) and
 * exit animation.
 */
public class PipTransition extends PipTransitionController {

    private final int mEnterExitAnimationDuration;
    private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
    private Transitions.TransitionFinishCallback mFinishCallback;

    public PipTransition(Context context,
            PipBoundsState pipBoundsState, PipMenuController pipMenuController,
            PipBoundsAlgorithm pipBoundsAlgorithm,
            PipAnimationController pipAnimationController,
            Transitions transitions,
            @NonNull ShellTaskOrganizer shellTaskOrganizer) {
        super(pipBoundsState, pipMenuController, pipBoundsAlgorithm,
                pipAnimationController, transitions, shellTaskOrganizer);
        mEnterExitAnimationDuration = context.getResources()
                .getInteger(R.integer.config_pipResizeAnimationDuration);
    }

    @Override
    public boolean startAnimation(@android.annotation.NonNull IBinder transition,
            @android.annotation.NonNull TransitionInfo info,
            @android.annotation.NonNull SurfaceControl.Transaction t,
            @android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) {
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);
            if (change.getTaskInfo() != null
                    && change.getTaskInfo().configuration.windowConfiguration.getWindowingMode()
                    == WINDOWING_MODE_PINNED) {
                mFinishCallback = finishCallback;
                return startEnterAnimation(change.getTaskInfo(), change.getLeash(), t);
            }
        }
        return false;
    }

    @Nullable
    @Override
    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
            @NonNull TransitionRequestInfo request) {
        return null;
    }

    @Override
    public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
            @PipAnimationController.TransitionDirection int direction,
            SurfaceControl.Transaction tx) {
        WindowContainerTransaction wct = new WindowContainerTransaction();
        prepareFinishResizeTransaction(taskInfo, destinationBounds,
                direction, tx, wct);
        mFinishCallback.onTransitionFinished(wct, null);
        finishResizeForMenu(destinationBounds);
    }

    private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
            final SurfaceControl.Transaction t) {
        setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
                taskInfo.topActivityInfo);
        final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
        final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
        PipAnimationController.PipTransitionAnimator animator;
        if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
            final Rect sourceHintRect =
                    PipBoundsAlgorithm.getValidSourceHintRect(
                            taskInfo.pictureInPictureParams, currentBounds);
            animator = mPipAnimationController.getAnimator(taskInfo, leash,
                    currentBounds, currentBounds, destinationBounds, sourceHintRect,
                    TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */);
        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
            t.setAlpha(leash, 0f);
            t.apply();
            animator = mPipAnimationController.getAnimator(taskInfo, leash,
                    destinationBounds, 0f, 1f);
            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
        } else {
            throw new RuntimeException("Unrecognized animation type: "
                    + mOneShotAnimationType);
        }
        animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                .setPipAnimationCallback(mPipAnimationCallback)
                .setDuration(mEnterExitAnimationDuration)
                .start();
        return true;
    }

    private void finishResizeForMenu(Rect destinationBounds) {
        mPipMenuController.movePipMenu(null, null, destinationBounds);
        mPipMenuController.updateMenuBounds(destinationBounds);
    }

    private void prepareFinishResizeTransaction(TaskInfo taskInfo, Rect destinationBounds,
            @PipAnimationController.TransitionDirection int direction,
            SurfaceControl.Transaction tx,
            WindowContainerTransaction wct) {
        Rect taskBounds = null;
        if (isInPipDirection(direction)) {
            // If we are animating from fullscreen using a bounds animation, then reset the
            // activity windowing mode set by WM, and set the task bounds to the final bounds
            taskBounds = destinationBounds;
            wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
            wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds);
        } else if (isOutPipDirection(direction)) {
            // If we are animating to fullscreen, then we need to reset the override bounds
            // on the task to ensure that the task "matches" the parent's bounds.
            taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP)
                    ? null : destinationBounds;
            wct.setWindowingMode(taskInfo.token, getOutPipWindowingMode());
            // Simply reset the activity mode set prior to the animation running.
            wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
        }

        wct.setBounds(taskInfo.token, taskBounds);
        wct.setBoundsChangeTransaction(taskInfo.token, tx);
    }
}
Loading