From c68a4cfaf4e3a232ce553793e62e1942f8b2336a Mon Sep 17 00:00:00 2001 From: Juan Sebastian Martinez Date: Mon, 10 Mar 2025 14:19:52 -0700 Subject: Reattaching and dismissing notifications when detached. This change re-applies the previous behavior that had to be reverted. Essentially: * Magnetically detaching a notification makes it dismiss if released from touch. * Notifications re-attach to the edge of the screen when dragged past a re-attachment threshold. Test: MagneticNotificationRowManagerImplTest Test: NotificationSwipeHelperTest Flag: com.android.systemui.magnetic_notification_swipes Bug: 397418247 Bug: 397418669 Change-Id: I9ad253d9e39160baf8a55df19abe38d298d3f33a --- .../notification/row/NotificationMenuRowTest.java | 3 ++ .../MagneticNotificationRowManagerImplTest.kt | 47 ++++++++++++++--- .../stack/NotificationSwipeHelperTest.java | 16 ++++++ .../src/com/android/systemui/SwipeHelper.java | 21 ++++++-- .../statusbar/notification/row/ExpandableView.java | 25 +++++++-- .../notification/row/NotificationMenuRow.java | 5 +- .../stack/MagneticNotificationRowManager.kt | 20 +++++-- .../stack/MagneticNotificationRowManagerImpl.kt | 61 ++++++++++++++++++---- .../notification/stack/MagneticRowListener.kt | 13 ++++- .../NotificationStackScrollLayoutController.java | 13 +++-- .../stack/NotificationSwipeHelper.java | 6 ++- 11 files changed, 192 insertions(+), 38 deletions(-) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index ce120c51db6a..95366568a37a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.platform.test.annotations.DisableFlags; import android.provider.Settings; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -38,6 +39,7 @@ import android.view.ViewGroup; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.notification.collection.EntryAdapter; @@ -418,6 +420,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest { assertTrue("when alpha is .5, menu is visible", row.isMenuVisible()); } + @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES) @Test public void testOnTouchMove() { NotificationMenuRow row = Mockito.spy( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt index ccc8be7de038..6c6ba933c03a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt @@ -130,7 +130,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled setTargets() @@ -150,7 +152,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled canRowBeDismissed = false @@ -172,7 +176,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled setTargets() @@ -192,7 +198,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { kosmos.testScope.runTest { // GIVEN a threshold of 100 px val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // GIVEN that targets are set and the rows are being pulled canRowBeDismissed = false @@ -294,6 +302,29 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { assertThat(underTest.isSwipedViewRoundableSet).isFalse() } + @Test + fun isMagneticRowDismissible_isDismissibleWhenDetached() = + kosmos.testScope.runTest { + setDetachedState() + + val isDismissible = underTest.isMagneticRowSwipeDetached(swipedRow) + assertThat(isDismissible).isTrue() + } + + @Test + fun setMagneticRowTranslation_whenDetached_belowAttachThreshold_reattaches() = + kosmos.testScope.runTest { + // GIVEN that the swiped view has been detached + setDetachedState() + + // WHEN setting a new translation above the attach threshold + val translation = 50f + underTest.setMagneticRowTranslation(swipedRow, translation) + + // THEN the swiped view reattaches magnetically and the state becomes PULLING + assertThat(underTest.currentState).isEqualTo(State.PULLING) + } + @After fun tearDown() { // We reset the manager so that all MagneticRowListener can cancel all animations @@ -302,7 +333,9 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { private fun setDetachedState() { val threshold = 100f - underTest.setSwipeThresholdPx(threshold) + underTest.onDensityChange( + threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + ) // Set the pulling state setTargets() @@ -327,8 +360,8 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() { private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener { val delegate = this return object : MagneticRowListener { - override fun setMagneticTranslation(translation: Float) { - delegate.setMagneticTranslation(translation) + override fun setMagneticTranslation(translation: Float, trackEagerly: Boolean) { + delegate.setMagneticTranslation(translation, trackEagerly) } override fun triggerMagneticForce( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 789701f5e4b0..de48f4018989 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -49,6 +49,7 @@ import android.view.ViewConfiguration; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.flags.FakeFeatureFlags; @@ -362,6 +363,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { verify(mSwipeHelper, times(1)).isFalseGesture(); } + @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES) @Test public void testIsDismissGesture_farEnough() { doReturn(false).when(mSwipeHelper).isFalseGesture(); @@ -374,6 +376,20 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { verify(mSwipeHelper, times(1)).isFalseGesture(); } + @EnableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES) + @Test + public void testIsDismissGesture_magneticSwipeIsDismissible() { + doReturn(false).when(mSwipeHelper).isFalseGesture(); + doReturn(false).when(mSwipeHelper).swipedFarEnough(); + doReturn(false).when(mSwipeHelper).swipedFastEnough(); + doReturn(true).when(mCallback).isMagneticViewDetached(any()); + when(mCallback.canChildBeDismissedInDirection(any(), anyBoolean())).thenReturn(true); + when(mEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_UP); + + assertTrue("Should be a dismissal", mSwipeHelper.isDismissGesture(mEvent)); + verify(mSwipeHelper, times(1)).isFalseGesture(); + } + @Test public void testIsDismissGesture_notFarOrFastEnough() { doReturn(false).when(mSwipeHelper).isFalseGesture(); diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 089466707298..d017754ae653 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -778,18 +778,26 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { protected boolean swipedFarEnough() { float translation = getTranslation(mTouchedView); - return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize( - mTouchedView); + return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mTouchedView); } public boolean isDismissGesture(MotionEvent ev) { float translation = getTranslation(mTouchedView); return ev.getActionMasked() == MotionEvent.ACTION_UP && !mFalsingManager.isUnlockingDisabled() - && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough()) + && !isFalseGesture() && isSwipeDismissible() && mCallback.canChildBeDismissedInDirection(mTouchedView, translation > 0); } + /** Can the swipe gesture on the touched view be considered as a dismiss intention */ + public boolean isSwipeDismissible() { + if (magneticNotificationSwipes()) { + return mCallback.isMagneticViewDetached(mTouchedView) || swipedFastEnough(); + } else { + return swipedFastEnough() || swipedFarEnough(); + } + } + /** Returns true if the gesture should be rejected. */ public boolean isFalseGesture() { boolean falsingDetected = mCallback.isAntiFalsingNeeded(); @@ -969,6 +977,13 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { */ void onMagneticInteractionEnd(View view, float velocity); + /** + * Determine if a view managed by magnetic interactions is magnetically detached + * @param view The magnetic view + * @return if the view is detached according to its magnetic state. + */ + boolean isMagneticViewDetached(View view); + /** * Called when the child is long pressed and available to start drag and drop. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 292f74a65554..f36a0cf51b97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.Flags.notificationColorUpdateLogger; import static com.android.systemui.Flags.physicalNotificationMovement; +import static java.lang.Math.abs; + import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Configuration; @@ -29,6 +31,7 @@ import android.util.FloatProperty; import android.util.IndentingPrintWriter; import android.util.Log; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.FrameLayout; @@ -110,14 +113,27 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro protected SpringAnimation mMagneticAnimator = new SpringAnimation( this /* object */, DynamicAnimation.TRANSLATION_X); + private int mTouchSlop; + protected MagneticRowListener mMagneticRowListener = new MagneticRowListener() { @Override - public void setMagneticTranslation(float translation) { - if (mMagneticAnimator.isRunning()) { - mMagneticAnimator.animateToFinalPosition(translation); - } else { + public void setMagneticTranslation(float translation, boolean trackEagerly) { + if (!mMagneticAnimator.isRunning()) { setTranslation(translation); + return; + } + + if (trackEagerly) { + float delta = abs(getTranslation() - translation); + if (delta > mTouchSlop) { + mMagneticAnimator.animateToFinalPosition(translation); + } else { + mMagneticAnimator.cancel(); + setTranslation(translation); + } + } else { + mMagneticAnimator.animateToFinalPosition(translation); } } @@ -183,6 +199,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro private void initDimens() { mContentShift = getResources().getDimensionPixelSize( R.dimen.shelf_transform_content_shift); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 977936fa34fc..c03dc279888f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -46,7 +46,6 @@ import com.android.systemui.Flags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.res.R; import com.android.systemui.statusbar.AlphaOptimizedImageView; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; @@ -363,7 +362,9 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl final float dismissThreshold = getDismissThreshold(); final boolean snappingToDismiss = delta < -dismissThreshold || delta > dismissThreshold; if (mSnappingToDismiss != snappingToDismiss) { - getMenuView().performHapticFeedback(CLOCK_TICK); + if (!Flags.magneticNotificationSwipes()) { + getMenuView().performHapticFeedback(CLOCK_TICK); + } } mSnappingToDismiss = snappingToDismiss; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt index aa6951715755..48cff7497e3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt @@ -33,12 +33,12 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow interface MagneticNotificationRowManager { /** - * Set the swipe threshold in pixels. After crossing the threshold, the magnetic target detaches - * and the magnetic neighbors snap back. + * Notifies a change in the device density. The density can be used to compute the values of + * thresholds in pixels. * - * @param[threshold] Swipe threshold in pixels. + * @param[density] The device density. */ - fun setSwipeThresholdPx(thresholdPx: Float) + fun onDensityChange(density: Float) /** * Set the magnetic and roundable targets of a magnetic swipe interaction. @@ -87,6 +87,9 @@ interface MagneticNotificationRowManager { */ fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float? = null) + /** Determine if the given [ExpandableNotificationRow] has been magnetically detached. */ + fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean + /* Reset any roundness that magnetic targets may have */ fun resetRoundness() @@ -104,12 +107,15 @@ interface MagneticNotificationRowManager { /** Detaching threshold in dp */ const val MAGNETIC_DETACH_THRESHOLD_DP = 56 + /** Re-attaching threshold in dp */ + const val MAGNETIC_ATTACH_THRESHOLD_DP = 40 + /* An empty implementation of a manager */ @JvmStatic val Empty: MagneticNotificationRowManager get() = object : MagneticNotificationRowManager { - override fun setSwipeThresholdPx(thresholdPx: Float) {} + override fun onDensityChange(density: Float) {} override fun setMagneticAndRoundableTargets( swipingRow: ExpandableNotificationRow, @@ -127,6 +133,10 @@ interface MagneticNotificationRowManager { velocity: Float?, ) {} + override fun isMagneticRowSwipeDetached( + row: ExpandableNotificationRow + ): Boolean = false + override fun resetRoundness() {} override fun reset() {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt index 5a23f7cc2861..6e8b2226b4f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt @@ -47,6 +47,7 @@ constructor( private set private var magneticDetachThreshold = Float.POSITIVE_INFINITY + private var magneticAttachThreshold = 0f // Has the roundable target been set for the magnetic view that is being swiped. val isSwipedViewRoundableSet: Boolean @@ -57,13 +58,25 @@ constructor( SpringForce().setStiffness(DETACH_STIFFNESS).setDampingRatio(DETACH_DAMPING_RATIO) private val snapForce = SpringForce().setStiffness(SNAP_BACK_STIFFNESS).setDampingRatio(SNAP_BACK_DAMPING_RATIO) + private val attachForce = + SpringForce().setStiffness(ATTACH_STIFFNESS).setDampingRatio(ATTACH_DAMPING_RATIO) // Multiplier applied to the translation of a row while swiped val swipedRowMultiplier = MAGNETIC_TRANSLATION_MULTIPLIERS[MAGNETIC_TRANSLATION_MULTIPLIERS.size / 2] - override fun setSwipeThresholdPx(thresholdPx: Float) { - magneticDetachThreshold = thresholdPx + /** + * An offset applied to input translation that increases on subsequent re-attachments of a + * detached magnetic view. This helps keep computations consistent when the drag gesture input + * and the swiped notification don't share the same origin point after a re-attaching animation. + */ + private var translationOffset = 0f + + override fun onDensityChange(density: Float) { + magneticDetachThreshold = + density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP + magneticAttachThreshold = + density * MagneticNotificationRowManager.MAGNETIC_ATTACH_THRESHOLD_DP } override fun setMagneticAndRoundableTargets( @@ -72,6 +85,7 @@ constructor( sectionsManager: NotificationSectionsManager, ) { if (currentState == State.IDLE) { + translationOffset = 0f updateMagneticAndRoundableTargets(swipingRow, stackScrollLayout, sectionsManager) currentState = State.TARGETS_SET } else { @@ -121,36 +135,36 @@ constructor( val canTargetBeDismissed = currentMagneticListeners.swipedListener()?.canRowBeDismissed() ?: false + val correctedTranslation = translation - translationOffset when (currentState) { State.IDLE -> { logger.logMagneticRowTranslationNotSet(currentState, row.getLoggingKey()) return false } State.TARGETS_SET -> { - pullTargets(translation, canTargetBeDismissed) + pullTargets(correctedTranslation, canTargetBeDismissed) currentState = State.PULLING } State.PULLING -> { - updateRoundness(translation) + updateRoundness(correctedTranslation) if (canTargetBeDismissed) { - pullDismissibleRow(translation) + pullDismissibleRow(correctedTranslation) } else { - pullTargets(translation, canSwipedBeDismissed = false) + pullTargets(correctedTranslation, canSwipedBeDismissed = false) } } State.DETACHED -> { - val swiped = currentMagneticListeners.swipedListener() - swiped?.setMagneticTranslation(translation) + translateDetachedRow(correctedTranslation) } } return true } - private fun updateRoundness(translation: Float) { + private fun updateRoundness(translation: Float, animate: Boolean = false) { val normalizedTranslation = abs(swipedRowMultiplier * translation) / magneticDetachThreshold notificationRoundnessManager.setRoundnessForAffectedViews( /* roundness */ normalizedTranslation.coerceIn(0f, MAX_PRE_DETACH_ROUNDNESS), - /* animate */ false, + animate, ) } @@ -232,7 +246,28 @@ constructor( ) } + private fun translateDetachedRow(translation: Float) { + val targetTranslation = swipedRowMultiplier * translation + val crossedThreshold = abs(targetTranslation) <= magneticAttachThreshold + if (crossedThreshold) { + translationOffset += translation + updateRoundness(translation = 0f, animate = true) + currentMagneticListeners.swipedListener()?.let { attach(it) } + currentState = State.PULLING + } else { + val swiped = currentMagneticListeners.swipedListener() + swiped?.setMagneticTranslation(translation, trackEagerly = false) + } + } + + private fun attach(listener: MagneticRowListener) { + listener.cancelMagneticAnimations() + listener.triggerMagneticForce(endTranslation = 0f, attachForce) + msdlPlayer.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR) + } + override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) { + translationOffset = 0f if (row.isSwipedTarget()) { when (currentState) { State.PULLING -> { @@ -254,9 +289,13 @@ constructor( } } + override fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean = + row.isSwipedTarget() && currentState == State.DETACHED + override fun resetRoundness() = notificationRoundnessManager.clear() override fun reset() { + translationOffset = 0f currentMagneticListeners.forEach { it?.cancelMagneticAnimations() it?.cancelTranslationAnimations() @@ -300,6 +339,8 @@ constructor( private const val DETACH_DAMPING_RATIO = 0.95f private const val SNAP_BACK_STIFFNESS = 550f private const val SNAP_BACK_DAMPING_RATIO = 0.6f + private const val ATTACH_STIFFNESS = 800f + private const val ATTACH_DAMPING_RATIO = 0.95f // Maximum value of corner roundness that gets applied during the pre-detach dragging private const val MAX_PRE_DETACH_ROUNDNESS = 0.8f diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt index 5959ef1e093b..344dab4369f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt @@ -21,8 +21,17 @@ import androidx.dynamicanimation.animation.SpringForce /** A listener that responds to magnetic forces applied to an [ExpandableNotificationRow] */ interface MagneticRowListener { - /** Set a translation due to a magnetic attachment. */ - fun setMagneticTranslation(translation: Float) + /** + * Set a translation due to a magnetic attachment. + * + * If a magnetic animation is running, [trackEagerly] decides if the new translation is applied + * immediately or if the animation finishes first. When applying the translation immediately, + * the change in translation must be greater than a touch slop threshold. + * + * @param[translation] Incoming gesture translation. + * @param[trackEagerly] Whether we eagerly track the incoming translation or not. + */ + fun setMagneticTranslation(translation: Float, trackEagerly: Boolean = true) /** * Trigger the magnetic behavior when the row detaches or snaps back from its magnetic diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index f3d8ee245540..612c19fc6696 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -485,6 +485,15 @@ public class NotificationStackScrollLayoutController implements Dumpable { } } + @Override + public boolean isMagneticViewDetached(View view) { + if (view instanceof ExpandableNotificationRow row) { + return mMagneticNotificationRowManager.isMagneticRowSwipeDetached(row); + } else { + return false; + } + } + @Override public float getTotalTranslationLength(View animView) { return mView.getTotalTranslationLength(animView); @@ -492,9 +501,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onDensityScaleChange(float density) { - mMagneticNotificationRowManager.setSwipeThresholdPx( - density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP - ); + mMagneticNotificationRowManager.onDensityChange(density); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index c5a846e1da05..5105e55b0a5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -255,12 +255,13 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc int menuSnapTarget = menuRow.getMenuSnapTarget(); boolean isNonFalseMenuRevealingGesture = isMenuRevealingGestureAwayFromMenu && !isFalseGesture(); + boolean isMagneticViewDetached = mCallback.isMagneticViewDetached(animView); if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture) && menuSnapTarget != 0) { // Menu has not been snapped to previously and this is menu revealing gesture snapOpen(animView, menuSnapTarget, velocity); menuRow.onSnapOpen(); - } else if (isDismissGesture && !gestureTowardsMenu) { + } else if (isDismissGesture && (!gestureTowardsMenu || isMagneticViewDetached)) { dismiss(animView, velocity); menuRow.onDismiss(); } else { @@ -272,6 +273,7 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc private void handleSwipeFromOpenState(MotionEvent ev, View animView, float velocity, NotificationMenuRowPlugin menuRow) { boolean isDismissGesture = isDismissGesture(ev); + boolean isMagneticViewDetached = mCallback.isMagneticViewDetached(animView); final boolean withinSnapMenuThreshold = menuRow.isWithinSnapMenuThreshold(); @@ -280,7 +282,7 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc // Haven't moved enough to unsnap from the menu menuRow.onSnapOpen(); snapOpen(animView, menuRow.getMenuSnapTarget(), velocity); - } else if (isDismissGesture && !menuRow.shouldSnapBack()) { + } else if (isDismissGesture && (!menuRow.shouldSnapBack() || isMagneticViewDetached)) { // Only dismiss if we're not moving towards the menu dismiss(animView, velocity); menuRow.onDismiss(); -- cgit v1.2.3-59-g8ed1b