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

Commit 25457d42 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Support PiP with fixed rotation

Add 2 cases with rotation change:
(a) Enter PiP from fullscreen.
    http://recall/-/d6GxE5mVL8Ww2IPnx7Nk0o/hDfmdrhrBFjJNOdi1s66OW
(b) Launch fullscreen app with the existing PiP.
    http://recall/-/d6GxE5mVL8Ww2IPnx7Nk0o/bUpHZ6Sh98zKZJnRM7oFy9

Steps of (a):
1. Task#setWindowingMode to PiP. If the next fullscreen activity
   will change display orientation, start fixed rotation on it.
   And start deferring orientation change.
2. PipTaskOrganizer#onFixedRotationStarted is called to mark it
   will be a special case.
3. PipTaskOrganizer#onTaskAppeared is called and starts the PiP
   rotation animation by animateResizePip with rotated destination
   bounds from rotated DisplayLayout.
4. When onPipAnimationEnd, use WCT#scheduleFinishEnterPip to notify
   that the animation is done, so the deferred orientation change
   can continue to update (PinnedTaskController#setEnterPipBounds).
   The end transaction of animation (reset matrix) is deferred
   until fixed rotation is finished. Also freeze the PiP task
   configuration one time, to avoid extra configuration change
   (letterboxed) by rotation change.
5. The seamless rotation starts, the PiP surface is transformed
   to previous rotation based on the bounds of previous step. So
   the PiP task can show the same orientation as rotated display.
   The frozen flag of PiP task configuration is cleared.
6. PipTaskOrganizer#onFixedRotationFinished is called. The final
   PiP destination bounds and the deferred transaction of step 4
   will be sent to WM.

Steps of (b):
1. Fixed rotation happens (PipTaskOrganizer#onFixedRotationStarted)
   when there is an existing PiP. Apply fade-out animation.
2. PipTaskOrganizer#onMovementBoundsChanged is called to update
   rotated destination bounds.
3. PipTaskOrganizer#onFixedRotationFinished is called to apply
   fade-in animation with new bounds.

Other changes:
- WCT#scheduleFinishEnterPip was used to set bounds and notify
  windowing mode change. That is already done by other config
  change oepration of WCT. So this redundant operation is changed
  to be a signal to notify that the PiP animation is done.
- Ignore calculating letterbox bounds for PiP activity because
  it should fill the task.
- Add PinnedTaskController#DEFER_ORIENTATION_CHANGE_TIMEOUT_MS
  to avoid using alpha animation after swiping from any task.
- Consider source rect hint with rotation.

Bug: 165794724
Bug: 175836469
Test: DisplayContentTests#testFixedRotationWithPip
      PipAnimationControllerTest#pipTransitionAnimator_rotatedEndValue

Change-Id: I2c26b5d93996193caaf020bd0e2314c8e1789545
parent 32a48b01
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -109,8 +109,8 @@ public final class WindowContainerTransaction implements Parcelable {
    }

    /**
     * Notify activities within the hierarchy of a container that they have entered picture-in-picture
     * mode with the given bounds.
     * Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task
     * has finished the enter animation with the given bounds.
     */
    @NonNull
    public WindowContainerTransaction scheduleFinishEnterPip(
+48 −29
Original line number Diff line number Diff line
@@ -427,36 +427,44 @@ public class PipAnimationController {
                Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
                @PipAnimationController.TransitionDirection int direction, float startingAngle,
                @Surface.Rotation int rotationDelta) {
            final boolean isOutPipDirection = isOutPipDirection(direction);

            // Just for simplicity we'll interpolate between the source rect hint insets and empty
            // insets to calculate the window crop
            final Rect initialSourceValue;
            if (isOutPipDirection(direction)) {
            if (isOutPipDirection) {
                initialSourceValue = new Rect(endValue);
            } else {
                initialSourceValue = new Rect(baseValue);
            }

            final Rect sourceHintRectInsets;
            if (sourceHintRect == null) {
                sourceHintRectInsets = null;
            } else {
                sourceHintRectInsets = new Rect(sourceHintRect.left - initialSourceValue.left,
                        sourceHintRect.top - initialSourceValue.top,
                        initialSourceValue.right - sourceHintRect.right,
                        initialSourceValue.bottom - sourceHintRect.bottom);
            }
            final Rect sourceInsets = new Rect(0, 0, 0, 0);

            final Rect rotatedEndRect;
            final Rect lastEndRect;
            final Rect initialContainerRect;
            if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
                lastEndRect = new Rect(endValue);
                rotatedEndRect = new Rect(endValue);
                // Rotate the end bounds according to the rotation delta because the display will
                // be rotated to the same orientation.
                rotatedEndRect = new Rect(endValue);
                rotateBounds(rotatedEndRect, endValue, rotationDelta);
                rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
                // Use the rect that has the same orientation as the hint rect.
                initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue;
            } else {
                rotatedEndRect = null;
                rotatedEndRect = lastEndRect = null;
                initialContainerRect = initialSourceValue;
            }

            final Rect sourceHintRectInsets;
            if (sourceHintRect == null) {
                sourceHintRectInsets = null;
            } else {
                sourceHintRectInsets = new Rect(sourceHintRect.left - initialContainerRect.left,
                        sourceHintRect.top - initialContainerRect.top,
                        initialContainerRect.right - sourceHintRect.right,
                        initialContainerRect.bottom - sourceHintRect.bottom);
            }
            final Rect zeroInsets = new Rect(0, 0, 0, 0);

            // construct new Rect instances in case they are recycled
            return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
                    endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue),
@@ -472,8 +480,8 @@ public class PipAnimationController {
                    final Rect end = getEndValue();
                    if (rotatedEndRect != null) {
                        // Animate the bounds in a different orientation. It only happens when
                        // leaving PiP to fullscreen.
                        applyRotation(tx, leash, fraction, start, end, rotatedEndRect);
                        // switching between PiP and fullscreen.
                        applyRotation(tx, leash, fraction, start, end);
                        return;
                    }
                    Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
@@ -481,20 +489,13 @@ public class PipAnimationController {
                    setCurrentValue(bounds);
                    if (inScaleTransition() || sourceHintRect == null) {

                        if (isOutPipDirection(direction)) {
                        if (isOutPipDirection) {
                            getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
                        } else {
                            getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle);
                        }
                    } else {
                        final Rect insets;
                        if (isOutPipDirection(direction)) {
                            insets = mInsetsEvaluator.evaluate(fraction, sourceHintRectInsets,
                                    sourceInsets);
                        } else {
                            insets = mInsetsEvaluator.evaluate(fraction, sourceInsets,
                                    sourceHintRectInsets);
                        }
                        final Rect insets = computeInsets(fraction);
                        getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
                                initialSourceValue, bounds, insets);
                    }
@@ -502,9 +503,17 @@ public class PipAnimationController {
                }

                private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
                        float fraction, Rect start, Rect end, Rect rotatedEndRect) {
                        float fraction, Rect start, Rect end) {
                    if (!end.equals(lastEndRect)) {
                        // If the end bounds are changed during animating (e.g. shelf height), the
                        // rotated end bounds also need to be updated.
                        rotatedEndRect.set(endValue);
                        rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
                        lastEndRect.set(end);
                    }
                    final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect);
                    setCurrentValue(bounds);
                    final Rect insets = computeInsets(fraction);
                    final float degree, x, y;
                    if (rotationDelta == ROTATION_90) {
                        degree = 90 * fraction;
@@ -515,11 +524,21 @@ public class PipAnimationController {
                        x = fraction * (end.left - start.left) + start.left;
                        y = fraction * (end.bottom - start.top) + start.top;
                    }
                    getSurfaceTransactionHelper().rotateAndScaleWithCrop(tx, leash, bounds,
                            rotatedEndRect, degree, x, y);
                    getSurfaceTransactionHelper().rotateAndScaleWithCrop(tx, leash,
                            initialContainerRect, bounds, insets, degree, x, y, isOutPipDirection,
                            rotationDelta == ROTATION_270 /* clockwise */);
                    tx.apply();
                }

                private Rect computeInsets(float fraction) {
                    if (sourceHintRectInsets == null) {
                        return zeroInsets;
                    }
                    final Rect startRect = isOutPipDirection ? sourceHintRectInsets : zeroInsets;
                    final Rect endRect = isOutPipDirection ? zeroInsets : sourceHintRectInsets;
                    return mInsetsEvaluator.evaluate(fraction, startRect, endRect);
                }

                @Override
                void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
                    getSurfaceTransactionHelper()
+30 −12
Original line number Diff line number Diff line
@@ -137,23 +137,41 @@ public class PipSurfaceTransactionHelper {
     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
     */
    public PipSurfaceTransactionHelper rotateAndScaleWithCrop(SurfaceControl.Transaction tx,
            SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees,
            float positionX, float positionY) {
            SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets,
            float degrees, float positionX, float positionY, boolean isExpanding,
            boolean clockwise) {
        mTmpDestinationRect.set(sourceBounds);
        final int dw = destinationBounds.width();
        final int dh = destinationBounds.height();
        mTmpDestinationRect.inset(insets);
        final int srcW = mTmpDestinationRect.width();
        final int srcH = mTmpDestinationRect.height();
        final int destW = destinationBounds.width();
        final int destH = destinationBounds.height();
        // Scale by the short side so there won't be empty area if the aspect ratio of source and
        // destination are different.
        final float scale = dw <= dh
                ? (float) sourceBounds.width() / dw
                : (float) sourceBounds.height() / dh;
        final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
        final Rect crop = mTmpDestinationRect;
        crop.set(0, 0, destW, destH);
        // Inverse scale for crop to fit in screen coordinates.
        mTmpDestinationRect.scale(1 / scale);
        mTmpTransform.setRotate(degrees);
        mTmpTransform.postScale(scale, scale);
        crop.scale(1 / scale);
        crop.offset(insets.left, insets.top);
        if (isExpanding) {
            // Expand bounds (shrink insets) in source orientation.
            positionX -= insets.left * scale;
            positionY -= insets.top * scale;
        } else {
            // Shrink bounds (expand insets) in destination orientation.
            if (clockwise) {
                positionX -= insets.top * scale;
                positionY -= insets.left * scale;
            } else {
                positionX += insets.top * scale;
                positionY += insets.left * scale;
            }
        }
        mTmpTransform.setScale(scale, scale);
        mTmpTransform.postRotate(degrees);
        mTmpTransform.postTranslate(positionX, positionY);
        tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
                .setWindowCrop(leash, mTmpDestinationRect.width(), mTmpDestinationRect.height());
        tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setWindowCrop(leash, crop);
        return this;
    }

+190 −43

File changed.

Preview size limit exceeded, changes collapsed.

+11 −7
Original line number Diff line number Diff line
@@ -114,13 +114,17 @@ public class PipController implements PipTransitionController.PipTransitionCallb
     */
    private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
            int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
        if (!mPipTaskOrganizer.isInPip()
                || mPipBoundsState.getDisplayLayout().rotation() == toRotation
                || mPipTaskOrganizer.isDeferringEnterPipAnimation()
                || mPipTaskOrganizer.isEntryScheduled()) {
            // Skip if the same rotation has been set or we aren't in PIP or haven't actually
            // entered PIP yet. We still need to update the display layout in the bounds handler
            // in this case.
        if (mPipBoundsState.getDisplayLayout().rotation() == toRotation) {
            // The same rotation may have been set by auto PiP-able or fixed rotation. So notify
            // the change with fromRotation=false to apply the rotated destination bounds from
            // PipTaskOrganizer#onMovementBoundsChanged.
            updateMovementBounds(null, false /* fromRotation */,
                    false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
            return;
        }
        if (!mPipTaskOrganizer.isInPip() || mPipTaskOrganizer.isEntryScheduled()) {
            // Update display layout and bounds handler if we aren't in PIP or haven't actually
            // entered PIP yet.
            onDisplayRotationChangedNotInPip(mContext, toRotation);
            // do not forget to update the movement bounds as well.
            updateMovementBounds(mPipBoundsState.getNormalBounds(), true /* fromRotation */,
Loading