Loading packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +6 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +152 −75 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. */ Loading Loading @@ -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(); Loading @@ -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; Loading @@ -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. Loading Loading @@ -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 Loading Loading @@ -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(); } /** Loading @@ -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(); 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; } /** Loading Loading @@ -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 */) Loading @@ -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 */); Loading @@ -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 */, Loading @@ -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; } /** Loading Loading @@ -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; } Loading Loading @@ -449,15 +501,36 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (int) toY + mBounds.height()); setAnimatingToBounds(mAnimatingToBounds); mAnimatedBoundsPhysicsAnimator .withEndActions(() -> { if (!dismiss) { mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds); 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(); }) .addUpdateListener(mResizePipUpdateListener) .start(); mSpringingToTouch = false; mDismissalPending = false; } /** Loading Loading @@ -503,8 +576,9 @@ 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) { 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(); Loading @@ -524,6 +598,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, }; } return mMagnetizedPip; } public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); Loading packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +19 −10 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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(); Loading @@ -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)); } Loading Loading @@ -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. Loading @@ -643,7 +653,6 @@ public class PipTouchHandler { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { mMotionHelper.synchronizePinnedStackBoundsForTouchGesture(); mGesture.onDown(mTouchState); break; } Loading Loading @@ -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; Loading Loading @@ -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 */); Loading packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt +34 −0 Original line number Diff line number Diff line Loading @@ -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. Loading packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +17 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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) } Loading Loading @@ -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, Loading Loading @@ -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) Loading Loading
packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +6 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading
packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +152 −75 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. */ Loading Loading @@ -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(); Loading @@ -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; Loading @@ -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. Loading Loading @@ -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 Loading Loading @@ -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(); } /** Loading @@ -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(); 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; } /** Loading Loading @@ -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 */) Loading @@ -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 */); Loading @@ -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 */, Loading @@ -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; } /** Loading Loading @@ -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; } Loading Loading @@ -449,15 +501,36 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (int) toY + mBounds.height()); setAnimatingToBounds(mAnimatingToBounds); mAnimatedBoundsPhysicsAnimator .withEndActions(() -> { if (!dismiss) { mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds); 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(); }) .addUpdateListener(mResizePipUpdateListener) .start(); mSpringingToTouch = false; mDismissalPending = false; } /** Loading Loading @@ -503,8 +576,9 @@ 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) { 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(); Loading @@ -524,6 +598,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, }; } return mMagnetizedPip; } public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); Loading
packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +19 −10 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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(); Loading @@ -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)); } Loading Loading @@ -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. Loading @@ -643,7 +653,6 @@ public class PipTouchHandler { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { mMotionHelper.synchronizePinnedStackBoundsForTouchGesture(); mGesture.onDown(mTouchState); break; } Loading Loading @@ -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; Loading Loading @@ -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 */); Loading
packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt +34 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +17 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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) } Loading Loading @@ -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, Loading Loading @@ -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) Loading