diff options
10 files changed, 470 insertions, 525 deletions
diff --git a/packages/SystemUI/res/drawable/dismiss_circle_background.xml b/packages/SystemUI/res/drawable/dismiss_circle_background.xml new file mode 100644 index 000000000000..e311c520d3d6 --- /dev/null +++ b/packages/SystemUI/res/drawable/dismiss_circle_background.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + + <stroke + android:width="1dp" + android:color="#66FFFFFF" /> + + <solid android:color="#B3000000" /> + +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/dismiss_target_x.xml b/packages/SystemUI/res/drawable/dismiss_target_x.xml new file mode 100644 index 000000000000..3672efffe139 --- /dev/null +++ b/packages/SystemUI/res/drawable/dismiss_target_x.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<!-- 'X' icon. --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z" + android:fillColor="#FFFFFFFF" + android:strokeColor="#FF000000"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7aaf6f9a778a..8bfba9e7b715 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1183,6 +1183,9 @@ <dimen name="bubble_dismiss_target_padding_x">40dp</dimen> <dimen name="bubble_dismiss_target_padding_y">20dp</dimen> + <dimen name="dismiss_circle_size">52dp</dimen> + <dimen name="dismiss_target_x_size">24dp</dimen> + <!-- Bubbles user education views --> <dimen name="bubbles_manage_education_width">160dp</dimen> <!-- The inset from the top bound of the manage button to place the user education. --> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 0cf6d89e8d74..8cc10d9d148f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -32,7 +32,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import android.annotation.NonNull; import android.app.Notification; import android.content.Context; import android.content.res.Configuration; @@ -47,7 +46,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Bundle; -import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; import android.view.Choreographer; @@ -56,6 +54,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; @@ -66,6 +65,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.MainThread; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.FloatPropertyCompat; @@ -81,7 +81,10 @@ import com.android.systemui.bubbles.animation.ExpandedAnimationController; import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; +import com.android.systemui.util.animation.PhysicsAnimator; +import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -227,8 +230,6 @@ public class BubbleStackView extends FrameLayout { pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress); pw.print(" showingDismiss: "); pw.println(mShowingDismiss); pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating); - pw.print(" draggingInDismiss: "); pw.println(mDraggingInDismissTarget); - pw.print(" animatingMagnet: "); pw.println(mAnimatingMagnet); mStackAnimationController.dump(fd, pw, args); mExpandedAnimationController.dump(fd, pw, args); } @@ -240,16 +241,6 @@ public class BubbleStackView extends FrameLayout { private boolean mIsExpansionAnimating = false; private boolean mShowingDismiss = false; - /** - * Whether the user is currently dragging their finger within the dismiss target. In this state - * the stack will be magnetized to the center of the target, so we shouldn't move it until the - * touch exits the dismiss target area. - */ - private boolean mDraggingInDismissTarget = false; - - /** Whether the stack is magneting towards the dismiss target. */ - private boolean mAnimatingMagnet = false; - /** The view to desaturate/darken when magneted to the dismiss target. */ private View mDesaturateAndDarkenTargetView; @@ -331,8 +322,100 @@ public class BubbleStackView extends FrameLayout { @NonNull private final SurfaceSynchronizer mSurfaceSynchronizer; - private BubbleDismissView mDismissContainer; - private Runnable mAfterMagnet; + /** + * The currently magnetized object, which is being dragged and will be attracted to the magnetic + * dismiss target. + * + * This is either the stack itself, or an individual bubble. + */ + private MagnetizedObject<?> mMagnetizedObject; + + /** + * The action to run when the magnetized object is released in the dismiss target. + * + * This will actually perform the dismissal of either the stack or an individual bubble. + */ + private Runnable mReleasedInDismissTargetAction; + + /** + * The MagneticTarget instance for our circular dismiss view. This is added to the + * MagnetizedObject instances for the stack and any dragged-out bubbles. + */ + private MagnetizedObject.MagneticTarget mMagneticTarget; + + /** Magnet listener that handles animating and dismissing individual dragged-out bubbles. */ + private final MagnetizedObject.MagnetListener mIndividualBubbleMagnetListener = + new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { + animateDesaturateAndDarken( + mExpandedAnimationController.getDraggedOutBubble(), true); + } + + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + animateDesaturateAndDarken( + mExpandedAnimationController.getDraggedOutBubble(), false); + + if (wasFlungOut) { + mExpandedAnimationController.snapBubbleBack( + mExpandedAnimationController.getDraggedOutBubble(), velX, velY); + hideDismissTarget(); + } else { + mExpandedAnimationController.onUnstuckFromTarget(); + } + } + + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mExpandedAnimationController.dismissDraggedOutBubble( + mExpandedAnimationController.getDraggedOutBubble(), + mReleasedInDismissTargetAction); + hideDismissTarget(); + } + }; + + /** Magnet listener that handles animating and dismissing the entire stack. */ + private final MagnetizedObject.MagnetListener mStackMagnetListener = + new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget( + @NonNull MagnetizedObject.MagneticTarget target) { + animateDesaturateAndDarken(mBubbleContainer, true); + } + + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + animateDesaturateAndDarken(mBubbleContainer, false); + + if (wasFlungOut) { + mStackAnimationController.flingStackThenSpringToEdge( + mStackAnimationController.getStackPosition().x, velX, velY); + hideDismissTarget(); + } else { + mStackAnimationController.onUnstuckFromTarget(); + } + } + + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mStackAnimationController.implodeStack( + () -> { + resetDesaturationAndDarken(); + mReleasedInDismissTargetAction.run(); + } + ); + + hideDismissTarget(); + } + }; + + private ViewGroup mDismissTargetContainer; + private PhysicsAnimator<View> mDismissTargetAnimator; + private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); private int mOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -409,12 +492,31 @@ public class BubbleStackView extends FrameLayout { .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring); - mDismissContainer = new BubbleDismissView(mContext); - mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams( + final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); + final View targetView = new DismissCircleView(context); + final FrameLayout.LayoutParams newParams = + new FrameLayout.LayoutParams(targetSize, targetSize); + newParams.gravity = Gravity.CENTER; + targetView.setLayoutParams(newParams); + mDismissTargetAnimator = PhysicsAnimator.getInstance(targetView); + + mDismissTargetContainer = new FrameLayout(context); + mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams( MATCH_PARENT, getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height), Gravity.BOTTOM)); - addView(mDismissContainer); + mDismissTargetContainer.setClipChildren(false); + mDismissTargetContainer.addView(targetView); + mDismissTargetContainer.setVisibility(View.INVISIBLE); + addView(mDismissTargetContainer); + + // Start translated down so the target springs up. + targetView.setTranslationY( + getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height)); + + // Save the MagneticTarget instance for the newly set up view - we'll add this to the + // MagnetizedObjects. + mMagneticTarget = new MagnetizedObject.MagneticTarget(targetView, mBubbleSize * 2); mExpandedViewXAnim = new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X); @@ -1066,6 +1168,14 @@ public class BubbleStackView extends FrameLayout { } } + /* + * Sets the action to run to dismiss the currently dragging object (either the stack or an + * individual bubble). + */ + public void setReleasedInDismissTargetAction(Runnable action) { + mReleasedInDismissTargetAction = action; + } + /** * Dismiss the stack of bubbles. * @@ -1262,7 +1372,12 @@ public class BubbleStackView extends FrameLayout { Log.d(TAG, "onBubbleDragStart: bubble=" + bubble); } maybeShowManageEducation(false); - mExpandedAnimationController.prepareForBubbleDrag(bubble); + mExpandedAnimationController.prepareForBubbleDrag(bubble, mMagneticTarget); + + // We're dragging an individual bubble, so set the magnetized object to the magnetized + // bubble. + mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut(); + mMagnetizedObject.setMagnetListener(mIndividualBubbleMagnetListener); } /** Called with the coordinates to which an individual bubble has been dragged. */ @@ -1304,7 +1419,9 @@ public class BubbleStackView extends FrameLayout { mBubbleContainer.setActiveController(mStackAnimationController); hideFlyoutImmediate(); - mDraggingInDismissTarget = false; + // Since we're dragging the stack, set the magnetized object to the magnetized stack. + mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget); + mMagnetizedObject.setMagnetListener(mStackMagnetListener); } void onDragged(float x, float y) { @@ -1425,6 +1542,11 @@ public class BubbleStackView extends FrameLayout { } } + /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */ + boolean passEventToMagnetizedObject(MotionEvent event) { + return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event); + } + /** Prepares and starts the desaturate/darken animation on the bubble stack. */ private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) { mDesaturateAndDarkenTargetView = targetView; @@ -1455,102 +1577,6 @@ public class BubbleStackView extends FrameLayout { mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null); } - /** - * Magnets the stack to the target, while also transforming the target to encircle the stack and - * desaturating/darkening the bubbles. - */ - void animateMagnetToDismissTarget( - View magnetView, boolean toTarget, float x, float y, float velX, float velY) { - mDraggingInDismissTarget = toTarget; - - if (toTarget) { - // The Y-value for the bubble stack to be positioned in the center of the dismiss target - final float destY = mDismissContainer.getDismissTargetCenterY() - mBubbleSize / 2f; - - mAnimatingMagnet = true; - - final Runnable afterMagnet = () -> { - mAnimatingMagnet = false; - if (mAfterMagnet != null) { - mAfterMagnet.run(); - } - }; - - if (magnetView == this) { - mStackAnimationController.magnetToDismiss(velX, velY, destY, afterMagnet); - animateDesaturateAndDarken(mBubbleContainer, true); - } else { - mExpandedAnimationController.magnetBubbleToDismiss( - magnetView, velX, velY, destY, afterMagnet); - - animateDesaturateAndDarken(magnetView, true); - } - } else { - mAnimatingMagnet = false; - - if (magnetView == this) { - mStackAnimationController.demagnetizeFromDismissToPoint(x, y, velX, velY); - animateDesaturateAndDarken(mBubbleContainer, false); - } else { - mExpandedAnimationController.demagnetizeBubbleTo(x, y, velX, velY); - animateDesaturateAndDarken(magnetView, false); - } - } - - mVibrator.vibrate(VibrationEffect.get(toTarget - ? VibrationEffect.EFFECT_CLICK - : VibrationEffect.EFFECT_TICK)); - } - - /** - * Magnets the stack to the dismiss target if it's not already there. Then, dismiss the stack - * using the 'implode' animation and animate out the target. - */ - void magnetToStackIfNeededThenAnimateDismissal( - View touchedView, float velX, float velY, Runnable after) { - final View draggedOutBubble = mExpandedAnimationController.getDraggedOutBubble(); - final Runnable animateDismissal = () -> { - mAfterMagnet = null; - - mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)); - mDismissContainer.springOut(); - - // 'Implode' the stack and then hide the dismiss target. - if (touchedView == this) { - mStackAnimationController.implodeStack( - () -> { - mAnimatingMagnet = false; - mShowingDismiss = false; - mDraggingInDismissTarget = false; - after.run(); - resetDesaturationAndDarken(); - }); - } else { - mExpandedAnimationController.dismissDraggedOutBubble(draggedOutBubble, () -> { - mAnimatingMagnet = false; - mShowingDismiss = false; - mDraggingInDismissTarget = false; - resetDesaturationAndDarken(); - after.run(); - }); - } - }; - - if (mAnimatingMagnet) { - // If the magnet animation is currently playing, dismiss the stack after it's done. This - // happens if the stack is flung towards the target. - mAfterMagnet = animateDismissal; - } else if (mDraggingInDismissTarget) { - // If we're in the dismiss target, but not animating, we already magneted - dismiss - // immediately. - animateDismissal.run(); - } else { - // Otherwise, we need to start the magnet animation and then dismiss afterward. - animateMagnetToDismissTarget(touchedView, true, -1 /* x */, -1 /* y */, velX, velY); - mAfterMagnet = animateDismissal; - } - } - /** Animates in the dismiss target. */ private void springInDismissTarget() { if (mShowingDismiss) { @@ -1559,10 +1585,14 @@ public class BubbleStackView extends FrameLayout { mShowingDismiss = true; - // Show the dismiss container and bring it to the front so the bubbles will go behind it. - mDismissContainer.springIn(); - mDismissContainer.bringToFront(); - mDismissContainer.setZ(Short.MAX_VALUE - 1); + mDismissTargetContainer.bringToFront(); + mDismissTargetContainer.setZ(Short.MAX_VALUE - 1); + mDismissTargetContainer.setVisibility(VISIBLE); + + mDismissTargetAnimator.cancel(); + mDismissTargetAnimator + .spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring) + .start(); } /** @@ -1574,13 +1604,13 @@ public class BubbleStackView extends FrameLayout { return; } - mDismissContainer.springOut(); mShowingDismiss = false; - } - /** Whether the location of the given MotionEvent is within the dismiss target area. */ - boolean isInDismissTarget(MotionEvent ev) { - return isIntersecting(mDismissContainer.getDismissTarget(), ev.getRawX(), ev.getRawY()); + mDismissTargetAnimator + .spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(), + mDismissTargetSpring) + .withEndActions(() -> mDismissTargetContainer.setVisibility(View.INVISIBLE)) + .start(); } /** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 46d1e0dfbab7..0c5bef4d2bde 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -30,28 +30,6 @@ import com.android.systemui.Dependency; * dismissing, and flings. */ class BubbleTouchHandler implements View.OnTouchListener { - /** Velocity required to dismiss the stack without dragging it into the dismiss target. */ - private static final float STACK_DISMISS_MIN_VELOCITY = 4000f; - - /** - * Velocity required to dismiss an individual bubble without dragging it into the dismiss - * target. - * - * This is higher than the stack dismiss velocity since unlike the stack, a downward fling could - * also be an attempted gesture to return the bubble to the row of expanded bubbles, which would - * usually be below the dragged bubble. By increasing the required velocity, it's less likely - * that the user is trying to drop it back into the row vs. fling it away. - */ - private static final float INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY = 6000f; - - /** - * When the stack is flung towards the bottom of the screen, it'll be dismissed if it's flung - * towards the center of the screen (where the dismiss target is). This value is the width of - * the target area to be considered 'towards the target'. For example 50% means that the stack - * needs to be flung towards the middle 50%, and the 25% on the left and right sides won't - * count. - */ - private static final float DISMISS_FLING_TARGET_WIDTH_PERCENT = 0.5f; private final PointF mTouchDown = new PointF(); private final PointF mViewPositionOnTouchDown = new PointF(); @@ -66,8 +44,6 @@ class BubbleTouchHandler implements View.OnTouchListener { /** View that was initially touched, when we received the first ACTION_DOWN event. */ private View mTouchedView; - /** Whether the current touched view is in the dismiss target. */ - private boolean mInDismissTarget; BubbleTouchHandler(BubbleStackView stackView, BubbleData bubbleData, Context context) { @@ -124,13 +100,33 @@ class BubbleTouchHandler implements View.OnTouchListener { if (isStack) { mViewPositionOnTouchDown.set(mStack.getStackPosition()); + + // Dismiss the entire stack if it's released in the dismiss target. + mStack.setReleasedInDismissTargetAction( + () -> mController.dismissStack(BubbleController.DISMISS_USER_GESTURE)); mStack.onDragStart(); + mStack.passEventToMagnetizedObject(event); } else if (isFlyout) { mStack.onFlyoutDragStart(); } else { mViewPositionOnTouchDown.set( mTouchedView.getTranslationX(), mTouchedView.getTranslationY()); + + // Dismiss only the dragged-out bubble if it's released in the target. + final String individualBubbleKey = ((BadgedImageView) mTouchedView).getKey(); + mStack.setReleasedInDismissTargetAction(() -> { + final Bubble bubble = + mBubbleData.getBubbleWithKey(individualBubbleKey); + // bubble can be null if the user is in the middle of + // dismissing the bubble, but the app also sent a cancel + if (bubble != null) { + mController.removeBubble(bubble.getEntry(), + BubbleController.DISMISS_USER_GESTURE); + } + }); + mStack.onBubbleDragStart(mTouchedView); + mStack.passEventToMagnetizedObject(event); } break; @@ -144,27 +140,16 @@ class BubbleTouchHandler implements View.OnTouchListener { } if (mMovedEnough) { - if (isStack) { - mStack.onDragged(viewX, viewY); - } else if (isFlyout) { + if (isFlyout) { mStack.onFlyoutDragged(deltaX); - } else { - mStack.onBubbleDragged(mTouchedView, viewX, viewY); - } - } - - final boolean currentlyInDismissTarget = mStack.isInDismissTarget(event); - if (currentlyInDismissTarget != mInDismissTarget) { - mInDismissTarget = currentlyInDismissTarget; - - mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000); - final float velX = mVelocityTracker.getXVelocity(); - final float velY = mVelocityTracker.getYVelocity(); - - // If the touch event is within the dismiss target, magnet the stack to it. - if (!isFlyout) { - mStack.animateMagnetToDismissTarget( - mTouchedView, mInDismissTarget, viewX, viewY, velX, velY); + } else if (!mStack.passEventToMagnetizedObject(event)) { + // If the magnetic target doesn't consume the event, drag the stack or + // bubble. + if (isStack) { + mStack.onDragged(viewX, viewY); + } else { + mStack.onBubbleDragged(mTouchedView, viewX, viewY); + } } } break; @@ -179,42 +164,21 @@ class BubbleTouchHandler implements View.OnTouchListener { final float velX = mVelocityTracker.getXVelocity(); final float velY = mVelocityTracker.getYVelocity(); - final boolean shouldDismiss = - isStack - ? mInDismissTarget - || isFastFlingTowardsDismissTarget(rawX, rawY, velX, velY) - : mInDismissTarget - || velY > INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY; - if (isFlyout && mMovedEnough) { mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX); - } else if (shouldDismiss) { - final String individualBubbleKey = - isStack ? null : ((BadgedImageView) mTouchedView).getKey(); - mStack.magnetToStackIfNeededThenAnimateDismissal(mTouchedView, velX, velY, - () -> { - if (isStack) { - mController.dismissStack(BubbleController.DISMISS_USER_GESTURE); - } else { - final Bubble bubble = - mBubbleData.getBubbleWithKey(individualBubbleKey); - // bubble can be null if the user is in the middle of - // dismissing the bubble, but the app also sent a cancel - if (bubble != null) { - mController.removeBubble(bubble.getEntry(), - BubbleController.DISMISS_USER_GESTURE); - } - } - }); } else if (isFlyout) { if (!mBubbleData.isExpanded() && !mMovedEnough) { mStack.onFlyoutTapped(); } } else if (mMovedEnough) { - if (isStack) { - mStack.onDragFinish(viewX, viewY, velX, velY); - } else { - mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY); + if (!mStack.passEventToMagnetizedObject(event)) { + // If the magnetic target didn't consume the event, tell the stack to finish + // the drag. + if (isStack) { + mStack.onDragFinish(viewX, viewY, velX, velY); + } else { + mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY); + } } } else if (mTouchedView == mStack.getExpandedBubbleView()) { mBubbleData.setExpanded(false); @@ -235,45 +199,15 @@ class BubbleTouchHandler implements View.OnTouchListener { return true; } - /** - * Whether the given touch data represents a powerful fling towards the bottom-center of the - * screen (the dismiss target). - */ - private boolean isFastFlingTowardsDismissTarget( - float rawX, float rawY, float velX, float velY) { - // Not a fling downward towards the target if velocity is zero or negative. - if (velY <= 0) { - return false; - } - - float bottomOfScreenInterceptX = rawX; - - // Only do math if the X velocity is non-zero, otherwise X won't change. - if (velX != 0) { - // Rise over run... - final float slope = velY / velX; - // ...y = mx + b, b = y / mx... - final float yIntercept = rawY - slope * rawX; - // ...calculate the x value when y = bottom of the screen. - bottomOfScreenInterceptX = (mStack.getHeight() - yIntercept) / slope; - } - - final float dismissTargetWidth = - mStack.getWidth() * DISMISS_FLING_TARGET_WIDTH_PERCENT; - return velY > STACK_DISMISS_MIN_VELOCITY - && bottomOfScreenInterceptX > dismissTargetWidth / 2f - && bottomOfScreenInterceptX < mStack.getWidth() - dismissTargetWidth / 2f; - } - /** Clears all touch-related state. */ private void resetForNextGesture() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } + mTouchedView = null; mMovedEnough = false; - mInDismissTarget = false; mStack.onGestureFinished(); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 607b5ef9b2c2..3eaa90c985d7 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -25,13 +25,14 @@ import android.view.DisplayCutout; import android.view.View; import android.view.WindowInsets; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.bubbles.BubbleExperimentConfig; +import com.android.systemui.util.magnetictarget.MagnetizedObject; import com.google.android.collect.Sets; @@ -62,6 +63,12 @@ public class ExpandedAnimationController /** What percentage of the screen to use when centering the bubbles in landscape. */ private static final float CENTER_BUBBLES_LANDSCAPE_PERCENT = 0.66f; + /** + * Velocity required to dismiss an individual bubble without dragging it into the dismiss + * target. + */ + private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f; + /** Horizontal offset between bubbles, which we need to know to re-stack them. */ private float mStackOffsetPx; /** Space between status bar and bubbles in the expanded state. */ @@ -79,9 +86,6 @@ public class ExpandedAnimationController /** What the current screen orientation is. */ private int mScreenOrientation; - /** Whether the dragged-out bubble is in the dismiss target. */ - private boolean mIndividualBubbleWithinDismissTarget = false; - private boolean mAnimatingExpand = false; private boolean mAnimatingCollapse = false; private @Nullable Runnable mAfterExpand; @@ -99,6 +103,17 @@ public class ExpandedAnimationController */ private boolean mSpringingBubbleToTouch = false; + /** + * Whether to spring the bubble to the next touch event coordinates. This is used to animate the + * bubble out of the magnetic dismiss target to the touch location. + * + * Once it 'catches up' and the animation ends, we'll revert to moving it directly. + */ + private boolean mSpringToTouchOnNextMotionEvent = false; + + /** The bubble currently being dragged out of the row (to potentially be dismissed). */ + private MagnetizedObject<View> mMagnetizedBubbleDraggingOut; + private int mExpandedViewPadding; public ExpandedAnimationController(Point displaySize, int expandedViewPadding, @@ -113,9 +128,6 @@ public class ExpandedAnimationController */ private boolean mBubbleDraggedOutEnough = false; - /** The bubble currently being dragged out of the row (to potentially be dismissed). */ - private View mBubbleDraggingOut; - /** * Animates expanding the bubbles into a row along the top of the screen. */ @@ -235,12 +247,46 @@ public class ExpandedAnimationController }).startAll(after); } + /** Notifies the controller that the dragged-out bubble was unstuck from the magnetic target. */ + public void onUnstuckFromTarget() { + mSpringToTouchOnNextMotionEvent = true; + } + /** Prepares the given bubble to be dragged out. */ - public void prepareForBubbleDrag(View bubble) { + public void prepareForBubbleDrag(View bubble, MagnetizedObject.MagneticTarget target) { mLayout.cancelAnimationsOnView(bubble); - mBubbleDraggingOut = bubble; - mBubbleDraggingOut.setTranslationZ(Short.MAX_VALUE); + bubble.setTranslationZ(Short.MAX_VALUE); + mMagnetizedBubbleDraggingOut = new MagnetizedObject<View>( + mLayout.getContext(), bubble, + DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) { + @Override + public float getWidth(@NonNull View underlyingObject) { + return mBubbleSizePx; + } + + @Override + public float getHeight(@NonNull View underlyingObject) { + return mBubbleSizePx; + } + + @Override + public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) { + loc[0] = (int) bubble.getTranslationX(); + loc[1] = (int) bubble.getTranslationY(); + } + }; + mMagnetizedBubbleDraggingOut.addTarget(target); + mMagnetizedBubbleDraggingOut.setHapticsEnabled(true); + mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); + } + + private void springBubbleTo(View bubble, float x, float y) { + animationForChild(bubble) + .translationX(x) + .translationY(y) + .withStiffness(SpringForce.STIFFNESS_HIGH) + .start(); } /** @@ -249,20 +295,20 @@ public class ExpandedAnimationController * bubble is dragged back into the row. */ public void dragBubbleOut(View bubbleView, float x, float y) { - if (mSpringingBubbleToTouch) { + if (mSpringToTouchOnNextMotionEvent) { + springBubbleTo(mMagnetizedBubbleDraggingOut.getUnderlyingObject(), x, y); + mSpringToTouchOnNextMotionEvent = false; + mSpringingBubbleToTouch = true; + } else if (mSpringingBubbleToTouch) { if (mLayout.arePropertiesAnimatingOnView( bubbleView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)) { - animationForChild(mBubbleDraggingOut) - .translationX(x) - .translationY(y) - .withStiffness(SpringForce.STIFFNESS_HIGH) - .start(); + springBubbleTo(mMagnetizedBubbleDraggingOut.getUnderlyingObject(), x, y); } else { mSpringingBubbleToTouch = false; } } - if (!mSpringingBubbleToTouch && !mIndividualBubbleWithinDismissTarget) { + if (!mSpringingBubbleToTouch && !mMagnetizedBubbleDraggingOut.getObjectStuckToTarget()) { bubbleView.setTranslationX(x); bubbleView.setTranslationY(y); } @@ -277,8 +323,6 @@ public class ExpandedAnimationController /** Plays a dismiss animation on the dragged out bubble. */ public void dismissDraggedOutBubble(View bubble, Runnable after) { - mIndividualBubbleWithinDismissTarget = false; - animationForChild(bubble) .withStiffness(SpringForce.STIFFNESS_HIGH) .scaleX(1.1f) @@ -290,37 +334,14 @@ public class ExpandedAnimationController } @Nullable public View getDraggedOutBubble() { - return mBubbleDraggingOut; - } - - /** Magnets the given bubble to the dismiss target. */ - public void magnetBubbleToDismiss( - View bubbleView, float velX, float velY, float destY, Runnable after) { - mIndividualBubbleWithinDismissTarget = true; - mSpringingBubbleToTouch = false; - animationForChild(bubbleView) - .withStiffness(SpringForce.STIFFNESS_MEDIUM) - .withDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) - .withPositionStartVelocities(velX, velY) - .translationX(mLayout.getWidth() / 2f - mBubbleSizePx / 2f) - .translationY(destY, after) - .start(); + return mMagnetizedBubbleDraggingOut == null + ? null + : mMagnetizedBubbleDraggingOut.getUnderlyingObject(); } - /** - * Springs the dragged-out bubble towards the given coordinates and sets flags to have touch - * events update the spring's final position until it's settled. - */ - public void demagnetizeBubbleTo(float x, float y, float velX, float velY) { - mIndividualBubbleWithinDismissTarget = false; - mSpringingBubbleToTouch = true; - - animationForChild(mBubbleDraggingOut) - .translationX(x) - .translationY(y) - .withPositionStartVelocities(velX, velY) - .withStiffness(SpringForce.STIFFNESS_HIGH) - .start(); + /** Returns the MagnetizedObject instance for the dragging-out bubble. */ + public MagnetizedObject<View> getMagnetizedBubbleDraggingOut() { + return mMagnetizedBubbleDraggingOut; } /** @@ -335,13 +356,14 @@ public class ExpandedAnimationController .withPositionStartVelocities(velX, velY) .start(() -> bubbleView.setTranslationZ(0f) /* after */); + mMagnetizedBubbleDraggingOut = null; + updateBubblePositions(); } /** Resets bubble drag out gesture flags. */ public void onGestureFinished() { mBubbleDraggedOutEnough = false; - mBubbleDraggingOut = null; updateBubblePositions(); } @@ -373,7 +395,6 @@ public class ExpandedAnimationController pw.print(" isActive: "); pw.println(isActiveController()); pw.print(" animatingExpand: "); pw.println(mAnimatingExpand); pw.print(" animatingCollapse: "); pw.println(mAnimatingCollapse); - pw.print(" bubbleInDismiss: "); pw.println(mIndividualBubbleWithinDismissTarget); pw.print(" springingBubble: "); pw.println(mSpringingBubbleToTouch); } @@ -453,8 +474,8 @@ public class ExpandedAnimationController final PhysicsAnimationLayout.PhysicsPropertyAnimator animator = animationForChild(child); // If we're removing the dragged-out bubble, that means it got dismissed. - if (child.equals(mBubbleDraggingOut)) { - mBubbleDraggingOut = null; + if (child.equals(getDraggedOutBubble())) { + mMagnetizedBubbleDraggingOut = null; finishRemoval.run(); } else { animator.alpha(0f, finishRemoval /* endAction */) @@ -490,7 +511,7 @@ public class ExpandedAnimationController // Don't animate the dragging out bubble, or it'll jump around while being dragged. It // will be snapped to the correct X value after the drag (if it's not dismissed). - if (bubble.equals(mBubbleDraggingOut)) { + if (bubble.equals(getDraggedOutBubble())) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index f22c8faad95c..b81665cd186a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -16,7 +16,6 @@ package com.android.systemui.bubbles.animation; -import android.annotation.NonNull; import android.content.res.Resources; import android.graphics.PointF; import android.graphics.Rect; @@ -25,6 +24,7 @@ import android.util.Log; import android.view.View; import android.view.WindowInsets; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.FlingAnimation; @@ -35,6 +35,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.util.animation.PhysicsAnimator; +import com.android.systemui.util.magnetictarget.MagnetizedObject; import com.google.android.collect.Sets; @@ -92,6 +93,9 @@ public class StackAnimationController extends */ private static final float ESCAPE_VELOCITY = 750f; + /** Velocity required to dismiss the stack without dragging it into the dismiss target. */ + private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f; + /** * The canonical position of the stack. This is typically the position of the first bubble, but * we need to keep track of it separately from the first bubble's translation in case there are @@ -100,6 +104,12 @@ public class StackAnimationController extends private PointF mStackPosition = new PointF(-1, -1); /** + * MagnetizedObject instance for the stack, which is used by the touch handler for the magnetic + * dismiss target. + */ + private MagnetizedObject<StackAnimationController> mMagnetizedStack; + + /** * The area that Bubbles will occupy after all animations end. This is used to move other * floating content out of the way proactively. */ @@ -136,11 +146,6 @@ public class StackAnimationController extends private boolean mIsMovingFromFlinging = false; /** - * Whether the stack is within the dismiss target (either by being dragged, magnet'd, or flung). - */ - private boolean mWithinDismissTarget = false; - - /** * Whether the first bubble is springing towards the touch point, rather than using the default * behavior of moving directly to the touch point with the rest of the stack following it. * @@ -154,6 +159,14 @@ public class StackAnimationController extends */ private boolean mFirstBubbleSpringingToTouch = false; + /** + * Whether to spring the stack to the next touch event coordinates. This is used to animate the + * stack (including the first bubble) out of the magnetic dismiss target to the touch location. + * Once it 'catches up' and the animation ends, we'll revert to moving the first bubble directly + * and only animating the following bubbles. + */ + private boolean mSpringToTouchOnNextMotionEvent = false; + /** Horizontal offset of bubbles in the stack. */ private float mStackOffset; /** Diameter of the bubble icon. */ @@ -273,7 +286,8 @@ public class StackAnimationController extends * Note that we need new SpringForce instances per animation despite identical configs because * SpringAnimation uses SpringForce's internal (changing) velocity while the animation runs. */ - public void springStack(float destinationX, float destinationY, float stiffness) { + public void springStack( + float destinationX, float destinationY, float stiffness) { notifyFloatingCoordinatorStackAnimatingTo(destinationX, destinationY); springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, @@ -404,7 +418,7 @@ public class StackAnimationController extends pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null"); pw.print(" currentStackPos: "); pw.println(mStackPosition.toString()); pw.print(" isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging); - pw.print(" withinDismiss: "); pw.println(mWithinDismissTarget); + pw.print(" withinDismiss: "); pw.println(isStackStuckToTarget()); pw.print(" firstBubbleSpringing: "); pw.println(mFirstBubbleSpringingToTouch); } @@ -580,14 +594,18 @@ public class StackAnimationController extends /** Moves the stack in response to a touch event. */ public void moveStackFromTouch(float x, float y) { - - // If we're springing to the touch point to 'catch up' after dragging out of the dismiss - // target, then update the stack position animations instead of moving the bubble directly. - if (mFirstBubbleSpringingToTouch) { + // Begin the spring-to-touch catch up animation if needed. + if (mSpringToTouchOnNextMotionEvent) { + springStack(x, y, DEFAULT_STIFFNESS); + mSpringToTouchOnNextMotionEvent = false; + mFirstBubbleSpringingToTouch = true; + } else if (mFirstBubbleSpringingToTouch) { final SpringAnimation springToTouchX = - (SpringAnimation) mStackPositionAnimations.get(DynamicAnimation.TRANSLATION_X); + (SpringAnimation) mStackPositionAnimations.get( + DynamicAnimation.TRANSLATION_X); final SpringAnimation springToTouchY = - (SpringAnimation) mStackPositionAnimations.get(DynamicAnimation.TRANSLATION_Y); + (SpringAnimation) mStackPositionAnimations.get( + DynamicAnimation.TRANSLATION_Y); // If either animation is still running, we haven't caught up. Update the animations. if (springToTouchX.isRunning() || springToTouchY.isRunning()) { @@ -600,56 +618,14 @@ public class StackAnimationController extends } } - if (!mFirstBubbleSpringingToTouch && !mWithinDismissTarget) { + if (!mFirstBubbleSpringingToTouch && !isStackStuckToTarget()) { moveFirstBubbleWithStackFollowing(x, y); } } - /** - * Demagnetizes the stack, springing it towards the given point. This also sets flags so that - * subsequent touch events will update the final position of the demagnetization spring instead - * of directly moving the bubbles, until demagnetization is complete. - */ - public void demagnetizeFromDismissToPoint(float x, float y, float velX, float velY) { - mWithinDismissTarget = false; - mFirstBubbleSpringingToTouch = true; - - springFirstBubbleWithStackFollowing( - DynamicAnimation.TRANSLATION_X, - new SpringForce() - .setDampingRatio(DEFAULT_BOUNCINESS) - .setStiffness(DEFAULT_STIFFNESS), - velX, x); - - springFirstBubbleWithStackFollowing( - DynamicAnimation.TRANSLATION_Y, - new SpringForce() - .setDampingRatio(DEFAULT_BOUNCINESS) - .setStiffness(DEFAULT_STIFFNESS), - velY, y); - } - - /** - * Spring the stack towards the dismiss target, respecting existing velocity. This also sets - * flags so that subsequent touch events will not move the stack until it's demagnetized. - */ - public void magnetToDismiss(float velX, float velY, float destY, Runnable after) { - mWithinDismissTarget = true; - mFirstBubbleSpringingToTouch = false; - - springFirstBubbleWithStackFollowing( - DynamicAnimation.TRANSLATION_X, - new SpringForce() - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) - .setStiffness(SpringForce.STIFFNESS_MEDIUM), - velX, mLayout.getWidth() / 2f - mBubbleBitmapSize / 2f); - - springFirstBubbleWithStackFollowing( - DynamicAnimation.TRANSLATION_Y, - new SpringForce() - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) - .setStiffness(SpringForce.STIFFNESS_MEDIUM), - velY, destY, after); + /** Notify the controller that the stack has been unstuck from the dismiss target. */ + public void onUnstuckFromTarget() { + mSpringToTouchOnNextMotionEvent = true; } /** @@ -663,13 +639,7 @@ public class StackAnimationController extends .alpha(0f) .withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY) .withStiffness(SpringForce.STIFFNESS_HIGH) - .start(() -> { - // Run the callback and reset flags. The child translation animations might - // still be running, but that's fine. Once the alpha is at 0f they're no longer - // visible anyway. - after.run(); - mWithinDismissTarget = false; - }); + .start(after); } /** @@ -720,7 +690,7 @@ public class StackAnimationController extends if (property.equals(DynamicAnimation.TRANSLATION_X) || property.equals(DynamicAnimation.TRANSLATION_Y)) { return index + 1; - } else if (mWithinDismissTarget) { + } else if (isStackStuckToTarget()) { return index + 1; // Chain all animations in dismiss (scale, alpha, etc. are used). } else { return NONE; @@ -733,7 +703,7 @@ public class StackAnimationController extends if (property.equals(DynamicAnimation.TRANSLATION_X)) { // If we're in the dismiss target, have the bubbles pile on top of each other with no // offset. - if (mWithinDismissTarget) { + if (isStackStuckToTarget()) { return 0f; } else { // Offset to the left if we're on the left, or the right otherwise. @@ -755,7 +725,7 @@ public class StackAnimationController extends @Override void onChildAdded(View child, int index) { // Don't animate additions within the dismiss target. - if (mWithinDismissTarget) { + if (isStackStuckToTarget()) { return; } @@ -784,8 +754,6 @@ public class StackAnimationController extends if (mLayout.getChildCount() > 0) { animationForChildAtIndex(0).translationX(mStackPosition.x).start(); } else { - // If there's no other bubbles, and we were in the dismiss target, reset the flag. - mWithinDismissTarget = false; // When all children are removed ensure stack position is sane setStackPosition(mRestingStackPosition == null ? getDefaultStartPosition() @@ -831,6 +799,9 @@ public class StackAnimationController extends } } + private boolean isStackStuckToTarget() { + return mMagnetizedStack != null && mMagnetizedStack.getObjectStuckToTarget(); + } /** Moves the stack, without any animation, to the starting position. */ private void moveStackToStartPosition() { @@ -959,6 +930,44 @@ public class StackAnimationController extends } /** + * Returns the {@link MagnetizedObject} instance for the bubble stack, with the provided + * {@link MagnetizedObject.MagneticTarget} added as a target. + */ + public MagnetizedObject<StackAnimationController> getMagnetizedStack( + MagnetizedObject.MagneticTarget target) { + if (mMagnetizedStack == null) { + mMagnetizedStack = new MagnetizedObject<StackAnimationController>( + mLayout.getContext(), + this, + new StackPositionProperty(DynamicAnimation.TRANSLATION_X), + new StackPositionProperty(DynamicAnimation.TRANSLATION_Y) + ) { + @Override + public float getWidth(@NonNull StackAnimationController underlyingObject) { + return mBubbleSize; + } + + @Override + public float getHeight(@NonNull StackAnimationController underlyingObject) { + return mBubbleSize; + } + + @Override + public void getLocationOnScreen(@NonNull StackAnimationController underlyingObject, + @NonNull int[] loc) { + loc[0] = (int) mStackPosition.x; + loc[1] = (int) mStackPosition.y; + } + }; + mMagnetizedStack.addTarget(target); + mMagnetizedStack.setHapticsEnabled(true); + mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); + } + + return mMagnetizedStack; + } + + /** * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's * translation and animate the rest of the stack with it. A DynamicAnimation can animate this * property directly to move the first bubble and cause the stack to 'follow' to the new diff --git a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java new file mode 100644 index 000000000000..6c3538cb6142 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java @@ -0,0 +1,62 @@ +/* + * 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.systemui.util; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.systemui.R; + +/** + * Circular view with a semitransparent, circular background with an 'X' inside it. + * + * This is used by both Bubbles and PIP as the dismiss target. + */ +public class DismissCircleView extends FrameLayout { + + private final ImageView mIconView = new ImageView(getContext()); + + public DismissCircleView(Context context) { + super(context); + final Resources res = getResources(); + + setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); + + mIconView.setImageDrawable(res.getDrawable(R.drawable.dismiss_target_x)); + addView(mIconView); + + setViewSizes(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + setViewSizes(); + } + + /** Retrieves the current dimensions for the icon and circle and applies them. */ + private void setViewSizes() { + final Resources res = getResources(); + final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size); + mIconView.setLayoutParams( + new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java index ae4581a80503..ec6d3e9d0dff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java @@ -17,7 +17,6 @@ package com.android.systemui.bubbles.animation; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.mockito.Mockito.verify; import android.content.res.Configuration; @@ -118,118 +117,6 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC testBubblesInCorrectExpandedPositions(); } - @Test - @Ignore - public void testBubbleDraggedNotDismissedSnapsBack() throws InterruptedException { - expand(); - - final View draggedBubble = mViews.get(0); - mExpandedController.prepareForBubbleDrag(draggedBubble); - mExpandedController.dragBubbleOut(draggedBubble, 500f, 500f); - - assertEquals(500f, draggedBubble.getTranslationX(), 1f); - assertEquals(500f, draggedBubble.getTranslationY(), 1f); - - // Snap it back and make sure it made it back correctly. - mExpandedController.snapBubbleBack(draggedBubble, 0f, 0f); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - testBubblesInCorrectExpandedPositions(); - } - - @Test - @Ignore - public void testBubbleDismissed() throws InterruptedException { - expand(); - - final View draggedBubble = mViews.get(0); - mExpandedController.prepareForBubbleDrag(draggedBubble); - mExpandedController.dragBubbleOut(draggedBubble, 500f, 500f); - - assertEquals(500f, draggedBubble.getTranslationX(), 1f); - assertEquals(500f, draggedBubble.getTranslationY(), 1f); - - mLayout.removeView(draggedBubble); - waitForLayoutMessageQueue(); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - - assertEquals(-1, mLayout.indexOfChild(draggedBubble)); - testBubblesInCorrectExpandedPositions(); - } - - @Test - @Ignore("Flaky") - public void testMagnetToDismiss_dismiss() throws InterruptedException { - expand(); - - final View draggedOutView = mViews.get(0); - final Runnable after = Mockito.mock(Runnable.class); - - mExpandedController.prepareForBubbleDrag(draggedOutView); - mExpandedController.dragBubbleOut(draggedOutView, 25, 25); - - // Magnet to dismiss, verify the bubble is at the dismiss target and the callback was - // called. - mExpandedController.magnetBubbleToDismiss( - mViews.get(0), 100 /* velX */, 100 /* velY */, 1000 /* destY */, after); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - verify(after).run(); - assertEquals(1000, mViews.get(0).getTranslationY(), .1f); - - // Dismiss the now-magneted bubble, verify that the callback was called. - final Runnable afterDismiss = Mockito.mock(Runnable.class); - mExpandedController.dismissDraggedOutBubble(draggedOutView, afterDismiss); - waitForPropertyAnimations(DynamicAnimation.ALPHA); - verify(after).run(); - - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - - assertEquals(mBubblePaddingTop, mViews.get(1).getTranslationX(), 1f); - } - - @Test - @Ignore("Flaky") - public void testMagnetToDismiss_demagnetizeThenDrag() throws InterruptedException { - expand(); - - final View draggedOutView = mViews.get(0); - final Runnable after = Mockito.mock(Runnable.class); - - mExpandedController.prepareForBubbleDrag(draggedOutView); - mExpandedController.dragBubbleOut(draggedOutView, 25, 25); - - // Magnet to dismiss, verify the bubble is at the dismiss target and the callback was - // called. - mExpandedController.magnetBubbleToDismiss( - draggedOutView, 100 /* velX */, 100 /* velY */, 1000 /* destY */, after); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - verify(after).run(); - assertEquals(1000, mViews.get(0).getTranslationY(), .1f); - - // Demagnetize the bubble towards (25, 25). - mExpandedController.demagnetizeBubbleTo(25 /* x */, 25 /* y */, 100, 100); - - // Start dragging towards (20, 20). - mExpandedController.dragBubbleOut(draggedOutView, 20, 20); - - // Since we just demagnetized, the bubble shouldn't be at (20, 20), it should be animating - // towards it. - assertNotEquals(20, draggedOutView.getTranslationX()); - assertNotEquals(20, draggedOutView.getTranslationY()); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - - // Waiting for the animations should result in the bubble ending at (20, 20) since the - // animation end value was updated. - assertEquals(20, draggedOutView.getTranslationX(), 1f); - assertEquals(20, draggedOutView.getTranslationY(), 1f); - - // Drag to (30, 30). - mExpandedController.dragBubbleOut(draggedOutView, 30, 30); - - // It should go there instantly since the animations finished. - assertEquals(30, draggedOutView.getTranslationX(), 1f); - assertEquals(30, draggedOutView.getTranslationY(), 1f); - } - /** Expand the stack and wait for animations to finish. */ private void expand() throws InterruptedException { mExpandedController.expandFromStack(Mockito.mock(Runnable.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java index 9cc034996687..e3187cb9a6c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -17,7 +17,6 @@ package com.android.systemui.bubbles.animation; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -41,7 +40,6 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -242,61 +240,6 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase } @Test - @Ignore("Flaky") - public void testMagnetToDismiss_dismiss() throws InterruptedException { - final Runnable after = Mockito.mock(Runnable.class); - - // Magnet to dismiss, verify the stack is at the dismiss target and the callback was - // called. - mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - verify(after).run(); - assertEquals(1000, mViews.get(0).getTranslationY(), .1f); - - // Dismiss the stack, verify that the callback was called. - final Runnable afterImplode = Mockito.mock(Runnable.class); - mStackController.implodeStack(afterImplode); - waitForPropertyAnimations( - DynamicAnimation.ALPHA, DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y); - verify(after).run(); - } - - @Test - @Ignore("Flaking") - public void testMagnetToDismiss_demagnetizeThenDrag() throws InterruptedException { - final Runnable after = Mockito.mock(Runnable.class); - - // Magnet to dismiss, verify the stack is at the dismiss target and the callback was - // called. - mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after); - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - verify(after).run(); - - assertEquals(1000, mViews.get(0).getTranslationY(), .1f); - - // Demagnetize towards (25, 25) and then send a touch event. - mStackController.demagnetizeFromDismissToPoint(25, 25, 0, 0); - waitForLayoutMessageQueue(); - mStackController.moveStackFromTouch(20, 20); - - // Since the stack is demagnetizing, it shouldn't be at the stack position yet. - assertNotEquals(20, mStackController.getStackPosition().x, 1f); - assertNotEquals(20, mStackController.getStackPosition().y, 1f); - - waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); - - // Once the animation is done it should end at the touch position coordinates. - assertEquals(20, mStackController.getStackPosition().x, 1f); - assertEquals(20, mStackController.getStackPosition().y, 1f); - - mStackController.moveStackFromTouch(30, 30); - - // Touches after the animation are done should change the stack position instantly. - assertEquals(30, mStackController.getStackPosition().x, 1f); - assertEquals(30, mStackController.getStackPosition().y, 1f); - } - - @Test public void testFloatingCoordinator() { // We should have called onContentAdded only once while adding all of the bubbles in // setup(). |