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

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

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
parent e30124bf
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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(
+40 −7
Original line number Diff line number Diff line
@@ -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(
+16 −0
Original line number Diff line number Diff line
@@ -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();
+18 −3
Original line number Diff line number Diff line
@@ -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.
         *
+21 −4
Original line number Diff line number Diff line
@@ -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,15 +113,28 @@ 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()) {
        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);
            }
        }

        @Override
@@ -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
Loading