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

Commit 33c3fcc6 authored by Hongwei Wang's avatar Hongwei Wang
Browse files

Fix entering PiP transition in button navigation mode

Included in this CL
- when there is no source rect hint, use a content overlay during the transition
- otherwise, take account the display cutout to offset/inset the source
  rect hint

Test with following variants when entering PiP in button nav
- from 0 / 90 / 270 rotation
- with or without source rect hint
- display cutout mode set to default or shortEdge

Video: http://recall/-/aaaaaabFQoRHlzixHdtY/cxk4vy8VenQPSv83vHEs22
Bug: 191310680
Test: manual, see the test cases listed and video above
Change-Id: Ie54a54de6e55397e25024373ea4e2855fde2d9f7
Merged-In: Ie54a54de6e55397e25024373ea4e2855fde2d9f7
parent 5a590720
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
@@ -26,10 +26,14 @@ import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.app.TaskInfo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;

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

        private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
                @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue,
@@ -331,6 +336,53 @@ public class PipAnimationController {
            return false;
        }

        SurfaceControl getContentOverlay() {
            return mContentOverlay;
        }

        PipTransitionAnimator<T> setUseContentOverlay(Context context) {
            final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
            if (mContentOverlay != null) {
                // remove existing content overlay if there is any.
                tx.remove(mContentOverlay);
                tx.apply();
            }
            mContentOverlay = new SurfaceControl.Builder(new SurfaceSession())
                    .setCallsite("PipAnimation")
                    .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) {
            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();
            }
        }

        /**
         * Clears the {@link #mContentOverlay}, this should be done after the content overlay is
         * faded out, such as in {@link PipTaskOrganizer#fadeOutAndRemoveOverlay}
         */
        void clearContentOverlay() {
            mContentOverlay = null;
        }

        @VisibleForTesting
        @TransitionDirection public int getTransitionDirection() {
            return mTransitionDirection;
@@ -517,6 +569,9 @@ public class PipAnimationController {
                    final Rect base = getBaseValue();
                    final Rect start = getStartValue();
                    final Rect end = getEndValue();
                    if (mContentOverlay != null) {
                        tx.setAlpha(mContentOverlay, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
                    }
                    if (rotatedEndRect != null) {
                        // Animate the bounds in a different orientation. It only happens when
                        // switching between PiP and fullscreen.
+54 −18
Original line number Diff line number Diff line
@@ -107,6 +107,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
     */
    private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;

    /**
     * The fixed start delay in ms when fading out the content overlay from bounds animation.
     * This is to overcome the flicker caused by configuration change when rotating from landscape
     * to portrait PiP in button navigation mode.
     */
    private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500;

    // Not a complete set of states but serves what we want right now.
    private enum State {
        UNDEFINED(0),
@@ -176,6 +183,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
            final int direction = animator.getTransitionDirection();
            final int animationType = animator.getAnimationType();
            final Rect destinationBounds = animator.getDestinationBounds();
            if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
                fadeOutAndRemoveOverlay(animator.getContentOverlay(),
                        animator::clearContentOverlay, true /* withStartDelay*/);
            }
            if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
                    && direction == TRANSITION_DIRECTION_TO_PIP) {
                // Notify the display to continue the deferred orientation change.
@@ -199,17 +210,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
                finishResize(tx, destinationBounds, direction, animationType);
                sendOnPipTransitionFinished(direction);
            }
            if (direction == TRANSITION_DIRECTION_TO_PIP) {
                // TODO (b//169221267): Add jank listener for transactions without buffer updates.
                //InteractionJankMonitor.getInstance().end(
                //        InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
            }
        }

        @Override
        public void onPipAnimationCancel(TaskInfo taskInfo,
                PipAnimationController.PipTransitionAnimator animator) {
            sendOnPipTransitionCancelled(animator.getTransitionDirection());
            final int direction = animator.getTransitionDirection();
            if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
                fadeOutAndRemoveOverlay(animator.getContentOverlay(),
                        animator::clearContentOverlay, true /* withStartDelay */);
            }
            sendOnPipTransitionCancelled(direction);
        }
    };

@@ -640,7 +651,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,

            // Remove the swipe to home overlay
            if (swipeToHomeOverlay != null) {
                fadeOutAndRemoveOverlay(swipeToHomeOverlay);
                fadeOutAndRemoveOverlay(swipeToHomeOverlay,
                        null /* callback */, false /* withStartDelay */);
            }
        }, tx);
        mInSwipePipToHomeTransition = false;
@@ -723,9 +735,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
            mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
        }

        final PipAnimationController.PipTransitionAnimator animator =
        final PipAnimationController.PipTransitionAnimator<?> animator =
                mPipAnimationController.getCurrentAnimator();
        if (animator != null) {
            if (animator.getContentOverlay() != null) {
                removeContentOverlay(animator.getContentOverlay(), animator::clearContentOverlay);
            }
            animator.removeAllUpdateListeners();
            animator.removeAllListeners();
            animator.cancel();
@@ -1196,7 +1211,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
                            snapshotDest);

                    // Start animation to fade out the snapshot.
                    fadeOutAndRemoveOverlay(snapshotSurface);
                    fadeOutAndRemoveOverlay(snapshotSurface,
                            null /* callback */, false /* withStartDelay */);
                });
            } else {
                applyFinishBoundsResize(wct, direction);
@@ -1287,15 +1303,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
        animator.setTransitionDirection(direction)
                .setPipAnimationCallback(mPipAnimationCallback)
                .setPipTransactionHandler(mPipTransactionHandler)
                .setDuration(durationMs)
                .start();
        if (rotationDelta != Surface.ROTATION_0 && direction == TRANSITION_DIRECTION_TO_PIP) {
                .setDuration(durationMs);
        if (isInPipDirection(direction)) {
            // Similar to auto-enter-pip transition, we use content overlay when there is no
            // source rect hint to enter PiP use bounds animation.
            if (sourceHintRect == null) animator.setUseContentOverlay(mContext);
            // 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
            // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
            // without affecting the animation.
            if (rotationDelta != Surface.ROTATION_0) {
                animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
            }
        }
        animator.start();
        return animator;
    }

@@ -1308,6 +1329,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
            outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
            // Transform the destination bounds to current display coordinates.
            rotateBounds(outDestinationBounds, displayBounds, mNextRotation, mCurrentRotation);
            // When entering PiP (from button navigation mode), adjust the source rect hint by
            // display cutout if applicable.
            if (sourceHintRect != null && mTaskInfo.displayCutoutInsets != null) {
                if (rotationDelta == Surface.ROTATION_270) {
                    sourceHintRect.offset(mTaskInfo.displayCutoutInsets.left,
                            mTaskInfo.displayCutoutInsets.top);
                }
            }
        } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
            final Rect rotatedDestinationBounds = new Rect(outDestinationBounds);
            rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(),
@@ -1346,7 +1375,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
    /**
     * Fades out and removes an overlay surface.
     */
    private void fadeOutAndRemoveOverlay(SurfaceControl surface) {
    private void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback,
            boolean withStartDelay) {
        if (surface == null) {
            return;
        }
@@ -1363,13 +1393,19 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                removeContentOverlay(surface, callback);
            }
        });
        animator.setStartDelay(withStartDelay ? CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
        animator.start();
    }

    private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
        final SurfaceControl.Transaction tx =
                mSurfaceControlTransactionFactory.getTransaction();
        tx.remove(surface);
        tx.apply();
            }
        });
        animator.start();
        if (callback != null) callback.run();
    }

    /**
+1 −1
Original line number Diff line number Diff line
@@ -1884,7 +1884,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
            forAllWindows(w -> {
                w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
            }, true /* traverseTopToBottom */);
            mPinnedTaskController.startSeamlessRotationIfNeeded(transaction);
            mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
        }

        mWmService.mDisplayManagerInternal.performTraversal(transaction);
+15 −1
Original line number Diff line number Diff line
@@ -27,13 +27,16 @@ import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.RotationUtils;
import android.util.Slog;
import android.view.IPinnedTaskListener;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.PictureInPictureSurfaceTransaction;

@@ -237,7 +240,8 @@ class PinnedTaskController {
     * rotation of display. The final surface matrix will be replaced by PiPTaskOrganizer after it
     * receives the callback of fixed rotation completion.
     */
    void startSeamlessRotationIfNeeded(SurfaceControl.Transaction t) {
    void startSeamlessRotationIfNeeded(SurfaceControl.Transaction t,
            int oldRotation, int newRotation) {
        final Rect bounds = mDestRotatedBounds;
        final PictureInPictureSurfaceTransaction pipTx = mPipTransaction;
        if (bounds == null && pipTx == null) {
@@ -280,6 +284,16 @@ class PinnedTaskController {
                ? params.getSourceRectHint()
                : null;
        Slog.i(TAG, "Seamless rotation PiP bounds=" + bounds + " hintRect=" + sourceHintRect);
        final int rotationDelta = RotationUtils.deltaRotation(oldRotation, newRotation);
        // Adjust for display cutout if applicable.
        if (sourceHintRect != null && rotationDelta == Surface.ROTATION_270) {
            if (pinnedTask.getDisplayCutoutInsets() != null) {
                final int rotationBackDelta = RotationUtils.deltaRotation(newRotation, oldRotation);
                final Rect displayCutoutInsets = RotationUtils.rotateInsets(
                        Insets.of(pinnedTask.getDisplayCutoutInsets()), rotationBackDelta).toRect();
                sourceHintRect.offset(displayCutoutInsets.left, displayCutoutInsets.top);
            }
        }
        final Rect contentBounds = sourceHintRect != null && areaBounds.contains(sourceHintRect)
                ? sourceHintRect : areaBounds;
        final int w = contentBounds.width();
+5 −6
Original line number Diff line number Diff line
@@ -4107,7 +4107,7 @@ class Task extends WindowContainer<WindowContainer> {
        info.positionInParent = getRelativePosition();

        info.pictureInPictureParams = getPictureInPictureParams(top);
        info.displayCutoutInsets = getDisplayCutoutInsets(top);
        info.displayCutoutInsets = top != null ? top.getDisplayCutoutInsets() : null;
        info.topActivityInfo = mReuseActivitiesReport.top != null
                ? mReuseActivitiesReport.top.info
                : null;
@@ -4142,16 +4142,15 @@ class Task extends WindowContainer<WindowContainer> {
                ? null : new PictureInPictureParams(topVisibleActivity.pictureInPictureArgs);
    }

    private Rect getDisplayCutoutInsets(Task top) {
        if (top == null || top.mDisplayContent == null
                || top.getDisplayInfo().displayCutout == null) return null;
        final WindowState w = top.getTopVisibleAppMainWindow();
    Rect getDisplayCutoutInsets() {
        if (mDisplayContent == null || getDisplayInfo().displayCutout == null) return null;
        final WindowState w = getTopVisibleAppMainWindow();
        final int displayCutoutMode = w == null
                ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                : w.getAttrs().layoutInDisplayCutoutMode;
        return (displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
                || displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)
                ? null : top.getDisplayInfo().displayCutout.getSafeInsets();
                ? null : getDisplayInfo().displayCutout.getSafeInsets();
    }

    /**