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

Commit ce9d014a authored by Juan Sebastian Martinez's avatar Juan Sebastian Martinez
Browse files

Magnetic Swipe Refactor to better handle edge cases.

For cases in which magnetic interaction ends during magnetic animations,
we make sure that the corresponding magnetic and external animations are
cancelled or coordinate to avoid flickering behaviors. This involves
cases when notifications are swiped away by the system when the magnetic
behavior is active (e.g., clicking "clear all")

Test: MagneticRowManagerImplTest
Test: manual. Verified correct animations in edge cases above.
flag: com.android.systemui.magnetic_notification_swipes
Bug: 392752275
Change-Id: I961384df9167960985fd031d6ff8bc7d5ea4b817
parent 5a836187
Loading
Loading
Loading
Loading
+36 −1
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
    private val sectionsManager = mock<NotificationSectionsManager>()
    private val msdlPlayer = kosmos.fakeMSDLPlayer
    private var canRowBeDismissed = true
    private var magneticAnimationsCancelled = false

    private val underTest = kosmos.magneticNotificationRowManagerImpl

@@ -64,6 +65,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
        children = notificationTestHelper.createGroup(childrenNumber).childrenContainer
        swipedRow = children.attachedChildren[childrenNumber / 2]
        configureMagneticRowListener(swipedRow)
        magneticAnimationsCancelled = false
    }

    @Test
@@ -247,6 +249,35 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
            assertThat(underTest.currentState).isEqualTo(State.IDLE)
        }

    @Test
    fun onMagneticInteractionEnd_whenDetached_cancelsMagneticAnimations() =
        kosmos.testScope.runTest {
            // GIVEN the swiped row is detached
            setDetachedState()

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

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

    @Test
    fun onMagneticInteractionEnd_forMagneticNeighbor_cancelsMagneticAnimations() =
        kosmos.testScope.runTest {
            val neighborRow = children.attachedChildren[childrenNumber / 2 - 1]
            configureMagneticRowListener(neighborRow)

            // GIVEN that targets are set
            setTargets()

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

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

    private fun setDetachedState() {
        val threshold = 100f
        underTest.setSwipeThresholdPx(threshold)
@@ -284,7 +315,11 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
                    startVelocity: Float,
                ) {}

                override fun cancelMagneticAnimations() {}
                override fun cancelMagneticAnimations() {
                    magneticAnimationsCancelled = true
                }

                override fun cancelTranslationAnimations() {}

                override fun canRowBeDismissed(): Boolean = canRowBeDismissed
            }
+3 −3
Original line number Diff line number Diff line
@@ -405,7 +405,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
        doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 0, 0);
        mSwipeHelper.snapChild(mNotificationRow, 0, 0);

        verify(mCallback, times(1)).onDragCancelledWithVelocity(mNotificationRow, 0);
        verify(mCallback, times(1)).onDragCancelled(mNotificationRow);
        verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 0, 0);
        verify(mSwipeHelper, times(1)).handleMenuCoveredOrDismissed();
    }
@@ -416,7 +416,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
        doNothing().when(mSwipeHelper).superSnapChild(mNotificationRow, 10, 0);
        mSwipeHelper.snapChild(mNotificationRow, 10, 0);

        verify(mCallback, times(1)).onDragCancelledWithVelocity(mNotificationRow, 0);
        verify(mCallback, times(1)).onDragCancelled(mNotificationRow);
        verify(mSwipeHelper, times(1)).superSnapChild(mNotificationRow, 10, 0);
        verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed();
    }
@@ -426,7 +426,7 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
        doNothing().when(mSwipeHelper).superSnapChild(mView, 10, 0);
        mSwipeHelper.snapChild(mView, 10, 0);

        verify(mCallback).onDragCancelledWithVelocity(mView, 0);
        verify(mCallback).onDragCancelled(mView);
        verify(mSwipeHelper, never()).superSnapChild(mView, 10, 0);
    }

+16 −7
Original line number Diff line number Diff line
@@ -350,6 +350,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
                            && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
                        if (mCallback.canChildBeDragged(mTouchedView)) {
                            mIsSwiping = true;
                            mCallback.setMagneticAndRoundableTargets(mTouchedView);
                            mCallback.onBeginDrag(mTouchedView);
                            mInitialTouchPos = getPos(ev);
                            mTranslation = getTranslation(mTouchedView);
@@ -442,6 +443,7 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
        };

        Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
        mCallback.onMagneticInteractionEnd(animView, velocity);
        if (anim == null) {
            onDismissChildWithAnimationFinished();
            return;
@@ -724,7 +726,8 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
                        dismissChild(mTouchedView, velocity,
                                !swipedFastEnough() /* useAccelerateInterpolator */);
                    } else {
                        mCallback.onDragCancelledWithVelocity(mTouchedView, velocity);
                        mCallback.onMagneticInteractionEnd(mTouchedView, velocity);
                        mCallback.onDragCancelled(mTouchedView);
                        snapChild(mTouchedView, 0 /* leftTarget */, velocity);
                    }
                    mTouchedView = null;
@@ -926,18 +929,24 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {

        void onBeginDrag(View v);

        /**
         * Set magnetic and roundable targets for a view.
         */
        void setMagneticAndRoundableTargets(View v);

        void onChildDismissed(View v);

        void onDragCancelled(View v);

        /**
         * A drag operation has been cancelled on a view with a final velocity.
         * @param v View that was dragged.
         * @param finalVelocity Final velocity of the drag.
         * Notify that a magnetic interaction ended on a view with a velocity.
         * <p>
         * This method should be called when a view will snap back or be dismissed.
         *
         * @param view The {@link  View} whose magnetic interaction ended.
         * @param velocity The velocity when the interaction ended.
         */
        default void onDragCancelledWithVelocity(View v, float finalVelocity) {
            onDragCancelled(v);
        }
        void onMagneticInteractionEnd(View view, float velocity);

        /**
         * Called when the child is long pressed and available to start drag and drop.
+6 −2
Original line number Diff line number Diff line
@@ -106,7 +106,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
        @Override
        public void triggerMagneticForce(float endTranslation, @NonNull SpringForce springForce,
                float startVelocity) {
            cancelMagneticAnimations();
            cancelTranslationAnimations();
            mMagneticAnimator.setSpring(springForce);
            mMagneticAnimator.setStartVelocity(startVelocity);
            mMagneticAnimator.animateToFinalPosition(endTranslation);
@@ -114,10 +114,14 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro

        @Override
        public void cancelMagneticAnimations() {
            cancelTranslationAnimations();
            mMagneticAnimator.cancel();
        }

        @Override
        public void cancelTranslationAnimations() {
            ExpandableView.this.cancelTranslationAnimations();
        }

        @Override
        public boolean canRowBeDismissed() {
            return canExpandableViewBeDismissed();
+22 −11
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ constructor(
                stackScrollLayout,
                MAGNETIC_TRANSLATION_MULTIPLIERS.size,
            )
        currentMagneticListeners.swipedListener()?.cancelTranslationAnimations()
        newListeners.forEach {
            if (currentMagneticListeners.contains(it)) {
                it?.cancelMagneticAnimations()
@@ -214,22 +215,32 @@ constructor(
    }

    override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) {
        if (!row.isSwipedTarget()) return

        if (row.isSwipedTarget()) {
            when (currentState) {
                State.PULLING -> {
                    snapNeighborsBack(velocity)
                    currentState = State.IDLE
                }
                State.DETACHED -> {
                    // Cancel any detaching animation that may be occurring
                    currentMagneticListeners.swipedListener()?.cancelMagneticAnimations()
                    currentState = State.IDLE
                }
                else -> {}
            }
        } else {
            // A magnetic neighbor may be dismissing. In this case, we need to cancel any snap back
            // magnetic animation to let the external dismiss animation proceed.
            val listener = currentMagneticListeners.find { it == row.magneticRowListener }
            listener?.cancelMagneticAnimations()
        }
    }

    override fun reset() {
        currentMagneticListeners.forEach { it?.cancelMagneticAnimations() }
        currentMagneticListeners.forEach {
            it?.cancelMagneticAnimations()
            it?.cancelTranslationAnimations()
        }
        currentState = State.IDLE
        currentMagneticListeners = listOf()
        currentRoundableTargets = null
Loading