Re-implement pinch-to-resize gesture.
This removes ScaleGestureDetector and instead just do the calculation inside the class itself. As user sets two input points on the PiP surface, this signals the start of the gesture, and as user drags the two fingers, it resizes the PiP to the maximum possible size. After the user releases their fingers, then it auto-offsets/snaps to the right position. Bug: 166478885 Test: Start a pinch-to-resize while feature flag is on Change-Id: I634bd14e97f0cf0cc7bf9c1a1b64a1f12130e0e9
This commit is contained in:
@@ -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<Float> ofAlpha(SurfaceControl leash,
|
||||
Rect destinationBounds, float startValue, float endValue) {
|
||||
return new PipTransitionAnimator<Float>(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<Rect> 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<Rect>(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;
|
||||
|
||||
@@ -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<Rect> 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<Rect> 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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Rect, Rect> 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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user