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 d82946269ee80..fe018115408a8 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 @@ -54,6 +54,7 @@ public class PipAnimationController { public static final int TRANSITION_DIRECTION_LEAVE_PIP = 3; public static final int TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN = 4; public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5; + public static final int TRANSITION_DIRECTION_SNAP_AFTER_RESIZE = 6; @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = { TRANSITION_DIRECTION_NONE, @@ -61,7 +62,8 @@ public class PipAnimationController { TRANSITION_DIRECTION_TO_PIP, TRANSITION_DIRECTION_LEAVE_PIP, TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN, - TRANSITION_DIRECTION_REMOVE_STACK + TRANSITION_DIRECTION_REMOVE_STACK, + TRANSITION_DIRECTION_SNAP_AFTER_RESIZE }) @Retention(RetentionPolicy.SOURCE) public @interface TransitionDirection {} @@ -109,13 +111,27 @@ public class PipAnimationController { } @SuppressWarnings("unchecked") + /** + * 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 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 + * to the correct snap fraction region), then provide the base bounds, which is current PiP + * leash bounds before transformation/any animation. This is so when we try to construct + * the different transformation matrices for the animation, we are constructing this based off + * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed. + */ @VisibleForTesting - public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds, - Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction) { + public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect baseBounds, + Rect startBounds, Rect endBounds, Rect sourceHintRect, + @PipAnimationController.TransitionDirection int direction) { if (mCurrentAnimator == null) { mCurrentAnimator = setupPipTransitionAnimator( - PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect, - direction)); + PipTransitionAnimator.ofBounds(leash, startBounds, startBounds, endBounds, + sourceHintRect, direction)); } 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 @@ -130,8 +146,8 @@ public class PipAnimationController { } else { mCurrentAnimator.cancel(); mCurrentAnimator = setupPipTransitionAnimator( - PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect, - direction)); + PipTransitionAnimator.ofBounds(leash, baseBounds, startBounds, endBounds, + sourceHintRect, direction)); } return mCurrentAnimator; } @@ -180,6 +196,7 @@ public class PipAnimationController { private final @AnimationType int mAnimationType; private final Rect mDestinationBounds = new Rect(); + private T mBaseValue; protected T mCurrentValue; protected T mStartValue; private T mEndValue; @@ -190,10 +207,11 @@ public class PipAnimationController { private @TransitionDirection int mTransitionDirection; private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType, - Rect destinationBounds, T startValue, T endValue) { + Rect destinationBounds, T baseValue, T startValue, T endValue) { mLeash = leash; mAnimationType = animationType; mDestinationBounds.set(destinationBounds); + mBaseValue = baseValue; mStartValue = startValue; mEndValue = endValue; addListener(this); @@ -263,6 +281,10 @@ public class PipAnimationController { return mStartValue; } + T getBaseValue() { + return mBaseValue; + } + @VisibleForTesting public T getEndValue() { return mEndValue; @@ -334,7 +356,7 @@ public class PipAnimationController { static PipTransitionAnimator ofAlpha(SurfaceControl leash, Rect destinationBounds, float startValue, float endValue) { return new PipTransitionAnimator(leash, ANIM_TYPE_ALPHA, - destinationBounds, startValue, endValue) { + destinationBounds, startValue, startValue, endValue) { @Override void applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction) { @@ -367,7 +389,7 @@ public class PipAnimationController { } static PipTransitionAnimator ofBounds(SurfaceControl leash, - Rect startValue, Rect endValue, Rect sourceHintRect, + Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction) { // Just for simplicity we'll interpolate between the source rect hint insets and empty // insets to calculate the window crop @@ -375,7 +397,7 @@ public class PipAnimationController { if (isOutPipDirection(direction)) { initialSourceValue = new Rect(endValue); } else { - initialSourceValue = new Rect(startValue); + initialSourceValue = new Rect(baseValue); } final Rect sourceHintRectInsets; @@ -391,22 +413,24 @@ public class PipAnimationController { // construct new Rect instances in case they are recycled return new PipTransitionAnimator(leash, ANIM_TYPE_BOUNDS, - endValue, new Rect(startValue), new Rect(endValue)) { + endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) { private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); @Override void applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction) { + final Rect base = getBaseValue(); final Rect start = getStartValue(); final Rect end = getEndValue(); Rect bounds = mRectEvaluator.evaluate(fraction, start, end); setCurrentValue(bounds); if (inScaleTransition() || sourceHintRect == null) { + if (isOutPipDirection(direction)) { getSurfaceTransactionHelper().scale(tx, leash, end, bounds); } else { - getSurfaceTransactionHelper().scale(tx, leash, start, bounds); + getSurfaceTransactionHelper().scale(tx, leash, base, bounds); } } else { final Rect insets; 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 98ed822a387f2..167b9f9975f38 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 @@ -30,6 +30,7 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_NONE; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SNAP_AFTER_RESIZE; 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; @@ -814,6 +815,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback); } + /** + * Animates resizing of the pinned stack given the duration and start bounds. + * This is used when the starting bounds is not the current PiP bounds. + */ + public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration, + Consumer updateBoundsCallback) { + if (mShouldDeferEnteringPip) { + Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred"); + return; + } + scheduleAnimateResizePip(fromBounds, toBounds, null /* sourceHintRect */, + TRANSITION_DIRECTION_SNAP_AFTER_RESIZE, duration, updateBoundsCallback); + } + private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, int durationMs, Consumer updateBoundsCallback) { @@ -1073,8 +1088,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Log.w(TAG, "Abort animation, invalid leash"); return; } + Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE + ? mPipBoundsState.getBounds() : currentBounds; mPipAnimationController - .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect, direction) + .getAnimator(mLeash, baseBounds, currentBounds, destinationBounds, sourceHintRect, + direction) .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 new file mode 100644 index 0000000000000..28cbe35745a93 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 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.phone; + +import android.graphics.Point; +import android.graphics.Rect; + +/** + * Helper class to calculate the new size given two-fingers pinch to resize. + */ +public class PipPinchResizingAlgorithm { + private static final Rect TMP_RECT = new Rect(); + /** + * Given inputs and requirements and current PiP bounds, return the new size. + * + * @param x0 x-coordinate of the primary input. + * @param y0 y-coordinate of the primary input. + * @param x1 x-coordinate of the secondary input. + * @param y1 y-coordinate of the secondary input. + * @param downx0 x-coordinate of the original down point of the primary input. + * @param downy0 y-coordinate of the original down ponit of the primary input. + * @param downx1 x-coordinate of the original down point of the secondary input. + * @param downy1 y-coordinate of the original down point of the secondary input. + * @param currentPipBounds current PiP bounds. + * @param minVisibleWidth minimum visible width. + * @param minVisibleHeight minimum visible height. + * @param maxSize max size. + * @return The new resized PiP bounds, sharing the same center. + */ + public static Rect pinchResize(float x0, float y0, float x1, float y1, + float downx0, float downy0, float downx1, float downy1, Rect currentPipBounds, + int minVisibleWidth, int minVisibleHeight, Point maxSize) { + + int width = currentPipBounds.width(); + int height = currentPipBounds.height(); + int left = currentPipBounds.left; + int top = currentPipBounds.top; + int right = currentPipBounds.right; + int bottom = currentPipBounds.bottom; + 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)); + + width = Math.max(minVisibleWidth, Math.min(width + widthDelta, maxSize.x)); + height = Math.max(minVisibleHeight, Math.min(height + heightDelta, maxSize.y)); + + // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major + // drag axis. What ever is producing the bigger rectangle will be chosen. + int width1; + int width2; + int height1; + int height2; + if (aspect > 1.0f) { + // 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); + 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))); + } + // 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); + 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))); + } + } 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); + 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))); + } + // 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); + 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))); + } + } + + // Use the bigger of the two rectangles if the major change was positive, otherwise + // do the opposite. + final boolean grows = width > (right - left) || height > (bottom - top); + if (grows == (width1 * height1 > width2 * height2)) { + width = width1; + height = height1; + } else { + width = width2; + height = height2; + } + + TMP_RECT.set(currentPipBounds.centerX() - width / 2, + currentPipBounds.centerY() - height / 2, + currentPipBounds.centerX() + width / 2, + currentPipBounds.centerY() + height / 2); + 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 bb545bdda922d..88a11689b90ba 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 @@ -39,7 +39,6 @@ import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputMonitor; import android.view.MotionEvent; -import android.view.ScaleGestureDetector; import android.view.ViewConfiguration; import androidx.annotation.VisibleForTesting; @@ -62,19 +61,21 @@ import java.util.function.Function; public class PipResizeGestureHandler { private static final String TAG = "PipResizeGestureHandler"; - private static final float PINCH_THRESHOLD = 0.05f; - private static final float STARTING_SCALE_FACTOR = 1.0f; + private static final int PINCH_RESIZE_SNAP_DURATION = 250; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final PipMotionHelper mMotionHelper; private final PipBoundsState mPipBoundsState; + private final PipTaskOrganizer mPipTaskOrganizer; + private final PhonePipMenuController mPhonePipMenuController; + private final PipUiEventLogger mPipUiEventLogger; private final int mDisplayId; private final Executor mMainExecutor; - private final ScaleGestureDetector mScaleGestureDetector; private final Region mTmpRegion = new Region(); private final PointF mDownPoint = new PointF(); + private final PointF mDownSecondaryPoint = new PointF(); private final Point mMaxSize = new Point(); private final Point mMinSize = new Point(); private final Rect mLastResizeBounds = new Rect(); @@ -88,6 +89,7 @@ public class PipResizeGestureHandler { private final Rect mDisplayBounds = new Rect(); private final Function mMovementBoundsSupplier; private final Runnable mUpdateMovementBoundsRunnable; + private final Handler mHandler; private int mDelta; private float mTouchSlop; @@ -96,15 +98,17 @@ public class PipResizeGestureHandler { private boolean mIsEnabled; private boolean mEnablePinchResize; private boolean mIsSysUiStateValid; + // For drag-resize private boolean mThresholdCrossed; + // For pinch-resize + private boolean mThresholdCrossed0; + private boolean mThresholdCrossed1; private boolean mUsingPinchToZoom = false; - private float mScaleFactor = STARTING_SCALE_FACTOR; + int mFirstIndex = -1; + int mSecondIndex = -1; private InputMonitor mInputMonitor; private InputEventReceiver mInputEventReceiver; - private PipTaskOrganizer mPipTaskOrganizer; - private PhonePipMenuController mPhonePipMenuController; - private PipUiEventLogger mPipUiEventLogger; private int mCtrlType; @@ -124,66 +128,11 @@ public class PipResizeGestureHandler { mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; + mHandler = new Handler(Looper.getMainLooper()); context.getDisplay().getRealSize(mMaxSize); reloadResources(); - mScaleGestureDetector = new ScaleGestureDetector(context, - new ScaleGestureDetector.OnScaleGestureListener() { - @Override - public boolean onScale(ScaleGestureDetector detector) { - mScaleFactor *= detector.getScaleFactor(); - - if (!mThresholdCrossed - && (mScaleFactor > (STARTING_SCALE_FACTOR + PINCH_THRESHOLD) - || mScaleFactor < (STARTING_SCALE_FACTOR - PINCH_THRESHOLD))) { - mThresholdCrossed = true; - mInputMonitor.pilferPointers(); - } - if (mThresholdCrossed) { - int height = Math.min(mMaxSize.y, Math.max(mMinSize.y, - (int) (mScaleFactor * mLastDownBounds.height()))); - int width = Math.min(mMaxSize.x, Math.max(mMinSize.x, - (int) (mScaleFactor * mLastDownBounds.width()))); - int top, bottom, left, right; - - if ((mCtrlType & CTRL_TOP) != 0) { - top = mLastDownBounds.bottom - height; - bottom = mLastDownBounds.bottom; - } else { - top = mLastDownBounds.top; - bottom = mLastDownBounds.top + height; - } - - if ((mCtrlType & CTRL_LEFT) != 0) { - left = mLastDownBounds.right - width; - right = mLastDownBounds.right; - } else { - left = mLastDownBounds.left; - right = mLastDownBounds.left + width; - } - - mLastResizeBounds.set(left, top, right, bottom); - mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, - mLastResizeBounds, - null); - } - return true; - } - - @Override - public boolean onScaleBegin(ScaleGestureDetector detector) { - setCtrlTypeForPinchToZoom(); - return true; - } - - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - mScaleFactor = STARTING_SCALE_FACTOR; - finishResize(); - } - }); - mEnablePinchResize = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, PIP_PINCH_RESIZE, @@ -274,13 +223,20 @@ public class PipResizeGestureHandler { if (ev instanceof MotionEvent) { if (mUsingPinchToZoom) { - mScaleGestureDetector.onTouchEvent((MotionEvent) ev); + onPinchResize((MotionEvent) ev); } else { onDragCornerResize((MotionEvent) ev); } } } + /** + * Checks if there is currently an on-going gesture, either drag-resize or pinch-resize. + */ + public boolean hasOngoingGesture() { + return mCtrlType != CTRL_NONE || mUsingPinchToZoom; + } + /** * Check whether the current x,y coordinate is within the region in which drag-resize should * start. @@ -295,7 +251,7 @@ public class PipResizeGestureHandler { * |_|_|_________|_|_| * |_|_| |_|_| */ - public boolean isWithinTouchRegion(int x, int y) { + public boolean isWithinDragResizeRegion(int x, int y) { final Rect currentPipBounds = mPipBoundsState.getBounds(); if (currentPipBounds == null) { return false; @@ -327,15 +283,14 @@ public class PipResizeGestureHandler { if (isInValidSysUiState()) { switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: - // Always pass the DOWN event to the ScaleGestureDetector - mScaleGestureDetector.onTouchEvent(ev); - if (isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY())) { + if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) { return true; } break; case MotionEvent.ACTION_POINTER_DOWN: if (mEnablePinchResize && ev.getPointerCount() == 2) { + onPinchResize(ev); mUsingPinchToZoom = true; return true; } @@ -348,33 +303,11 @@ public class PipResizeGestureHandler { return false; } - private void setCtrlTypeForPinchToZoom() { - final Rect currentPipBounds = mPipBoundsState.getBounds(); - mLastDownBounds.set(mPipBoundsState.getBounds()); - - Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds); - mDisplayBounds.set(movementBounds.left, - movementBounds.top, - movementBounds.right + currentPipBounds.width(), - movementBounds.bottom + currentPipBounds.height()); - - if (currentPipBounds.left == mDisplayBounds.left) { - mCtrlType |= CTRL_RIGHT; - } else { - mCtrlType |= CTRL_LEFT; - } - - if (currentPipBounds.top > mDisplayBounds.top + mDisplayBounds.height()) { - mCtrlType |= CTRL_TOP; - } else { - mCtrlType |= CTRL_BOTTOM; - } - } - private void setCtrlType(int x, int y) { final Rect currentPipBounds = mPipBoundsState.getBounds(); Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds); + mDisplayBounds.set(movementBounds.left, movementBounds.top, movementBounds.right + currentPipBounds.width(), @@ -408,6 +341,78 @@ public class PipResizeGestureHandler { return mIsSysUiStateValid; } + private void onPinchResize(MotionEvent ev) { + int action = ev.getActionMasked(); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + mFirstIndex = -1; + mSecondIndex = -1; + finishResize(); + } + + if (ev.getPointerCount() != 2) { + return; + } + + if (action == MotionEvent.ACTION_POINTER_DOWN) { + 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()); + } + } + + if (action == MotionEvent.ACTION_MOVE) { + if (mFirstIndex == -1 || mSecondIndex == -1) { + return; + } + + float x0 = ev.getRawX(mFirstIndex); + float y0 = ev.getRawY(mFirstIndex); + float x1 = ev.getRawX(mSecondIndex); + float y1 = ev.getRawY(mSecondIndex); + + double hypot0 = Math.hypot(x0 - mDownPoint.x, y0 - mDownPoint.y); + double hypot1 = Math.hypot(x1 - mDownSecondaryPoint.x, y1 - mDownSecondaryPoint.y); + // Capture inputs + if (hypot0 > mTouchSlop && !mThresholdCrossed0) { + mInputMonitor.pilferPointers(); + mThresholdCrossed0 = true; + // Reset the down to begin resizing from this point + mDownPoint.set(x0, y0); + } + if (hypot1 > mTouchSlop && !mThresholdCrossed1) { + mInputMonitor.pilferPointers(); + mThresholdCrossed1 = true; + // Reset the down to begin resizing from this point + mDownSecondaryPoint.set(x1, y1); + } + if (mThresholdCrossed0 || mThresholdCrossed1) { + if (mPhonePipMenuController.isMenuVisible()) { + mPhonePipMenuController.hideMenu(); + } + + x0 = mThresholdCrossed0 ? x0 : mDownPoint.x; + y0 = mThresholdCrossed0 ? y0 : mDownPoint.y; + x1 = mThresholdCrossed1 ? x1 : mDownSecondaryPoint.x; + y1 = mThresholdCrossed1 ? y1 : mDownSecondaryPoint.y; + + final Rect currentPipBounds = mPipBoundsState.getBounds(); + mLastResizeBounds.set(PipPinchResizingAlgorithm.pinchResize(x0, y0, x1, y1, + mDownPoint.x, mDownPoint.y, mDownSecondaryPoint.x, mDownSecondaryPoint.y, + currentPipBounds, mMinSize.x, mMinSize.y, mMaxSize)); + + mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, + null); + } + } + } + private void onDragCornerResize(MotionEvent ev) { int action = ev.getActionMasked(); float x = ev.getX(); @@ -415,7 +420,7 @@ public class PipResizeGestureHandler { if (action == MotionEvent.ACTION_DOWN) { final Rect currentPipBounds = mPipBoundsState.getBounds(); mLastResizeBounds.setEmpty(); - mAllowGesture = isInValidSysUiState() && isWithinTouchRegion((int) x, (int) y); + mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y); if (mAllowGesture) { setCtrlType((int) x, (int) y); mDownPoint.set(x, y); @@ -468,15 +473,30 @@ public class PipResizeGestureHandler { private void finishResize() { if (!mLastResizeBounds.isEmpty()) { - mUserResizeBounds.set(mLastResizeBounds); - mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, - (Rect bounds) -> { - new Handler(Looper.getMainLooper()).post(() -> { - mMotionHelper.synchronizePinnedStackBounds(); - mUpdateMovementBoundsRunnable.run(); - resetState(); + final Runnable callback = () -> { + mUserResizeBounds.set(mLastResizeBounds); + mMotionHelper.synchronizePinnedStackBounds(); + mUpdateMovementBoundsRunnable.run(); + resetState(); + }; + + // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped + // position correctly. Drag-resize does not need to move, so just finalize resize. + if (mUsingPinchToZoom) { + final Rect startBounds = new Rect(mLastResizeBounds); + mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, + mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds())); + mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, + PINCH_RESIZE_SNAP_DURATION, + (Rect rect) -> { + mHandler.post(callback); }); - }); + } else { + mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, + (Rect bounds) -> { + mHandler.post(callback); + }); + } mPipUiEventLogger.log( PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE); } else { 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 b7cfad9030f7b..9281f58f522f3 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 @@ -470,6 +470,11 @@ public class PipTouchHandler { return true; } + if (mPipResizeGestureHandler.hasOngoingGesture()) { + mPipDismissTargetHandler.hideDismissTargetMaybe(); + return true; + } + if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting()) && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) { // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event 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 55e7a354f4cd7..7f280cd124d21 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 @@ -79,7 +79,8 @@ public class PipAnimationControllerTest extends ShellTestCase { @Test public void getAnimator_withBounds_returnBoundsAnimator() { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, new Rect(), new Rect(), null, TRANSITION_DIRECTION_TO_PIP); + .getAnimator(mLeash, new Rect(), new Rect(), new Rect(), null, + TRANSITION_DIRECTION_TO_PIP); assertEquals("Expect ANIM_TYPE_BOUNDS animation", animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS); @@ -87,16 +88,19 @@ public class PipAnimationControllerTest extends ShellTestCase { @Test public void getAnimator_whenSameTypeRunning_updateExistingAnimator() { + final Rect baseValue = new Rect(0, 0, 100, 100); final Rect startValue = new Rect(0, 0, 100, 100); final Rect endValue1 = new Rect(100, 100, 200, 200); final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP); + .getAnimator(mLeash, baseValue, startValue, endValue1, null, + TRANSITION_DIRECTION_TO_PIP); oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); oldAnimator.start(); final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue2, null, TRANSITION_DIRECTION_TO_PIP); + .getAnimator(mLeash, baseValue, startValue, endValue2, null, + TRANSITION_DIRECTION_TO_PIP); assertEquals("getAnimator with same type returns same animator", oldAnimator, newAnimator); @@ -122,11 +126,13 @@ public class PipAnimationControllerTest extends ShellTestCase { @Test @SuppressWarnings("unchecked") public void pipTransitionAnimator_updateEndValue() { + final Rect baseValue = new Rect(0, 0, 100, 100); final Rect startValue = new Rect(0, 0, 100, 100); final Rect endValue1 = new Rect(100, 100, 200, 200); final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP); + .getAnimator(mLeash, baseValue, startValue, endValue1, null, + TRANSITION_DIRECTION_TO_PIP); animator.updateEndValue(endValue2); @@ -135,10 +141,12 @@ public class PipAnimationControllerTest extends ShellTestCase { @Test public void pipTransitionAnimator_setPipAnimationCallback() { + final Rect baseValue = new Rect(0, 0, 100, 100); final Rect startValue = new Rect(0, 0, 100, 100); final Rect endValue = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue, null, TRANSITION_DIRECTION_TO_PIP); + .getAnimator(mLeash, baseValue, startValue, endValue, null, + TRANSITION_DIRECTION_TO_PIP); animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); animator.setPipAnimationCallback(mPipAnimationCallback);