Merge "Add content fade to notification dismissal" into tm-qpr-dev am: 99f5cefd6c am: 64a9cdfd4a

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19900962

Change-Id: Ica081369833b160f45c549457159b16d62973b8c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 37549c9..9188ce0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -337,9 +337,6 @@
      have been scrolled off-screen. -->
     <bool name="config_showNotificationShelf">true</bool>
 
-    <!-- Whether or not the notifications should always fade as they are dismissed. -->
-    <bool name="config_fadeNotificationsOnDismiss">false</bool>
-
     <!-- Whether or not the fade on the notification is based on the amount that it has been swiped
          off-screen. -->
     <bool name="config_fadeDependingOnAmountSwiped">false</bool>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index fe6dbe5..873a695 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -38,6 +38,8 @@
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -66,7 +68,7 @@
     private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
     private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
 
-    static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
+    public static final float SWIPE_PROGRESS_FADE_END = 0.6f; // fraction of thumbnail width
                                               // beyond which swipe progress->0
     public static final float SWIPED_FAR_ENOUGH_SIZE_FRACTION = 0.6f;
     static final float MAX_SCROLL_SIZE_FRACTION = 0.3f;
@@ -235,7 +237,11 @@
         return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
     }
 
-    private float getSwipeAlpha(float progress) {
+    /**
+     * Returns the alpha value depending on the progress of the swipe.
+     */
+    @VisibleForTesting
+    public float getSwipeAlpha(float progress) {
         if (mFadeDependingOnAmountSwiped) {
             // The more progress has been fade, the lower the alpha value so that the view fades.
             return Math.max(1 - progress, 0);
@@ -260,7 +266,7 @@
                         animView.setLayerType(View.LAYER_TYPE_NONE, null);
                     }
                 }
-                animView.setAlpha(getSwipeAlpha(swipeProgress));
+                updateSwipeProgressAlpha(animView, getSwipeAlpha(swipeProgress));
             }
         }
         invalidateGlobalRegion(animView);
@@ -561,6 +567,14 @@
         mCallback.onChildSnappedBack(animView, targetLeft);
     }
 
+
+    /**
+     * Called to update the content alpha while the view is swiped
+     */
+    protected void updateSwipeProgressAlpha(View animView, float alpha) {
+        animView.setAlpha(alpha);
+    }
+
     /**
      * Give the swipe helper itself a chance to do something on snap back so NSSL doesn't have
      * to tell us what to do
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index c9f19fc..aa57175 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -71,7 +71,9 @@
     public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
             false);
 
-    // next id: 112
+    public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true);
+
+    // next id: 114
 
     /***************************************/
     // 200 - keyguard/lockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 6138265..07ed013 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1477,6 +1477,20 @@
         }
     }
 
+    /**
+     * Sets the alpha on the content, while leaving the background of the row itself as is.
+     *
+     * @param alpha alpha value to apply to the notification content
+     */
+    public void setContentAlpha(float alpha) {
+        for (NotificationContentView l : mLayouts) {
+            l.setAlpha(alpha);
+        }
+        if (mChildrenContainer != null) {
+            mChildrenContainer.setAlpha(alpha);
+        }
+    }
+
     public void setIsLowPriority(boolean isLowPriority) {
         mIsLowPriority = isLowPriority;
         mPrivateLayout.setIsLowPriority(isLowPriority);
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 843a9ff..5c09d61 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
@@ -56,12 +56,13 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -178,6 +179,7 @@
     @Nullable private Boolean mHistoryEnabled;
     private int mBarState;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
+    private final FeatureFlags mFeatureFlags;
 
     private View mLongPressedView;
 
@@ -639,7 +641,8 @@
             InteractionJankMonitor jankMonitor,
             StackStateLogger stackLogger,
             NotificationStackScrollLogger logger,
-            NotificationStackSizeCalculator notificationStackSizeCalculator) {
+            NotificationStackSizeCalculator notificationStackSizeCalculator,
+            FeatureFlags featureFlags) {
         mStackStateLogger = stackLogger;
         mLogger = logger;
         mAllowLongPress = allowLongPress;
@@ -675,6 +678,7 @@
         mUiEventLogger = uiEventLogger;
         mRemoteInputManager = remoteInputManager;
         mShadeController = shadeController;
+        mFeatureFlags = featureFlags;
         updateResources();
     }
 
@@ -739,9 +743,7 @@
 
         mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
 
-        mFadeNotificationsOnDismiss =  // TODO: this should probably be injected directly
-                mResources.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
-
+        mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
         mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
         mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
 
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 2d2fbe5..ee57411 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
@@ -185,6 +185,13 @@
         return false;
     }
 
+    @Override
+    protected void updateSwipeProgressAlpha(View animView, float alpha) {
+        if (animView instanceof ExpandableNotificationRow) {
+            ((ExpandableNotificationRow) animView).setContentAlpha(alpha);
+        }
+    }
+
     @VisibleForTesting
     protected void handleMenuRowSwipe(MotionEvent ev, View animView, float velocity,
             NotificationMenuRowPlugin menuRow) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 8be9eb5..1c9b0be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
@@ -127,6 +128,7 @@
     @Mock private NotificationStackScrollLogger mLogger;
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
     @Mock private ShadeTransitionController mShadeTransitionController;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -174,7 +176,8 @@
                 mJankMonitor,
                 mStackLogger,
                 mLogger,
-                mNotificationStackSizeCalculator
+                mNotificationStackSizeCalculator,
+                mFeatureFlags
         );
 
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 1305d79..4ea1c71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -18,10 +18,13 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -51,10 +54,15 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.mockito.stubbing.Answer;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
 /**
  * Tests for {@link NotificationSwipeHelper}.
  */
@@ -74,7 +82,11 @@
     private Runnable mFalsingCheck;
     private FeatureFlags mFeatureFlags;
 
-    @Rule public MockitoRule mockito = MockitoJUnit.rule();
+    private static final int FAKE_ROW_WIDTH = 20;
+    private static final int FAKE_ROW_HEIGHT = 20;
+
+    @Rule
+    public MockitoRule mockito = MockitoJUnit.rule();
 
     @Before
     public void setUp() throws Exception {
@@ -444,8 +456,8 @@
         doReturn(5f).when(mEvent).getRawX();
         doReturn(10f).when(mEvent).getRawY();
 
-        doReturn(20).when(mView).getWidth();
-        doReturn(20).when(mView).getHeight();
+        doReturn(FAKE_ROW_WIDTH).when(mView).getWidth();
+        doReturn(FAKE_ROW_HEIGHT).when(mView).getHeight();
 
         Answer answer = (Answer) invocation -> {
             int[] arr = invocation.getArgument(0);
@@ -472,8 +484,8 @@
         doReturn(5f).when(mEvent).getRawX();
         doReturn(10f).when(mEvent).getRawY();
 
-        doReturn(20).when(mNotificationRow).getWidth();
-        doReturn(20).when(mNotificationRow).getActualHeight();
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getWidth();
+        doReturn(FAKE_ROW_HEIGHT).when(mNotificationRow).getActualHeight();
 
         Answer answer = (Answer) invocation -> {
             int[] arr = invocation.getArgument(0);
@@ -491,4 +503,56 @@
         assertFalse("Touch is not within the view",
                 mSwipeHelper.isTouchInView(mEvent, mNotificationRow));
     }
+
+    @Test
+    public void testContentAlphaRemainsUnchangedWhenNotificationIsNotDismissible() {
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+        mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, false);
+
+        verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+    }
+
+    @Test
+    public void testContentAlphaRemainsUnchangedWhenFeatureFlagIsDisabled() {
+
+        // Returning true prevents alpha fade. In an unmocked scenario the callback is instantiated
+        // within NotificationStackScrollLayoutController and returns the inverted value of the
+        // feature flag
+        doReturn(true).when(mCallback).updateSwipeProgress(any(), anyBoolean(), anyFloat());
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+        mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, true);
+
+        verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+    }
+
+    @Test
+    public void testContentAlphaFadeAnimationSpecs() {
+        // The alpha fade should be linear from 1f to 0f as translation progresses from 0 to 60% of
+        // view-width, and 0f at translations higher than that.
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+        List<Integer> translations = Arrays.asList(
+                -FAKE_ROW_WIDTH * 2,
+                -FAKE_ROW_WIDTH,
+                (int) (-FAKE_ROW_WIDTH * 0.3),
+                0,
+                (int) (FAKE_ROW_WIDTH * 0.3),
+                (int) (FAKE_ROW_WIDTH * 0.6),
+                FAKE_ROW_WIDTH,
+                FAKE_ROW_WIDTH * 2);
+        List<Float> expectedAlphas = translations.stream().map(translation ->
+                        mSwipeHelper.getSwipeAlpha(Math.abs((float) translation / FAKE_ROW_WIDTH)))
+                .collect(Collectors.toList());
+
+        for (Integer translation : translations) {
+            mSwipeHelper.onTranslationUpdate(mNotificationRow, translation, true);
+        }
+
+        ArgumentCaptor<Float> capturedValues = ArgumentCaptor.forClass(Float.class);
+        verify(mNotificationRow, times(translations.size())).setContentAlpha(
+                capturedValues.capture());
+        assertEquals(expectedAlphas, capturedValues.getAllValues());
+    }
 }