Merge "Scale down PIP when it's dragged into the dismiss target." into rvc-dev am: 7a1cd3f757

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11886098

Change-Id: Ieb8edfb8f7bd7071649702ee4a42e0201e18b2da
This commit is contained in:
Josh Tsuji
2020-06-18 14:40:34 +00:00
committed by Automerger Merge Worker
5 changed files with 227 additions and 89 deletions

View File

@@ -708,6 +708,12 @@ public class PipTaskOrganizer extends TaskOrganizer implements
Log.w(TAG, "Abort animation, invalid leash");
return;
}
if (startBounds.isEmpty() || destinationBounds.isEmpty()) {
Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
return;
}
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds);
tx.apply();

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
import android.util.Log;
@@ -38,6 +39,9 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject;
import java.io.PrintWriter;
import java.util.function.Consumer;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
/**
* A helper to animate and manipulate the PiP.
*/
@@ -74,9 +78,15 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
new SfVsyncFrameCallbackProvider();
/**
* Bounds that are animated using the physics animator.
* Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP
* using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into
* and expanding out of the magnetic dismiss target.
*
* Once PIP is done being dragged or animated, we set {@link #mBounds} equal to these temporary
* bounds, and call {@link PipTaskOrganizer#scheduleFinishResizePip} to 'officially' move PIP to
* its new bounds.
*/
private final Rect mAnimatedBounds = new Rect();
private final Rect mTemporaryBounds = new Rect();
/** The destination bounds to which PIP is animating. */
private final Rect mAnimatingToBounds = new Rect();
@@ -85,20 +95,20 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private FloatingContentCoordinator mFloatingContentCoordinator;
/** Callback that re-sizes PIP to the animated bounds. */
private final Choreographer.FrameCallback mResizePipVsyncCallback =
l -> resizePipUnchecked(mAnimatedBounds);
private final Choreographer.FrameCallback mResizePipVsyncCallback;
/**
* PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations.
* PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations.
*/
private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
mAnimatedBounds);
private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
mTemporaryBounds);
private MagnetizedObject<Rect> mMagnetizedPip;
/**
* Update listener that resizes the PIP to {@link #mAnimatedBounds}.
* Update listener that resizes the PIP to {@link #mTemporaryBounds}.
*/
final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
(target, values) -> mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback);
private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
/** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
private PhysicsAnimator.FlingConfig mFlingConfigX;
@@ -123,6 +133,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
*/
private boolean mSpringingToTouch = false;
/**
* Whether PIP was released in the dismiss target, and will be animated out and dismissed
* shortly.
*/
private boolean mDismissalPending = false;
/**
* Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
* used to show menu activity when the expand animation is completed.
@@ -155,6 +171,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
mResizePipVsyncCallback = l -> {
if (!mTemporaryBounds.isEmpty()) {
mPipTaskOrganizer.scheduleUserResizePip(
mBounds, mTemporaryBounds, null);
}
};
mResizePipUpdateListener = (target, values) ->
mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback);
}
@NonNull
@@ -186,19 +212,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
}
}
/**
* Synchronizes the current bounds with either the pinned stack, or the ongoing animation. This
* is done to prepare for a touch gesture.
*/
void synchronizePinnedStackBoundsForTouchGesture() {
if (mAnimatingToBounds.isEmpty()) {
// If we're not animating anywhere, sync normally.
synchronizePinnedStackBounds();
} else {
// If we're animating, set the current bounds to the animated bounds. That way, the
// touch gesture will begin at the most recent animated location of the bounds.
mBounds.set(mAnimatedBounds);
}
boolean isAnimating() {
return mTemporaryBoundsPhysicsAnimator.isRunning();
}
/**
@@ -224,32 +239,54 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
// If we are moving PIP directly to the touch event locations, cancel any animations and
// move PIP to the given bounds.
cancelAnimations();
resizePipUnchecked(toBounds);
mBounds.set(toBounds);
if (!isDragging) {
resizePipUnchecked(toBounds);
mBounds.set(toBounds);
} else {
mTemporaryBounds.set(toBounds);
mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds, null);
}
} else {
// If PIP is 'catching up' after being stuck in the dismiss target, update the animation
// to spring towards the new touch location.
mAnimatedBoundsPhysicsAnimator
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig)
.spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
.spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig)
.withEndActions(() -> mSpringingToTouch = false);
.spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig);
startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */,
false /* dismiss */);
}
}
/** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
void setSpringingToTouch(boolean springingToTouch) {
if (springingToTouch) {
mAnimatedBounds.set(mBounds);
}
/** Animates the PIP into the dismiss target, scaling it down. */
void animateIntoDismissTarget(
MagnetizedObject.MagneticTarget target,
float velX, float velY,
boolean flung, Function0<Unit> after) {
final PointF targetCenter = target.getCenterOnScreen();
mSpringingToTouch = springingToTouch;
final float desiredWidth = mBounds.width() / 2;
final float desiredHeight = mBounds.height() / 2;
final float destinationX = targetCenter.x - (desiredWidth / 2f);
final float destinationY = targetCenter.y - (desiredHeight / 2f);
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig)
.spring(FloatProperties.RECT_Y, destinationY, velY, mSpringConfig)
.spring(FloatProperties.RECT_WIDTH, desiredWidth, mSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, desiredHeight, mSpringConfig)
.withEndActions(after);
startBoundsAnimator(destinationX, destinationY, false);
}
void prepareForAnimation() {
mAnimatedBounds.set(mBounds);
/** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
void setSpringingToTouch(boolean springingToTouch) {
mSpringingToTouch = springingToTouch;
}
/**
@@ -308,14 +345,23 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
return mBounds;
}
/**
* Returns the PIP bounds if we're not animating, or the current, temporary animating bounds
* otherwise.
*/
Rect getPossiblyAnimatingBounds() {
return mTemporaryBounds.isEmpty() ? mBounds : mTemporaryBounds;
}
/**
* Flings the PiP to the closest snap target.
*/
void flingToSnapTarget(
float velocityX, float velocityY,
@Nullable Runnable updateAction, @Nullable Runnable endAction) {
mAnimatedBounds.set(mBounds);
mAnimatedBoundsPhysicsAnimator
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig)
.flingThenSpring(
FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig,
true /* flingMustReachMinOrMax */)
@@ -324,13 +370,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
.withEndActions(endAction);
if (updateAction != null) {
mAnimatedBoundsPhysicsAnimator.addUpdateListener(
mTemporaryBoundsPhysicsAnimator.addUpdateListener(
(target, values) -> updateAction.run());
}
final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right;
final float estimatedFlingYEndValue =
PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY);
PhysicsAnimator.estimateFlingEndValue(
mTemporaryBounds.top, velocityY, mFlingConfigY);
startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */,
false /* dismiss */);
@@ -341,8 +388,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
* configuration
*/
void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
mAnimatedBounds.set(mBounds);
mAnimatedBoundsPhysicsAnimator
if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
// Animate from the current bounds if we're not already animating.
mTemporaryBounds.set(mBounds);
}
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_X, bounds.left, springConfig)
.spring(FloatProperties.RECT_Y, bounds.top, springConfig);
startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */,
@@ -353,18 +404,19 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
* Animates the dismissal of the PiP off the edge of the screen.
*/
void animateDismiss() {
mAnimatedBounds.set(mBounds);
// Animate off the bottom of the screen, then dismiss PIP.
mAnimatedBoundsPhysicsAnimator
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_Y,
mBounds.bottom + mBounds.height(),
mMovementBounds.bottom + mBounds.height() * 2,
0,
mSpringConfig)
.withEndActions(this::dismissPip);
startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */,
startBoundsAnimator(
mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */,
true /* dismiss */);
mDismissalPending = false;
}
/**
@@ -415,7 +467,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
* Cancels all existing animations.
*/
private void cancelAnimations() {
mAnimatedBoundsPhysicsAnimator.cancel();
mTemporaryBoundsPhysicsAnimator.cancel();
mAnimatingToBounds.setEmpty();
mSpringingToTouch = false;
}
@@ -449,15 +501,36 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
(int) toY + mBounds.height());
setAnimatingToBounds(mAnimatingToBounds);
mAnimatedBoundsPhysicsAnimator
.withEndActions(() -> {
if (!dismiss) {
mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds);
}
mAnimatingToBounds.setEmpty();
})
.addUpdateListener(mResizePipUpdateListener)
.start();
if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
mTemporaryBoundsPhysicsAnimator
.addUpdateListener(mResizePipUpdateListener)
.withEndActions(this::onBoundsAnimationEnd);
}
mTemporaryBoundsPhysicsAnimator.start();
}
/**
* Notify that PIP was released in the dismiss target and will be animated out and dismissed
* shortly.
*/
void notifyDismissalPending() {
mDismissalPending = true;
}
private void onBoundsAnimationEnd() {
if (!mDismissalPending
&& !mSpringingToTouch
&& !mMagnetizedPip.getObjectStuckToTarget()) {
mBounds.set(mTemporaryBounds);
mPipTaskOrganizer.scheduleFinishResizePip(mBounds);
mTemporaryBounds.setEmpty();
}
mAnimatingToBounds.setEmpty();
mSpringingToTouch = false;
mDismissalPending = false;
}
/**
@@ -503,25 +576,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
* magnetic dismiss target so it can calculate PIP's size and position.
*/
MagnetizedObject<Rect> getMagnetizedPip() {
return new MagnetizedObject<Rect>(
mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
@Override
public float getWidth(@NonNull Rect animatedPipBounds) {
return animatedPipBounds.width();
}
if (mMagnetizedPip == null) {
mMagnetizedPip = new MagnetizedObject<Rect>(
mContext, mTemporaryBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
@Override
public float getWidth(@NonNull Rect animatedPipBounds) {
return animatedPipBounds.width();
}
@Override
public float getHeight(@NonNull Rect animatedPipBounds) {
return animatedPipBounds.height();
}
@Override
public float getHeight(@NonNull Rect animatedPipBounds) {
return animatedPipBounds.height();
}
@Override
public void getLocationOnScreen(
@NonNull Rect animatedPipBounds, @NonNull int[] loc) {
loc[0] = animatedPipBounds.left;
loc[1] = animatedPipBounds.top;
}
};
@Override
public void getLocationOnScreen(
@NonNull Rect animatedPipBounds, @NonNull int[] loc) {
loc[0] = animatedPipBounds.left;
loc[1] = animatedPipBounds.top;
}
};
}
return mMagnetizedPip;
}
public void dump(PrintWriter pw, String prefix) {

View File

@@ -70,6 +70,8 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject;
import java.io.PrintWriter;
import kotlin.Unit;
/**
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
* the PIP.
@@ -262,12 +264,14 @@ public class PipTouchHandler {
mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
updateMagneticTargetSize();
mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener);
mMagnetizedPip.setAnimateStuckToTarget(
(target, velX, velY, flung, after) -> {
mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
return Unit.INSTANCE;
});
mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
mMotionHelper.prepareForAnimation();
// Show the dismiss target, in case the initial touch event occurred within the
// magnetic field radius.
showDismissTargetMaybe();
@@ -286,12 +290,13 @@ public class PipTouchHandler {
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
mMotionHelper.notifyDismissalPending();
mHandler.post(() -> {
mMotionHelper.animateDismiss();
hideDismissTarget();
});
MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
PipUtils.getTopPipActivity(mContext, mActivityManager));
}
@@ -617,11 +622,16 @@ public class PipTouchHandler {
}
MotionEvent ev = (MotionEvent) inputEvent;
if (mPipResizeGestureHandler.isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY())) {
if (!mTouchState.isDragging()
&& !mMagnetizedPip.getObjectStuckToTarget()
&& !mMotionHelper.isAnimating()
&& mPipResizeGestureHandler.isWithinTouchRegion(
(int) ev.getRawX(), (int) ev.getRawY())) {
return true;
}
if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
&& mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
// If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
// to the touch state. Touch state needs a DOWN event in order to later process MOVE
// events it'll receive if the object is dragged out of the magnetic field.
@@ -643,7 +653,6 @@ public class PipTouchHandler {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
mMotionHelper.synchronizePinnedStackBoundsForTouchGesture();
mGesture.onDown(mTouchState);
break;
}
@@ -872,7 +881,7 @@ public class PipTouchHandler {
return;
}
Rect bounds = mMotionHelper.getBounds();
Rect bounds = mMotionHelper.getPossiblyAnimatingBounds();
mDelta.set(0f, 0f);
mStartPosition.set(bounds.left, bounds.top);
mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
@@ -914,7 +923,7 @@ public class PipTouchHandler {
mDelta.x += left - lastX;
mDelta.y += top - lastY;
mTmpBounds.set(mMotionHelper.getBounds());
mTmpBounds.set(mMotionHelper.getPossiblyAnimatingBounds());
mTmpBounds.offsetTo((int) left, (int) top);
mMotionHelper.movePip(mTmpBounds, true /* isDragging */);

View File

@@ -66,6 +66,40 @@ class FloatProperties {
}
}
/**
* Represents the width of a [Rect]. Typically used to animate resizing a Rect horizontally.
*
* This property's getter returns [Rect.width], and its setter changes the value of
* [Rect.right] by adding the animated width value to [Rect.left].
*/
@JvmField
val RECT_WIDTH = object : FloatPropertyCompat<Rect>("RectWidth") {
override fun getValue(rect: Rect): Float {
return rect.width().toFloat()
}
override fun setValue(rect: Rect, value: Float) {
rect.right = rect.left + value.toInt()
}
}
/**
* Represents the height of a [Rect]. Typically used to animate resizing a Rect vertically.
*
* This property's getter returns [Rect.height], and its setter changes the value of
* [Rect.bottom] by adding the animated height value to [Rect.top].
*/
@JvmField
val RECT_HEIGHT = object : FloatPropertyCompat<Rect>("RectHeight") {
override fun getValue(rect: Rect): Float {
return rect.height().toFloat()
}
override fun setValue(rect: Rect, value: Float) {
rect.bottom = rect.top + value.toInt()
}
}
/**
* Represents the x-coordinate of a [RectF]. Typically used to animate moving a RectF
* horizontally.

View File

@@ -177,6 +177,18 @@ abstract class MagnetizedObject<T : Any>(
*/
var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null
/**
* Method that is called when the object should be animated stuck to the target. The default
* implementation uses the object's x and y properties to animate the object centered inside the
* target. You can override this if you need custom animation.
*
* The method is invoked with the MagneticTarget that the object is sticking to, the X and Y
* velocities of the gesture that brought the object into the magnetic radius, whether or not it
* was flung, and a callback you must call after your animation completes.
*/
var animateStuckToTarget: (MagneticTarget, Float, Float, Boolean, (() -> Unit)?) -> Unit =
::animateStuckToTargetInternal
/**
* Sets whether forcefully flinging the object vertically towards a target causes it to be
* attracted to the target and then released immediately, despite never being dragged within the
@@ -373,7 +385,7 @@ abstract class MagnetizedObject<T : Any>(
targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf
cancelAnimations()
magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!)
animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false)
animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null)
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
} else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) {
@@ -430,8 +442,8 @@ abstract class MagnetizedObject<T : Any>(
targetObjectIsStuckTo = flungToTarget
animateStuckToTarget(flungToTarget, velX, velY, true) {
targetObjectIsStuckTo = null
magnetListener.onReleasedInTarget(flungToTarget)
targetObjectIsStuckTo = null
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
}
@@ -465,7 +477,7 @@ abstract class MagnetizedObject<T : Any>(
}
/** Animates sticking the object to the provided target with the given start velocities. */
private fun animateStuckToTarget(
private fun animateStuckToTargetInternal(
target: MagneticTarget,
velX: Float,
velY: Float,
@@ -581,10 +593,10 @@ abstract class MagnetizedObject<T : Any>(
* multiple objects.
*/
class MagneticTarget(
internal val targetView: View,
val targetView: View,
var magneticFieldRadiusPx: Int
) {
internal val centerOnScreen = PointF()
val centerOnScreen = PointF()
private val tempLoc = IntArray(2)