Align bubble behavior with DND settings.

As described at https://docs.google.com/presentation/d/1mPxqu8QBWpz-ieWkJGyLzBLcfAJNVUn8sxS1m58ogn8/edit#slide=id.g51f96ac1fd_25_230.

Test: atest SystemUITests
Fixes: 123540994
Change-Id: Ib45c10a2ae148806c8ddd766e987eb24473f0a39
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7e016bb..e9ac875 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.bubbles;
 
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
@@ -52,6 +55,7 @@
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
 import android.util.Log;
 import android.view.Display;
 import android.view.IPinnedStackController;
@@ -78,6 +82,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ZenModeController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
@@ -143,6 +148,7 @@
 
     // Bubbles get added to the status bar view
     private final StatusBarWindowController mStatusBarWindowController;
+    private final ZenModeController mZenModeController;
     private StatusBarStateListener mStatusBarStateListener;
 
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
@@ -204,17 +210,31 @@
     @Inject
     public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
             BubbleData data, ConfigurationController configurationController,
-            NotificationInterruptionStateProvider interruptionStateProvider) {
+            NotificationInterruptionStateProvider interruptionStateProvider,
+            ZenModeController zenModeController) {
         this(context, statusBarWindowController, data, null /* synchronizer */,
-                configurationController, interruptionStateProvider);
+                configurationController, interruptionStateProvider, zenModeController);
     }
 
     public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
             BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             ConfigurationController configurationController,
-            NotificationInterruptionStateProvider interruptionStateProvider) {
+            NotificationInterruptionStateProvider interruptionStateProvider,
+            ZenModeController zenModeController) {
         mContext = context;
         mNotificationInterruptionStateProvider = interruptionStateProvider;
+        mZenModeController = zenModeController;
+        mZenModeController.addCallback(new ZenModeController.Callback() {
+            @Override
+            public void onZenChanged(int zen) {
+                updateStackViewForZenConfig();
+            }
+
+            @Override
+            public void onConfigChanged(ZenModeConfig config) {
+                updateStackViewForZenConfig();
+            }
+        });
 
         configurationController.addCallback(this /* configurationListener */);
 
@@ -260,6 +280,8 @@
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
             }
+
+            updateStackViewForZenConfig();
         }
     }
 
@@ -566,6 +588,35 @@
     };
 
     /**
+     * Updates the stack view's suppression flags from the latest config from the zen (do not
+     * disturb) controller.
+     */
+    private void updateStackViewForZenConfig() {
+        final ZenModeConfig zenModeConfig = mZenModeController.getConfig();
+
+        if (zenModeConfig == null || mStackView == null) {
+            return;
+        }
+
+        final int suppressedEffects = zenModeConfig.suppressedVisualEffects;
+        final boolean hideNotificationDotsSelected =
+                (suppressedEffects & SUPPRESSED_EFFECT_BADGE) != 0;
+        final boolean dontPopNotifsOnScreenSelected =
+                (suppressedEffects & SUPPRESSED_EFFECT_PEEK) != 0;
+        final boolean hideFromPullDownShadeSelected =
+                (suppressedEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0;
+
+        final boolean dndEnabled = mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF;
+
+        mStackView.setSuppressNewDot(
+                dndEnabled && hideNotificationDotsSelected);
+        mStackView.setSuppressFlyout(
+                dndEnabled && (dontPopNotifsOnScreenSelected
+                        || hideFromPullDownShadeSelected));
+    }
+
+    /**
+     * Lets any listeners know if bubble state has changed.
      * Updates the visibility of the bubbles based on current state.
      * Does not un-bubble, just hides or un-hides. Notifies any
      * {@link BubbleStateChangeListener}s of visibility changes.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index bec90d2..0abd25d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -287,6 +287,9 @@
     private BubbleDismissView mDismissContainer;
     private Runnable mAfterMagnet;
 
+    private boolean mSuppressNewDot = false;
+    private boolean mSuppressFlyout = false;
+
     public BubbleStackView(Context context, BubbleData data,
                            @Nullable SurfaceSynchronizer synchronizer) {
         super(context);
@@ -690,6 +693,9 @@
         mBubbleContainer.addView(bubble.iconView, 0,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
         ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters);
+        if (bubble.iconView != null) {
+            bubble.iconView.setSuppressDot(mSuppressNewDot, false /* animate */);
+        }
         animateInFlyoutForBubble(bubble);
         requestUpdate();
         logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
@@ -1308,6 +1314,29 @@
         }
     }
 
+    /** Sets whether all bubbles in the stack should not show the 'new' dot. */
+    void setSuppressNewDot(boolean suppressNewDot) {
+        mSuppressNewDot = suppressNewDot;
+
+        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+            bv.setSuppressDot(suppressNewDot, true /* animate */);
+        }
+    }
+
+    /**
+     * Sets whether the flyout should not appear, even if the notif otherwise would generate one.
+     */
+    void setSuppressFlyout(boolean suppressFlyout) {
+        mSuppressFlyout = suppressFlyout;
+    }
+
+    /**
+     * Callback to run after the flyout hides. Also called if a new flyout is shown before the
+     * previous one animates out.
+     */
+    private Runnable mAfterFlyoutHides;
+
     /**
      * Animates in the flyout for the given bubble, if available, and then hides it after some time.
      */
@@ -1319,22 +1348,48 @@
         if (updateMessage != null
                 && !isExpanded()
                 && !mIsExpansionAnimating
-                && !mIsGestureInProgress) {
+                && !mIsGestureInProgress
+                && !mSuppressFlyout) {
             if (bubble.iconView != null) {
-                bubble.iconView.setSuppressDot(true /* suppressDot */, false /* animate */);
+                // Temporarily suppress the dot while the flyout is visible.
+                bubble.iconView.setSuppressDot(
+                        true /* suppressDot */, false /* animate */);
+
                 mFlyoutDragDeltaX = 0f;
                 mFlyout.setAlpha(0f);
 
+                if (mAfterFlyoutHides != null) {
+                    mAfterFlyoutHides.run();
+                }
+
+                mAfterFlyoutHides = () -> {
+                    if (bubble.iconView == null) {
+                        return;
+                    }
+
+                    // If we're going to suppress the dot, make it visible first so it'll
+                    // visibly animate away.
+                    if (mSuppressNewDot) {
+                        bubble.iconView.setSuppressDot(
+                                false /* suppressDot */, false /* animate */);
+                    }
+
+                    // Reset dot suppression. If we're not suppressing due to DND, then
+                    // stop suppressing it with no animation (since the flyout has
+                    // transformed into the dot). If we are suppressing due to DND, animate
+                    // it away.
+                    bubble.iconView.setSuppressDot(
+                            mSuppressNewDot /* suppressDot */,
+                            mSuppressNewDot /* animate */);
+                };
+
                 // Post in case layout isn't complete and getWidth returns 0.
                 post(() -> mFlyout.showFlyout(
                         updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
                         mStackAnimationController.isStackOnLeftSide(),
-                        bubble.iconView.getBadgeColor(),
-                        () -> {
-                            bubble.iconView.setSuppressDot(
-                                    false /* suppressDot */, false /* animate */);
-                        }));
+                        bubble.iconView.getBadgeColor(), mAfterFlyoutHides));
             }
+
             mFlyout.removeCallbacks(mHideFlyout);
             mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
             logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
@@ -1343,6 +1398,10 @@
 
     /** Hide the flyout immediately and cancel any pending hide runnables. */
     private void hideFlyoutImmediate() {
+        if (mAfterFlyoutHides != null) {
+            mAfterFlyoutHides.run();
+        }
+
         mFlyout.removeCallbacks(mHideFlyout);
         mFlyout.hideFlyout();
     }
@@ -1445,6 +1504,7 @@
         int bubbsCount = mBubbleContainer.getChildCount();
         for (int i = 0; i < bubbsCount; i++) {
             BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+            bv.updateDotVisibility(true /* animate */);
             bv.setZ((BubbleController.MAX_BUBBLES
                     * getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index aa32b94..6f1ed28 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -138,19 +138,6 @@
         updateDotVisibility(animate, null /* after */);
     }
 
-    /**
-     * Changes the dot's visibility to match the bubble view's state, running the provided callback
-     * after animation if requested.
-     */
-    void updateDotVisibility(boolean animate, Runnable after) {
-        boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot;
-
-        if (animate) {
-            animateDot(showDot, after);
-        } else {
-            mBadgedImageView.setShowDot(showDot);
-        }
-    }
 
     /**
      * Sets whether or not to hide the dot even if we'd otherwise show it. This is used while the
@@ -178,17 +165,34 @@
     }
 
     /**
+     * Changes the dot's visibility to match the bubble view's state, running the provided callback
+     * after animation if requested.
+     */
+    private void updateDotVisibility(boolean animate, Runnable after) {
+        boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot;
+
+        if (animate) {
+            animateDot(showDot, after);
+        } else {
+            mBadgedImageView.setShowDot(showDot);
+        }
+    }
+
+    /**
      * Animates the badge to show or hide.
      */
     private void animateDot(boolean showDot, Runnable after) {
         if (mBadgedImageView.isShowingDot() != showDot) {
-            mBadgedImageView.setShowDot(showDot);
+            if (showDot) {
+                mBadgedImageView.setShowDot(true);
+            }
+
             mBadgedImageView.clearAnimation();
             mBadgedImageView.animate().setDuration(200)
                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                     .setUpdateListener((valueAnimator) -> {
                         float fraction = valueAnimator.getAnimatedFraction();
-                        fraction = showDot ? fraction : 1 - fraction;
+                        fraction = showDot ? fraction : 1f - fraction;
                         mBadgedImageView.setDotScale(fraction);
                     }).withEndAction(() -> {
                         if (!showDot) {