Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 68b0b534 authored by Juan Sebastian Martinez's avatar Juan Sebastian Martinez
Browse files

Playing dismiss haptics when flinging to dismiss but without detaching.

The dismiss haptics that play when a magnetic notification detaches
should also play in the event that the user flings to dismiss a
notification, but the notitication hasn't magnetically detached. This
change adds this feedback and makes the dismiss cases fully consistent
in terms of haptic feedback.

Test: added Unit tests
Test: manual. Verified that the dismiss haptics are felt when the user
  flings a notification to dismiss it without previously dragging it
  beyond the magnetic detach threshold.
Flag: com.android.systemui.magnetic_notification_swipes
Bug: 411389706
Change-Id: I28f7c754f6489c837df4be876a71154c1d53704c
parent 2bbc91da
Loading
Loading
Loading
Loading
+58 −5
Original line number Original line Diff line number Diff line
@@ -16,12 +16,18 @@


package com.android.systemui.statusbar.notification.stack
package com.android.systemui.statusbar.notification.stack


import android.os.VibrationEffect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.dynamicanimation.animation.SpringForce
import androidx.dynamicanimation.animation.SpringForce
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.fakeVibratorHelper
import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.createRowGroup
import com.android.systemui.statusbar.notification.row.createRowGroup
@@ -46,6 +52,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
    private val stackScrollLayout = mock<NotificationStackScrollLayout>()
    private val stackScrollLayout = mock<NotificationStackScrollLayout>()
    private val sectionsManager = mock<NotificationSectionsManager>()
    private val sectionsManager = mock<NotificationSectionsManager>()
    private val msdlPlayer = kosmos.fakeMSDLPlayer
    private val msdlPlayer = kosmos.fakeMSDLPlayer
    private val vibratorHelper = kosmos.fakeVibratorHelper
    private var canRowBeDismissed = true
    private var canRowBeDismissed = true
    private var magneticAnimationsCancelled = MutableList(childrenNumber) { false }
    private var magneticAnimationsCancelled = MutableList(childrenNumber) { false }


@@ -235,7 +242,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
            underTest.setMagneticRowTranslation(swipedRow, translation = 100f)
            underTest.setMagneticRowTranslation(swipedRow, translation = 100f)


            // WHEN the interaction ends on the row
            // WHEN the interaction ends on the row
            underTest.onMagneticInteractionEnd(swipedRow, velocity = null)
            underTest.onMagneticInteractionEnd(swipedRow, dismissing = false, velocity = null)


            // THEN the state resets
            // THEN the state resets
            assertThat(underTest.currentState).isEqualTo(State.IDLE)
            assertThat(underTest.currentState).isEqualTo(State.IDLE)
@@ -248,7 +255,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
            setTargets()
            setTargets()


            // WHEN the interaction ends on the row
            // WHEN the interaction ends on the row
            underTest.onMagneticInteractionEnd(swipedRow, velocity = null)
            underTest.onMagneticInteractionEnd(swipedRow, dismissing = false, velocity = null)


            // THEN the state resets
            // THEN the state resets
            assertThat(underTest.currentState).isEqualTo(State.IDLE)
            assertThat(underTest.currentState).isEqualTo(State.IDLE)
@@ -261,7 +268,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
            setDetachedState()
            setDetachedState()


            // WHEN the interaction ends on the row
            // WHEN the interaction ends on the row
            underTest.onMagneticInteractionEnd(swipedRow, velocity = null)
            underTest.onMagneticInteractionEnd(swipedRow, dismissing = false, velocity = null)


            // THEN the state resets
            // THEN the state resets
            assertThat(underTest.currentState).isEqualTo(State.IDLE)
            assertThat(underTest.currentState).isEqualTo(State.IDLE)
@@ -274,7 +281,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
            setDetachedState()
            setDetachedState()


            // WHEN the interaction ends on the row
            // WHEN the interaction ends on the row
            underTest.onMagneticInteractionEnd(swipedRow, velocity = null)
            underTest.onMagneticInteractionEnd(swipedRow, dismissing = false, velocity = null)


            // THEN magnetic animations are cancelled
            // THEN magnetic animations are cancelled
            assertThat(magneticAnimationsCancelled[childrenNumber / 2]).isTrue()
            assertThat(magneticAnimationsCancelled[childrenNumber / 2]).isTrue()
@@ -290,12 +297,58 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
            setTargets()
            setTargets()


            // WHEN the interactionEnd is called on a target different from the swiped row
            // WHEN the interactionEnd is called on a target different from the swiped row
            underTest.onMagneticInteractionEnd(neighborRow, null)
            underTest.onMagneticInteractionEnd(neighborRow, dismissing = false, velocity = null)


            // THEN magnetic animations are cancelled
            // THEN magnetic animations are cancelled
            assertThat(magneticAnimationsCancelled[neighborIndex]).isTrue()
            assertThat(magneticAnimationsCancelled[neighborIndex]).isTrue()
        }
        }


    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun onMagneticInteractionEnd_whenPulling_fromDismiss_playsMSDLThresholdHaptics() =
        kosmos.testScope.runTest {
            // GIVEN a threshold of 100 px
            val threshold = 100f
            underTest.onDensityChange(
                threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
            )

            // GIVEN that targets are set and the swiped row is being pulled
            setTargets()
            underTest.setMagneticRowTranslation(swipedRow, translation = 100f)

            // WHEN the interaction ends on the row because it was dismissed
            underTest.onMagneticInteractionEnd(swipedRow, dismissing = true, velocity = null)

            // THEN threshold haptics play to indicate the dismissal
            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
        }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun onMagneticInteractionEnd_whenPulling_fromDismiss_playsThresholdVibration() =
        kosmos.testScope.runTest {
            // GIVEN a threshold of 100 px
            val threshold = 100f
            underTest.onDensityChange(
                threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
            )

            // GIVEN that targets are set and the swiped row is being pulled
            setTargets()
            underTest.setMagneticRowTranslation(swipedRow, translation = 100f)

            // WHEN the interaction ends on the row because it was dismissed
            underTest.onMagneticInteractionEnd(swipedRow, dismissing = true, velocity = null)

            // THEN threshold haptics play to indicate the dismissal
            val composition =
                VibrationEffect.startComposition()
                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.7f)
                    .compose()
            assertThat(vibratorHelper.hasVibratedWithEffects(composition)).isTrue()
        }

    @Test
    @Test
    fun onResetRoundness_swipedRoundableGetsCleared() =
    fun onResetRoundness_swipedRoundableGetsCleared() =
        kosmos.testScope.runTest {
        kosmos.testScope.runTest {
+5 −3
Original line number Original line Diff line number Diff line
@@ -464,7 +464,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
        };
        };


        Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
        Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
        mCallback.onMagneticInteractionEnd(animView, velocity);
        mCallback.onMagneticInteractionEnd(animView, /* dismissing = */ true, velocity);
        if (anim == null) {
        if (anim == null) {
            onDismissChildWithAnimationFinished();
            onDismissChildWithAnimationFinished();
            return;
            return;
@@ -754,7 +754,8 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
                        dismissChild(mTouchedView, velocity,
                        dismissChild(mTouchedView, velocity,
                                !swipedFastEnough() /* useAccelerateInterpolator */);
                                !swipedFastEnough() /* useAccelerateInterpolator */);
                    } else {
                    } else {
                        mCallback.onMagneticInteractionEnd(mTouchedView, velocity);
                        mCallback.onMagneticInteractionEnd(mTouchedView, /* dismissing = */ false,
                                velocity);
                        mCallback.onDragCancelled(mTouchedView);
                        mCallback.onDragCancelled(mTouchedView);
                        snapChild(mTouchedView, 0 /* leftTarget */, velocity);
                        snapChild(mTouchedView, 0 /* leftTarget */, velocity);
                    }
                    }
@@ -985,9 +986,10 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
         * This method should be called when a view will snap back or be dismissed.
         * This method should be called when a view will snap back or be dismissed.
         *
         *
         * @param view The {@link  View} whose magnetic interaction ended.
         * @param view The {@link  View} whose magnetic interaction ended.
         * @param dismissing If the interaction ended with the view being dismissed.
         * @param velocity The velocity when the interaction ended.
         * @param velocity The velocity when the interaction ended.
         */
         */
        void onMagneticInteractionEnd(View view, float velocity);
        void onMagneticInteractionEnd(View view, boolean dismissing, float velocity);


        /**
        /**
         * Determine if a view managed by magnetic interactions is dismissible when being swiped by
         * Determine if a view managed by magnetic interactions is dismissible when being swiped by
+8 −1
Original line number Original line Diff line number Diff line
@@ -85,10 +85,16 @@ interface MagneticNotificationRowManager {
     * after calls to [setMagneticRowTranslation].
     * after calls to [setMagneticRowTranslation].
     *
     *
     * @param[row] [ExpandableNotificationRow] that stopped whose interaction stopped.
     * @param[row] [ExpandableNotificationRow] that stopped whose interaction stopped.
     * @param[dismissing] If the interaction ended because the row is dismissing. If false, it is
     *   assumed that the row is snapping back instead.
     * @param[velocity] Optional velocity at the end of the interaction. Use this to trigger
     * @param[velocity] Optional velocity at the end of the interaction. Use this to trigger
     *   animations with a start velocity.
     *   animations with a start velocity.
     */
     */
    fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float? = null)
    fun onMagneticInteractionEnd(
        row: ExpandableNotificationRow,
        dismissing: Boolean,
        velocity: Float? = null,
    )


    /**
    /**
     * Determine if a magnetic row swiped is dismissible according to the end velocity of the swipe.
     * Determine if a magnetic row swiped is dismissible according to the end velocity of the swipe.
@@ -144,6 +150,7 @@ interface MagneticNotificationRowManager {


                    override fun onMagneticInteractionEnd(
                    override fun onMagneticInteractionEnd(
                        row: ExpandableNotificationRow,
                        row: ExpandableNotificationRow,
                        dismissing: Boolean,
                        velocity: Float?,
                        velocity: Float?,
                    ) {}
                    ) {}


+8 −1
Original line number Original line Diff line number Diff line
@@ -336,12 +336,19 @@ constructor(
        detachDirectionEstimator.reset()
        detachDirectionEstimator.reset()
    }
    }


    override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) {
    override fun onMagneticInteractionEnd(
        row: ExpandableNotificationRow,
        dismissing: Boolean,
        velocity: Float?,
    ) {
        detachDirectionEstimator.reset()
        detachDirectionEstimator.reset()
        if (row.isSwipedTarget()) {
        if (row.isSwipedTarget()) {
            when (currentState) {
            when (currentState) {
                State.TARGETS_SET -> currentState = State.IDLE
                State.TARGETS_SET -> currentState = State.IDLE
                State.PULLING -> {
                State.PULLING -> {
                    if (dismissing) {
                        playThresholdHaptics()
                    }
                    snapNeighborsBack(velocity)
                    snapNeighborsBack(velocity)
                    currentState = State.IDLE
                    currentState = State.IDLE
                }
                }
+4 −2
Original line number Original line Diff line number Diff line
@@ -503,9 +503,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
                }
                }


                @Override
                @Override
                public void onMagneticInteractionEnd(View view, float velocity) {
                public void onMagneticInteractionEnd(View view, boolean dismissing,
                        float velocity) {
                    if (view instanceof ExpandableNotificationRow row) {
                    if (view instanceof ExpandableNotificationRow row) {
                        mMagneticNotificationRowManager.onMagneticInteractionEnd(row, velocity);
                        mMagneticNotificationRowManager.onMagneticInteractionEnd(row, dismissing,
                                velocity);
                    }
                    }
                }
                }


Loading