summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2025-03-24 12:54:30 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2025-03-24 12:54:30 -0700
commit7c41f9bb59a72f0f0c369d8a82bffbbf41b49dd2 (patch)
tree57d821b2d91f9b65724ce956b3c69e799d8625b8 /packages/SystemUI/src
parentb552211dfbf687d17b95da2b60e24511846ce82a (diff)
parentb9b6d74e153859d710946df9f0ac2b3b2c001564 (diff)
Merge "Modifiying the rules that determine magnetic dismissibility" into main
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java6
5 files changed, 115 insertions, 16 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index d017754ae653..b131534fe818 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -792,7 +792,8 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
/** Can the swipe gesture on the touched view be considered as a dismiss intention */
public boolean isSwipeDismissible() {
if (magneticNotificationSwipes()) {
- return mCallback.isMagneticViewDetached(mTouchedView) || swipedFastEnough();
+ float velocity = getVelocity(mVelocityTracker);
+ return mCallback.isMagneticViewDismissible(mTouchedView, velocity);
} else {
return swipedFastEnough() || swipedFarEnough();
}
@@ -978,11 +979,14 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
void onMagneticInteractionEnd(View view, float velocity);
/**
- * Determine if a view managed by magnetic interactions is magnetically detached
+ * Determine if a view managed by magnetic interactions is dismissible when being swiped by
+ * a touch drag gesture.
+ *
* @param view The magnetic view
- * @return if the view is detached according to its magnetic state.
+ * @param endVelocity The velocity of the drag that is moving the magnetic view
+ * @return if the view is dismissible according to its magnetic logic.
*/
- boolean isMagneticViewDetached(View view);
+ boolean isMagneticViewDismissible(View view, float endVelocity);
/**
* 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/stack/MagneticNotificationRowManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
index 48cff7497e3c..9bd9a0bb0089 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
@@ -87,8 +87,10 @@ interface MagneticNotificationRowManager {
*/
fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float? = null)
- /** Determine if the given [ExpandableNotificationRow] has been magnetically detached. */
- fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean
+ /**
+ * Determine if a magnetic row swiped is dismissible according to the end velocity of the swipe.
+ */
+ fun isMagneticRowSwipedDismissible(row: ExpandableNotificationRow, endVelocity: Float): Boolean
/* Reset any roundness that magnetic targets may have */
fun resetRoundness()
@@ -133,8 +135,9 @@ interface MagneticNotificationRowManager {
velocity: Float?,
) {}
- override fun isMagneticRowSwipeDetached(
- row: ExpandableNotificationRow
+ override fun isMagneticRowSwipedDismissible(
+ row: ExpandableNotificationRow,
+ endVelocity: Float,
): Boolean = false
override fun resetRoundness() {}
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 5c52500b7f70..03ede2fdc12f 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
@@ -27,6 +27,7 @@ import com.google.android.msdl.domain.MSDLPlayer
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.pow
+import kotlin.math.sign
import org.jetbrains.annotations.TestOnly
@SysUISingleton
@@ -72,11 +73,16 @@ constructor(
*/
private var translationOffset = 0f
+ private var dismissVelocity = 0f
+
+ private val detachDirectionEstimator = DirectionEstimator()
+
override fun onDensityChange(density: Float) {
magneticDetachThreshold =
density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
magneticAttachThreshold =
density * MagneticNotificationRowManager.MAGNETIC_ATTACH_THRESHOLD_DP
+ dismissVelocity = density * DISMISS_VELOCITY
}
override fun setMagneticAndRoundableTargets(
@@ -86,6 +92,7 @@ constructor(
) {
if (currentState == State.IDLE) {
translationOffset = 0f
+ detachDirectionEstimator.reset()
updateMagneticAndRoundableTargets(swipingRow, stackScrollLayout, sectionsManager)
currentState = State.TARGETS_SET
} else {
@@ -142,10 +149,12 @@ constructor(
return false
}
State.TARGETS_SET -> {
+ detachDirectionEstimator.recordTranslation(correctedTranslation)
pullTargets(correctedTranslation, canTargetBeDismissed)
currentState = State.PULLING
}
State.PULLING -> {
+ detachDirectionEstimator.recordTranslation(correctedTranslation)
updateRoundness(correctedTranslation)
if (canTargetBeDismissed) {
pullDismissibleRow(correctedTranslation)
@@ -154,6 +163,7 @@ constructor(
}
}
State.DETACHED -> {
+ detachDirectionEstimator.recordTranslation(correctedTranslation)
translateDetachedRow(correctedTranslation)
}
}
@@ -171,6 +181,7 @@ constructor(
private fun pullDismissibleRow(translation: Float) {
val crossedThreshold = abs(translation) >= magneticDetachThreshold
if (crossedThreshold) {
+ detachDirectionEstimator.halt()
snapNeighborsBack()
currentMagneticListeners.swipedListener()?.let { detach(it, translation) }
currentState = State.DETACHED
@@ -249,6 +260,7 @@ constructor(
val crossedThreshold = abs(translation) <= magneticAttachThreshold
if (crossedThreshold) {
translationOffset += translation
+ detachDirectionEstimator.reset()
updateRoundness(translation = 0f, animate = true)
currentMagneticListeners.swipedListener()?.let { attach(it) }
currentState = State.PULLING
@@ -266,6 +278,7 @@ constructor(
override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) {
translationOffset = 0f
+ detachDirectionEstimator.reset()
if (row.isSwipedTarget()) {
when (currentState) {
State.TARGETS_SET -> currentState = State.IDLE
@@ -288,13 +301,28 @@ constructor(
}
}
- override fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean =
- row.isSwipedTarget() && currentState == State.DETACHED
+ override fun isMagneticRowSwipedDismissible(
+ row: ExpandableNotificationRow,
+ endVelocity: Float,
+ ): Boolean {
+ if (!row.isSwipedTarget()) return false
+ val isEndVelocityLargeEnough = abs(endVelocity) >= dismissVelocity
+ val shouldSnapBack =
+ isEndVelocityLargeEnough && detachDirectionEstimator.direction != sign(endVelocity)
+
+ return when (currentState) {
+ State.IDLE,
+ State.TARGETS_SET,
+ State.PULLING -> isEndVelocityLargeEnough
+ State.DETACHED -> !shouldSnapBack
+ }
+ }
override fun resetRoundness() = notificationRoundnessManager.clear()
override fun reset() {
translationOffset = 0f
+ detachDirectionEstimator.reset()
currentMagneticListeners.forEach {
it?.cancelMagneticAnimations()
it?.cancelTranslationAnimations()
@@ -315,6 +343,69 @@ constructor(
private fun NotificationRoundnessManager.setRoundableTargets(targets: RoundableTargets) =
setViewsAffectedBySwipe(targets.before, targets.swiped, targets.after)
+ /**
+ * A class to estimate the direction of a gesture translations with a moving average.
+ *
+ * The class holds a buffer that stores translations. When requested, the direction of movement
+ * is estimated as the sign of the average value from the buffer.
+ */
+ class DirectionEstimator {
+
+ // A buffer to hold past translations. This is used as a FIFO structure with a fixed size.
+ private val translationBuffer = ArrayDeque<Float>()
+
+ /**
+ * The estimated direction of the translations. It will be estimated as the average of the
+ * values in the [translationBuffer] and set only once when the estimator is halted.
+ */
+ var direction = 0f
+ private set
+
+ private var acceptTranslations = true
+
+ /**
+ * Add a new translation to the [translationBuffer] if we are still accepting translations
+ * (see [halt]). If the buffer is full, we remove the last value and add the new one to the
+ * end.
+ */
+ fun recordTranslation(translation: Float) {
+ if (!acceptTranslations) return
+
+ if (translationBuffer.size == TRANSLATION_BUFFER_SIZE) {
+ translationBuffer.removeFirst()
+ }
+ translationBuffer.addLast(translation)
+ }
+
+ /**
+ * Halt the operation of the estimator.
+ *
+ * This stops the estimator from receiving new translations and derives the estimated
+ * direction. This is the sign of the average value from the available data in the
+ * [translationBuffer].
+ */
+ fun halt() {
+ acceptTranslations = false
+ direction = translationBuffer.mean()
+ }
+
+ fun reset() {
+ translationBuffer.clear()
+ acceptTranslations = true
+ }
+
+ private fun ArrayDeque<Float>.mean(): Float =
+ if (isEmpty()) {
+ 0f
+ } else {
+ sign(sum() / translationBuffer.size)
+ }
+
+ companion object {
+ private const val TRANSLATION_BUFFER_SIZE = 10
+ }
+ }
+
enum class State {
IDLE,
TARGETS_SET,
@@ -341,6 +432,8 @@ constructor(
private const val ATTACH_STIFFNESS = 800f
private const val ATTACH_DAMPING_RATIO = 0.95f
+ private const val DISMISS_VELOCITY = 500 // in dp/sec
+
// 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/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index db56718e9f22..8363fbfcacf2 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
@@ -497,9 +497,10 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
@Override
- public boolean isMagneticViewDetached(View view) {
+ public boolean isMagneticViewDismissible(View view, float endVelocity) {
if (view instanceof ExpandableNotificationRow row) {
- return mMagneticNotificationRowManager.isMagneticRowSwipeDetached(row);
+ return mMagneticNotificationRowManager.isMagneticRowSwipedDismissible(row,
+ endVelocity);
} else {
return false;
}
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 5105e55b0a5c..d0096da8b269 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,13 +255,12 @@ 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 || isMagneticViewDetached)) {
+ } else if (isDismissGesture && (!gestureTowardsMenu || isSwipeDismissible())) {
dismiss(animView, velocity);
menuRow.onDismiss();
} else {
@@ -273,7 +272,6 @@ 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();
@@ -282,7 +280,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() || isMagneticViewDetached)) {
+ } else if (isDismissGesture && (!menuRow.shouldSnapBack() || isSwipeDismissible())) {
// Only dismiss if we're not moving towards the menu
dismiss(animView, velocity);
menuRow.onDismiss();