diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index ea8a5c3050298..c0bc73dcbd473 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -17,4 +17,5 @@ + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 3362d2a1ad349..87f9d5b7c1893 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -116,7 +116,8 @@ public class PipAnimationController { * Construct and return an animator that animates from the {@param startBounds} to the * {@param endBounds} with the given {@param direction}. If {@param direction} is type * {@link ANIM_TYPE_BOUNDS}, then {@param sourceHintRect} will be used to animate - * in a better, more smooth manner. + * in a better, more smooth manner. If the original bound was rotated and a reset needs to + * happen, pass in {@param startingAngle}. * * In the case where one wants to start animation during an intermediate animation (for example, * if the user is currently doing a pinch-resize, and upon letting go now PiP needs to animate @@ -128,11 +129,11 @@ public class PipAnimationController { @VisibleForTesting public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect, - @PipAnimationController.TransitionDirection int direction) { + @PipAnimationController.TransitionDirection int direction, float startingAngle) { if (mCurrentAnimator == null) { mCurrentAnimator = setupPipTransitionAnimator( PipTransitionAnimator.ofBounds(leash, startBounds, startBounds, endBounds, - sourceHintRect, direction)); + 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 @@ -148,7 +149,7 @@ public class PipAnimationController { mCurrentAnimator.cancel(); mCurrentAnimator = setupPipTransitionAnimator( PipTransitionAnimator.ofBounds(leash, baseBounds, startBounds, endBounds, - sourceHintRect, direction)); + sourceHintRect, direction, startingAngle)); } return mCurrentAnimator; } @@ -201,6 +202,7 @@ public class PipAnimationController { protected T mCurrentValue; protected T mStartValue; private T mEndValue; + private float mStartingAngle; private PipAnimationCallback mPipAnimationCallback; private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; @@ -208,13 +210,15 @@ public class PipAnimationController { private @TransitionDirection int mTransitionDirection; private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType, - Rect destinationBounds, T baseValue, T startValue, T endValue) { + Rect destinationBounds, T baseValue, T startValue, T endValue, + float startingAngle) { mLeash = leash; mAnimationType = animationType; mDestinationBounds.set(destinationBounds); mBaseValue = baseValue; mStartValue = startValue; mEndValue = endValue; + mStartingAngle = startingAngle; addListener(this); addUpdateListener(this); mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; @@ -363,7 +367,7 @@ public class PipAnimationController { static PipTransitionAnimator ofAlpha(SurfaceControl leash, Rect destinationBounds, float startValue, float endValue) { return new PipTransitionAnimator(leash, ANIM_TYPE_ALPHA, - destinationBounds, startValue, startValue, endValue) { + destinationBounds, startValue, startValue, endValue, 0) { @Override void applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction) { @@ -397,7 +401,7 @@ public class PipAnimationController { static PipTransitionAnimator ofBounds(SurfaceControl leash, Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, - @PipAnimationController.TransitionDirection int direction) { + @PipAnimationController.TransitionDirection int direction, float startingAngle) { // Just for simplicity we'll interpolate between the source rect hint insets and empty // insets to calculate the window crop final Rect initialSourceValue; @@ -420,7 +424,8 @@ public class PipAnimationController { // construct new Rect instances in case they are recycled return new PipTransitionAnimator(leash, ANIM_TYPE_BOUNDS, - endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) { + endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue), + startingAngle) { private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); @@ -431,13 +436,14 @@ public class PipAnimationController { final Rect start = getStartValue(); final Rect end = getEndValue(); Rect bounds = mRectEvaluator.evaluate(fraction, start, end); + float angle = (1.0f - fraction) * startingAngle; setCurrentValue(bounds); if (inScaleTransition() || sourceHintRect == null) { if (isOutPipDirection(direction)) { getSurfaceTransactionHelper().scale(tx, leash, end, bounds); } else { - getSurfaceTransactionHelper().scale(tx, leash, base, bounds); + getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle); } } else { final Rect insets; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java index d0d9b2623ab51..9595b5a3b0a9c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.graphics.Point; import android.graphics.Rect; import android.util.Size; import android.view.DisplayInfo; @@ -59,6 +60,8 @@ public final class PipBoundsState { private final @NonNull Rect mExpandedBounds = new Rect(); private final @NonNull Rect mNormalMovementBounds = new Rect(); private final @NonNull Rect mExpandedMovementBounds = new Rect(); + private final Point mMaxSize = new Point(); + private final Point mMinSize = new Point(); private final @NonNull Context mContext; private float mAspectRatio; private int mStashedState = STASH_TYPE_NONE; @@ -151,6 +154,24 @@ public final class PipBoundsState { mExpandedMovementBounds.set(bounds); } + /** Sets the max possible size for resize. */ + public void setMaxSize(int width, int height) { + mMaxSize.set(width, height); + } + + /** Sets the min possible size for resize. */ + public void setMinSize(int width, int height) { + mMinSize.set(width, height); + } + + public Point getMaxSize() { + return mMaxSize; + } + + public Point getMinSize() { + return mMinSize; + } + /** Returns the expanded movement bounds. */ @NonNull public Rect getExpandedMovementBounds() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index b9a5536de7438..5224bcc7c5392 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -84,9 +84,19 @@ public class PipSurfaceTransactionHelper { */ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds) { + return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */); + } + + /** + * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation. + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, Rect destinationBounds, float degrees) { mTmpSourceRectF.set(sourceBounds); mTmpDestinationRectF.set(destinationBounds); mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); + mTmpTransform.postRotate(degrees); tx.setMatrix(leash, mTmpTransform, mTmpFloat9) .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top); return this; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 801ee2b251847..f43f44b03affd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -195,9 +195,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Rect currentBounds = (Rect) args.arg2; Rect toBounds = (Rect) args.arg3; Rect sourceHintRect = (Rect) args.arg4; + float startingAngle = (float) args.arg5; int duration = args.argi2; animateResizePip(currentBounds, toBounds, sourceHintRect, - args.argi1 /* direction */, duration); + args.argi1 /* direction */, duration, startingAngle); if (updateBoundsCallback != null) { updateBoundsCallback.accept(toBounds); } @@ -227,7 +228,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, case MSG_RESIZE_USER: { Rect startBounds = (Rect) args.arg2; Rect toBounds = (Rect) args.arg3; - userResizePip(startBounds, toBounds); + float degrees = (float) args.arg4; + userResizePip(startBounds, toBounds, degrees); if (updateBoundsCallback != null) { updateBoundsCallback.accept(toBounds); } @@ -427,7 +429,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, final Rect sourceHintRect = getValidSourceHintRect(mPictureInPictureParams, destinationBounds); scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds, - sourceHintRect, direction, animationDurationMs, + 0 /* startingAngle */, sourceHintRect, direction, animationDurationMs, null /* updateBoundsCallback */); mState = State.EXITING_PIP; } @@ -535,8 +537,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipMenuController.attach(mLeash); final Rect sourceHintRect = getValidSourceHintRect(info.pictureInPictureParams, currentBounds); - scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect, - TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, + scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */, + sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, null /* updateBoundsCallback */); mState = State.ENTERING_PIP; } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { @@ -822,8 +824,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred"); return; } - scheduleAnimateResizePip(mPipBoundsState.getBounds(), toBounds, null /* sourceHintRect */, - TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback); + scheduleAnimateResizePip(mPipBoundsState.getBounds(), toBounds, 0 /* startingAngle */, + null /* sourceHintRect */, TRANSITION_DIRECTION_NONE, duration, + updateBoundsCallback); } /** @@ -831,18 +834,23 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * This is used when the starting bounds is not the current PiP bounds. */ public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration, - Consumer updateBoundsCallback) { + float startingAngle, Consumer updateBoundsCallback) { if (mWaitForFixedRotation) { Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred"); return; } - scheduleAnimateResizePip(fromBounds, toBounds, null /* sourceHintRect */, + scheduleAnimateResizePip(fromBounds, toBounds, startingAngle, null /* sourceHintRect */, TRANSITION_DIRECTION_SNAP_AFTER_RESIZE, duration, updateBoundsCallback); } + /** + * Animates resizing of the pinned stack given the duration and start bounds. + * This always animates the angle to zero from the starting angle. + */ private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds, - Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, - int durationMs, Consumer updateBoundsCallback) { + float startingAngle, Rect sourceHintRect, + @PipAnimationController.TransitionDirection int direction, int durationMs, + Consumer updateBoundsCallback) { if (!mState.isInPip()) { // TODO: tend to use shouldBlockResizeRequest here as well but need to consider // the fact that when in exitPip, scheduleAnimateResizePip is executed in the window @@ -855,6 +863,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, args.arg2 = currentBounds; args.arg3 = destinationBounds; args.arg4 = sourceHintRect; + args.arg5 = startingAngle; args.argi1 = direction; args.argi2 = durationMs; mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args)); @@ -871,16 +880,26 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_IMMEDIATE, args)); } + /** + * Directly perform manipulation/resize on the leash, along with rotation. This will not perform + * any {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called. + */ + public void scheduleUserResizePip(Rect startBounds, Rect toBounds, + Consumer updateBoundsCallback) { + scheduleUserResizePip(startBounds, toBounds, 0 /* degrees */, updateBoundsCallback); + } + /** * Directly perform a scaled matrix transformation on the leash. This will not perform any * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called. */ - public void scheduleUserResizePip(Rect startBounds, Rect toBounds, + public void scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees, Consumer updateBoundsCallback) { SomeArgs args = SomeArgs.obtain(); args.arg1 = updateBoundsCallback; args.arg2 = startBounds; args.arg3 = toBounds; + args.arg4 = degrees; mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_USER, args)); } @@ -959,7 +978,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, final Rect destinationBounds = new Rect(originalBounds); destinationBounds.offset(xOffset, yOffset); animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */, - TRANSITION_DIRECTION_SAME, durationMs); + TRANSITION_DIRECTION_SAME, durationMs, 0); } private void resizePip(Rect destinationBounds) { @@ -985,7 +1004,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } } - private void userResizePip(Rect startBounds, Rect destinationBounds) { + private void userResizePip(Rect startBounds, Rect destinationBounds, float degrees) { if (Looper.myLooper() != mUpdateHandler.getLooper()) { throw new RuntimeException("Callers should call scheduleUserResizePip() instead of " + "this directly"); @@ -1002,7 +1021,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); - mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds); + mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds, degrees); if (mPipMenuController.isMenuVisible()) { runOnMainHandler(() -> mPipMenuController.movePipMenu(mLeash, tx, destinationBounds)); @@ -1089,7 +1108,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect, - @PipAnimationController.TransitionDirection int direction, int durationMs) { + @PipAnimationController.TransitionDirection int direction, int durationMs, + float startingAngle) { if (Looper.myLooper() != mUpdateHandler.getLooper()) { throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of " + "this directly"); @@ -1103,7 +1123,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, ? mPipBoundsState.getBounds() : currentBounds; mPipAnimationController .getAnimator(mLeash, baseBounds, currentBounds, destinationBounds, sourceHintRect, - direction) + direction, startingAngle) .setTransitionDirection(direction) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(durationMs) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java index 28cbe35745a93..805123f81d813 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java @@ -53,6 +53,8 @@ public class PipPinchResizingAlgorithm { final float aspect = (float) width / (float) height; final int widthDelta = Math.round(Math.abs(x0 - x1) - Math.abs(downx0 - downx1)); final int heightDelta = Math.round(Math.abs(y0 - y1) - Math.abs(downy0 - downy1)); + final int dx = (int) ((x0 - downx0 + x1 - downx1) / 2); + final int dy = (int) ((y0 - downy0 + y1 - downy1) / 2); width = Math.max(minVisibleWidth, Math.min(width + widthDelta, maxSize.x)); height = Math.max(minVisibleHeight, Math.min(height + heightDelta, maxSize.y)); @@ -85,21 +87,21 @@ public class PipPinchResizingAlgorithm { } else { // Assuming that the width is our target we calculate the height. width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width)); - height1 = Math.round((float) width1 * aspect); + height1 = Math.round((float) width1 / aspect); if (height1 < minVisibleHeight) { // If the resulting height is too small we adjust to the minimal size. height1 = minVisibleHeight; width1 = Math.max(minVisibleWidth, - Math.min(maxSize.x, Math.round((float) height1 / aspect))); + Math.min(maxSize.x, Math.round((float) height1 * aspect))); } // Assuming that the height is our target we calculate the width. height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height)); - width2 = Math.round((float) height2 / aspect); + width2 = Math.round((float) height2 * aspect); if (width2 < minVisibleWidth) { // If the resulting width is too small we adjust to the minimal size. width2 = minVisibleWidth; height2 = Math.max(minVisibleHeight, - Math.min(maxSize.y, Math.round((float) width2 * aspect))); + Math.min(maxSize.y, Math.round((float) width2 / aspect))); } } @@ -118,6 +120,7 @@ public class PipPinchResizingAlgorithm { currentPipBounds.centerY() - height / 2, currentPipBounds.centerX() + width / 2, currentPipBounds.centerY() + height / 2); + TMP_RECT.offset(dx, dy); return TMP_RECT; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 02f6231c6ecf1..1f6df3aa7dc71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -62,6 +62,7 @@ public class PipResizeGestureHandler { private static final String TAG = "PipResizeGestureHandler"; private static final int PINCH_RESIZE_SNAP_DURATION = 250; + private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; @@ -359,12 +360,12 @@ public class PipResizeGestureHandler { if (mFirstIndex == -1 && mSecondIndex == -1) { mFirstIndex = 0; mSecondIndex = 1; - mLastResizeBounds.setEmpty(); mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex)); mDownSecondaryPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex)); - mLastResizeBounds.setEmpty(); + mLastDownBounds.set(mPipBoundsState.getBounds()); + mLastResizeBounds.set(mLastDownBounds); } } @@ -403,18 +404,63 @@ public class PipResizeGestureHandler { x1 = mThresholdCrossed1 ? x1 : mDownSecondaryPoint.x; y1 = mThresholdCrossed1 ? y1 : mDownSecondaryPoint.y; - final Rect currentPipBounds = mPipBoundsState.getBounds(); + final Rect originalPipBounds = mPipBoundsState.getBounds(); + int focusX = (int) originalPipBounds.centerX(); + int focusY = (int) originalPipBounds.centerY(); + + float down0X = mDownPoint.x; + float down0Y = mDownPoint.y; + float down1X = mDownSecondaryPoint.x; + float down1Y = mDownSecondaryPoint.y; + + // Top right + Bottom left pinch to zoom. + if ((down0X > focusX && down0Y < focusY && down1X < focusX && down1Y > focusY) + || (down1X > focusX && down1Y < focusY + && down0X < focusX && down0Y > focusY)) { + mAngle = calculateRotationAngle(mLastResizeBounds.centerX(), + mLastResizeBounds.centerY(), x0, y0, x1, y1, true); + } else if ((down0X < focusX && down0Y < focusY + && down1X > focusX && down1Y > focusY) + || (down1X < focusX && down1Y < focusY + && down0X > focusX && down0Y > focusY)) { + mAngle = calculateRotationAngle(mLastResizeBounds.centerX(), + mLastResizeBounds.centerY(), x0, y0, x1, y1, false); + } + mLastResizeBounds.set(PipPinchResizingAlgorithm.pinchResize(x0, y0, x1, y1, mDownPoint.x, mDownPoint.y, mDownSecondaryPoint.x, mDownSecondaryPoint.y, - currentPipBounds, mMinSize.x, mMinSize.y, mMaxSize)); + originalPipBounds, mMinSize.x, mMinSize.y, mMaxSize)); mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, - null); + (float) -mAngle, null); mPipBoundsState.setHasUserResizedPip(true); } } } + private float mAngle = 0; + + private float calculateRotationAngle(int focusX, int focusY, float x0, float y0, + float x1, float y1, boolean positive) { + + // The base angle is the angle formed by taking the angle between the center horizontal + // and one of the corners. + double baseAngle = Math.toDegrees(Math.atan2(Math.abs(mLastResizeBounds.top - focusY), + Math.abs(mLastResizeBounds.right - focusX))); + double angle0 = mThresholdCrossed0 + ? Math.toDegrees(Math.atan2(Math.abs(y0 - focusY), Math.abs(x0 - focusX))) + : baseAngle; + double angle1 = mThresholdCrossed1 + ? Math.toDegrees(Math.atan2(Math.abs(y1 - focusY), Math.abs(x1 - focusX))) + : baseAngle; + + // Calculate the percentage difference of [0, 90] compare to the base angle. + double diff0 = (Math.max(0, Math.min(angle0, 90)) - baseAngle) / 90; + double diff1 = (Math.max(0, Math.min(angle1, 90)) - baseAngle) / 90; + + return (float) (diff0 + diff1) / 2 * PINCH_RESIZE_MAX_ANGLE_ROTATION * (positive ? 1 : -1); + } + private void onDragCornerResize(MotionEvent ev) { int action = ev.getActionMasked(); float x = ev.getX(); @@ -490,7 +536,7 @@ public class PipResizeGestureHandler { mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds())); mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, - PINCH_RESIZE_SNAP_DURATION, + PINCH_RESIZE_SNAP_DURATION, mAngle, (Rect rect) -> { mHandler.post(callback); }); @@ -509,6 +555,7 @@ public class PipResizeGestureHandler { private void resetState() { mCtrlType = CTRL_NONE; + mAngle = 0; mUsingPinchToZoom = false; mAllowGesture = false; mThresholdCrossed = false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 6b6bf5e4a7761..75d674e4ceead 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -63,6 +63,7 @@ public class PipTouchHandler { private static final String TAG = "PipTouchHandler"; private static final float STASH_MINIMUM_VELOCITY_X = 3000.f; + private static final float MINIMUM_SIZE_PERCENT = 0.4f; // Allow PIP to resize to a slightly bigger state upon touch private final boolean mEnableResize; @@ -359,9 +360,13 @@ public class PipTouchHandler { mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds, bottomOffset); - mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height()); - mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(), - mPipBoundsState.getExpandedBounds().height()); + if (mPipResizeGestureHandler.isUsingPinchToZoom()) { + updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio); + } else { + mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height()); + mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(), + mPipBoundsState.getExpandedBounds().height()); + } // The extra offset does not really affect the movement bounds, but are applied based on the // current state (ime showing, or shelf offset) when we need to actually shift @@ -430,6 +435,30 @@ public class PipTouchHandler { } } + private void updatePinchResizeSizeConstraints(Rect insetBounds, Rect normalBounds, + float aspectRatio) { + final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(), + mPipBoundsState.getDisplayBounds().height()); + final int totalPadding = insetBounds.left * 2; + final int minWidth, minHeight, maxWidth, maxHeight; + if (aspectRatio > 1f) { + minWidth = (int) Math.min(normalBounds.width(), shorterLength * MINIMUM_SIZE_PERCENT); + minHeight = (int) (minWidth / aspectRatio); + maxWidth = (int) Math.max(normalBounds.width(), shorterLength - totalPadding); + maxHeight = (int) (maxWidth / aspectRatio); + } else { + minHeight = (int) Math.min(normalBounds.height(), shorterLength * MINIMUM_SIZE_PERCENT); + minWidth = (int) (minHeight * aspectRatio); + maxHeight = (int) Math.max(normalBounds.height(), shorterLength - totalPadding); + maxWidth = (int) (maxHeight * aspectRatio); + } + + mPipResizeGestureHandler.updateMinSize(minWidth, minHeight); + mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight); + mPipBoundsState.setMaxSize(maxWidth, maxHeight); + mPipBoundsState.setMinSize(minWidth, minHeight); + } + /** * TODO Add appropriate description */ @@ -640,13 +669,36 @@ public class PipTouchHandler { } } - private void animateToExpandedState(Runnable callback) { - Rect expandedBounds = new Rect(mPipBoundsState.getExpandedBounds()); - mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds, - mPipBoundsState.getMovementBounds(), mPipBoundsState.getExpandedMovementBounds(), + private void animateToMaximizedState(Runnable callback) { + Rect maxMovementBounds = new Rect(); + Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x, + mPipBoundsState.getMaxSize().y); + mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds, + mIsImeShowing ? mImeHeight : 0); + mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds, + mPipBoundsState.getMovementBounds(), maxMovementBounds, callback); } + private void animateToMinimizedState() { + animateToUnexpandedState(new Rect(0, 0, mPipBoundsState.getMinSize().x, + mPipBoundsState.getMinSize().y)); + } + + private void animateToExpandedState(Runnable callback) { + mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); + final Rect currentBounds = mPipBoundsState.getBounds(); + final Rect expandedBounds = mPipBoundsState.getExpandedBounds(); + Rect finalExpandedBounds = new Rect(expandedBounds.width() > expandedBounds.width() + && expandedBounds.height() > expandedBounds.height() + ? currentBounds : expandedBounds); + Rect restoredMovementBounds = new Rect(); + mPipBoundsAlgorithm.getMovementBounds(finalExpandedBounds, + mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0); + mSavedSnapFraction = mMotionHelper.animateToExpandedState(finalExpandedBounds, + mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback); + } + private void animateToUnexpandedState(Rect restoreBounds) { Rect restoredMovementBounds = new Rect(); mPipBoundsAlgorithm.getMovementBounds(restoreBounds, @@ -789,17 +841,15 @@ public class PipTouchHandler { // If using pinch to zoom, double-tap functions as resizing between max/min size if (mPipResizeGestureHandler.isUsingPinchToZoom()) { final boolean toExpand = mPipBoundsState.getBounds().width() - < mPipBoundsState.getExpandedBounds().width() + < mPipBoundsState.getMaxSize().x && mPipBoundsState.getBounds().height() - < mPipBoundsState.getExpandedBounds().height(); - mPipResizeGestureHandler.setUserResizeBounds(toExpand - ? mPipBoundsState.getExpandedBounds() - : mPipBoundsState.getNormalBounds()); + < mPipBoundsState.getMaxSize().y; if (toExpand) { - animateToExpandedState(null); + animateToMaximizedState(null); } else { - animateToUnexpandedState(mPipBoundsState.getNormalBounds()); + animateToMinimizedState(); } + mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); } else { // Expand to fullscreen if this is a double tap // the PiP should be frozen until the transition ends diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java index d30fa3830cf3d..c565a4cc2e28f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java @@ -80,7 +80,7 @@ public class PipAnimationControllerTest extends ShellTestCase { public void getAnimator_withBounds_returnBoundsAnimator() { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mLeash, new Rect(), new Rect(), new Rect(), null, - TRANSITION_DIRECTION_TO_PIP); + TRANSITION_DIRECTION_TO_PIP, 0); assertEquals("Expect ANIM_TYPE_BOUNDS animation", animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS); @@ -94,13 +94,13 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController .getAnimator(mLeash, baseValue, startValue, endValue1, null, - TRANSITION_DIRECTION_TO_PIP); + TRANSITION_DIRECTION_TO_PIP, 0); oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); oldAnimator.start(); final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController .getAnimator(mLeash, baseValue, startValue, endValue2, null, - TRANSITION_DIRECTION_TO_PIP); + TRANSITION_DIRECTION_TO_PIP, 0); assertEquals("getAnimator with same type returns same animator", oldAnimator, newAnimator); @@ -132,7 +132,7 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mLeash, baseValue, startValue, endValue1, null, - TRANSITION_DIRECTION_TO_PIP); + TRANSITION_DIRECTION_TO_PIP, 0); animator.updateEndValue(endValue2); @@ -146,7 +146,7 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mLeash, baseValue, startValue, endValue, null, - TRANSITION_DIRECTION_TO_PIP); + TRANSITION_DIRECTION_TO_PIP, 0); animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); animator.setPipAnimationCallback(mPipAnimationCallback);