Add flag to allowing the system to make BubbleMetadata for a notification

- Add experiment class to try and keep experimented related code in
- If notification has info to bubble & is allowed, make BubbleMetadata for
  it (but don't actually bubble)
- Options for only allowing messages to bubble
- Alter NotificationEntry to have settable BubbleMetadata
- Add notion of user created bubbles & user promoting / demoting content
  to a bubble; user created bubbles can't be canceled or finished, the
  user must explictly dismiss the notif
- Move all of our Dependency.get into constructor & use inject

Test: it compiles (see manual tests in CL with the menu affordance)
Test: atest BubbleControllerTest
Bug: 138116133
Bug: 143173197
Change-Id: I6100bce7b74146afd4e0c4c02d4ce7731fecd5af
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index f8e45d4..c6b9090 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -64,7 +64,8 @@
 
     private long mLastUpdated;
     private long mLastAccessed;
-    private boolean mIsRemoved;
+
+    private boolean mIsUserCreated;
 
     /**
      * Whether this notification should be shown in the shade when it is also displayed as a bubble.
@@ -74,9 +75,7 @@
      */
     private boolean mShowInShadeWhenBubble = true;
 
-    /**
-     * Whether the bubble should show a dot for the notification indicating updated content.
-     */
+    /** Whether the bubble should show a dot for the notification indicating updated content. */
     private boolean mShowBubbleUpdateDot = true;
 
     /** Whether flyout text should be suppressed, regardless of any other flags or state. */
@@ -294,6 +293,20 @@
         return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
     }
 
+    /**
+     * Whether this bubble was explicitly created by the user via a SysUI affordance.
+     */
+    boolean isUserCreated() {
+        return mIsUserCreated;
+    }
+
+    /**
+     * Set whether this bubble was explicitly created by the user via a SysUI affordance.
+     */
+    void setUserCreated(boolean isUserCreated) {
+        mIsUserCreated = isUserCreated;
+    }
+
     float getDesiredHeight(Context context) {
         Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
         boolean useRes = data.getDesiredHeightResId() != 0;
@@ -319,9 +332,8 @@
 
     @Nullable
     PendingIntent getBubbleIntent(Context context) {
-        Notification notif = mEntry.getSbn().getNotification();
-        Notification.BubbleMetadata data = notif.getBubbleMetadata();
-        if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) {
+        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
+        if (data != null) {
             return data.getIntent();
         }
         return null;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 0231b56..9f7bdd4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -67,7 +67,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -82,6 +81,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -96,6 +96,8 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * Bubbles are a special type of content that can "float" on top of other apps or System UI.
  * Bubbles can be expanded to show more content.
@@ -132,6 +134,7 @@
     private BubbleExpandListener mExpandListener;
     @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
     private final NotificationGroupManager mNotificationGroupManager;
+    private final Lazy<ShadeController> mShadeController;
 
     private BubbleData mBubbleData;
     @Nullable private BubbleStackView mStackView;
@@ -206,24 +209,34 @@
     }
 
     @Inject
-    public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
-            BubbleData data, ConfigurationController configurationController,
-            NotificationInterruptionStateProvider interruptionStateProvider,
-            ZenModeController zenModeController,
-            NotificationLockscreenUserManager notifUserManager,
-            NotificationGroupManager groupManager) {
-        this(context, statusBarWindowController, data, null /* synchronizer */,
-                configurationController, interruptionStateProvider, zenModeController,
-                notifUserManager, groupManager);
-    }
-
-    public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
-            BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
+    public BubbleController(Context context,
+            StatusBarWindowController statusBarWindowController,
+            StatusBarStateController statusBarStateController,
+            Lazy<ShadeController> shadeController,
+            BubbleData data,
             ConfigurationController configurationController,
             NotificationInterruptionStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
-            NotificationGroupManager groupManager) {
+            NotificationGroupManager groupManager,
+            NotificationEntryManager entryManager) {
+        this(context, statusBarWindowController, statusBarStateController, shadeController,
+                data, null /* synchronizer */, configurationController, interruptionStateProvider,
+                zenModeController, notifUserManager, groupManager, entryManager);
+    }
+
+    public BubbleController(Context context,
+            StatusBarWindowController statusBarWindowController,
+            StatusBarStateController statusBarStateController,
+            Lazy<ShadeController> shadeController,
+            BubbleData data,
+            @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
+            ConfigurationController configurationController,
+            NotificationInterruptionStateProvider interruptionStateProvider,
+            ZenModeController zenModeController,
+            NotificationLockscreenUserManager notifUserManager,
+            NotificationGroupManager groupManager,
+            NotificationEntryManager entryManager) {
         mContext = context;
         mNotificationInterruptionStateProvider = interruptionStateProvider;
         mNotifUserManager = notifUserManager;
@@ -249,7 +262,7 @@
         mBubbleData = data;
         mBubbleData.setListener(mBubbleDataListener);
 
-        mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
+        mNotificationEntryManager = entryManager;
         mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
         mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
         mNotificationGroupManager = groupManager;
@@ -271,9 +284,10 @@
                     }
                 });
 
+        mShadeController = shadeController;
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarStateListener = new StatusBarStateListener();
-        Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
+        statusBarStateController.addCallback(mStatusBarStateListener);
 
         mTaskStackListener = new BubbleTaskStackListener();
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -497,15 +511,45 @@
      * @param notif the notification associated with this bubble.
      */
     void updateBubble(NotificationEntry notif) {
-        updateBubble(notif, /* supressFlyout */ false);
+        updateBubble(notif, false /* suppressFlyout */);
     }
 
     void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
+        updateBubble(notif, suppressFlyout, true /* showInShade */);
+    }
+
+    void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
         // If this is an interruptive notif, mark that it's interrupted
         if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
             notif.setInterruption();
         }
-        mBubbleData.notificationEntryUpdated(notif, suppressFlyout);
+        mBubbleData.notificationEntryUpdated(notif, suppressFlyout, showInShade);
+    }
+
+    /**
+     * Called when a user has indicated that an active notification should be shown as a bubble.
+     * <p>
+     * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
+     * the notification from appearing in the shade.
+     *
+     * @param entry the notification to show as a bubble.
+     */
+    public void onUserCreatedBubbleFromNotification(NotificationEntry entry) {
+        mShadeController.get().collapsePanel(true);
+        entry.setFlagBubble(true);
+        updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
+        mBubbleData.getBubbleWithKey(entry.getKey()).setUserCreated(true);
+    }
+
+    /**
+     * Called when a user has indicated that an active notification appearing as a bubble should
+     * no longer be shown as a bubble.
+     *
+     * @param entry the notification to no longer show as a bubble.
+     */
+    public void onUserDemotedBubbleFromNotification(NotificationEntry entry) {
+        entry.setFlagBubble(false);
+        removeBubble(entry.getKey(), DISMISS_BLOCKED);
     }
 
     /**
@@ -571,7 +615,7 @@
                     mNotificationEntryManager.updateNotifications(
                             "BubbleController.onNotificationRemoveRequested");
                     return true;
-                } else if (!userRemovedNotif && entry != null) {
+                } else if (!userRemovedNotif && entry != null && !bubble.isUserCreated()) {
                     // This wasn't a user removal so we should remove the bubble as well
                     mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
                     return false;
@@ -631,6 +675,9 @@
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
         public void onPendingEntryAdded(NotificationEntry entry) {
+            Bubble b = mBubbleData.getBubbleWithKey(entry.getKey());
+            BubbleExperimentConfig.adjustForExperiments(mContext, entry, b);
+
             if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry)) {
                 updateBubble(entry);
@@ -639,13 +686,15 @@
 
         @Override
         public void onPreEntryUpdated(NotificationEntry entry) {
+            Bubble b = mBubbleData.getBubbleWithKey(entry.getKey());
+            BubbleExperimentConfig.adjustForExperiments(mContext, entry, b);
+
             boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry);
             if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
                 // It was previously a bubble but no longer a bubble -- lets remove it
                 removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
             } else if (shouldBubble) {
-                Bubble b = mBubbleData.getBubbleWithKey(entry.getKey());
                 updateBubble(entry);
             }
         }
@@ -949,19 +998,26 @@
         PendingIntent intent = entry.getBubbleMetadata() != null
                 ? entry.getBubbleMetadata().getIntent()
                 : null;
+        return canLaunchIntentInActivityView(context, entry, intent);
+    }
+
+    static boolean canLaunchIntentInActivityView(Context context, NotificationEntry entry,
+            PendingIntent intent) {
         if (intent == null) {
-            Log.w(TAG, "Unable to create bubble -- no intent");
+            Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey());
             return false;
         }
         ActivityInfo info =
                 intent.getIntent().resolveActivityInfo(context.getPackageManager(), 0);
         if (info == null) {
-            Log.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: "
+            Log.w(TAG, "Unable to send as bubble, "
+                    + entry.getKey() + " couldn't find activity info for intent: "
                     + intent);
             return false;
         }
         if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
-            Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: "
+            Log.w(TAG, "Unable to send as bubble, "
+                    + entry.getKey() + " activity is not resizable for intent: "
                     + intent);
             return false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 4e229c0..f4d48b2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -179,7 +179,8 @@
         dispatchPendingChanges();
     }
 
-    void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout) {
+    void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout,
+            boolean showInShade) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryUpdated: " + entry);
         }
@@ -208,7 +209,7 @@
             setSelectedBubbleInternal(bubble);
         }
         boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
-        bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected);
+        bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected && showInShade);
         bubble.setShowBubbleDot(!isBubbleExpandedAndSelected);
         dispatchPendingChanges();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 1d9f6b2..e9c19d2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -184,10 +184,12 @@
                         + " mActivityViewStatus=" + mActivityViewStatus
                         + " bubble=" + getBubbleKey());
             }
-            if (mBubble != null) {
-                // Must post because this is called from a binder thread.
-                post(() -> mBubbleController.removeBubble(mBubble.getKey(),
-                        BubbleController.DISMISS_TASK_FINISHED));
+            if (mBubble != null && !mBubble.isUserCreated()) {
+                if (mBubble != null) {
+                    // Must post because this is called from a binder thread.
+                    post(() -> mBubbleController.removeBubble(mBubble.getKey(),
+                            BubbleController.DISMISS_TASK_FINISHED));
+                }
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
new file mode 100644
index 0000000..b478a72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.provider.Settings;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * Common class for experiments controlled via secure settings.
+ */
+public class BubbleExperimentConfig {
+
+    private static final String ALLOW_ANY_NOTIF_TO_BUBBLE = "allow_any_notif_to_bubble";
+    private static final boolean ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT = false;
+
+    private static final String ALLOW_MESSAGE_NOTIFS_TO_BUBBLE = "allow_message_notifs_to_bubble";
+    private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = false;
+
+    /**
+     * When true, if a notification has the information necessary to bubble (i.e. valid
+     * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
+     * object will be created by the system and added to the notification.
+     *
+     * This does not produce a bubble, only adds the metadata. It should be used in conjunction
+     * with {@see #allowNotifBubbleMenu} which shows an affordance to bubble notification content.
+     */
+    static boolean allowAnyNotifToBubble(Context context) {
+        return Settings.Secure.getInt(context.getContentResolver(),
+                ALLOW_ANY_NOTIF_TO_BUBBLE,
+                ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
+    }
+
+    /**
+     * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that
+     * are using {@link Notification.MessagingStyle} and have remote input.
+     */
+    static boolean allowMessageNotifsToBubble(Context context) {
+        return Settings.Secure.getInt(context.getContentResolver(),
+                ALLOW_MESSAGE_NOTIFS_TO_BUBBLE,
+                ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
+    }
+
+    /**
+     * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds
+     * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as
+     * the notification has necessary info for BubbleMetadata.
+     */
+    static void adjustForExperiments(Context context, NotificationEntry entry,
+            Bubble previousBubble) {
+        if (entry.getBubbleMetadata() != null) {
+            // Has metadata, nothing to do.
+            return;
+        }
+
+        Notification notification = entry.getSbn().getNotification();
+        boolean isMessage = Notification.MessagingStyle.class.equals(
+                notification.getNotificationStyle());
+        boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context))
+                || allowAnyNotifToBubble(context);
+
+        final PendingIntent intent = notification.contentIntent;
+        if (bubbleNotifForExperiment
+                && BubbleController.canLaunchIntentInActivityView(context, entry, intent)) {
+            final Icon smallIcon = entry.getSbn().getNotification().getSmallIcon();
+            Notification.BubbleMetadata.Builder metadata =
+                    new Notification.BubbleMetadata.Builder()
+                            .setDesiredHeight(10000)
+                            .setIcon(smallIcon)
+                            .setIntent(intent);
+            entry.setBubbleMetadata(metadata.build());
+        }
+
+        if (previousBubble != null) {
+            // Update to a previously user-created bubble, set its flag now so the update goes
+            // to the bubble.
+            entry.setFlagBubble(true);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 7d09932..66ed864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -18,7 +18,6 @@
 
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -189,8 +188,7 @@
             return false;
         }
 
-        final Notification n = sbn.getNotification();
-        if (n.getBubbleMetadata() == null || n.getBubbleMetadata().getIntent() == null) {
+        if (entry.getBubbleMetadata() == null || entry.getBubbleMetadata().getIntent() == null) {
             if (DEBUG) {
                 Log.d(TAG, "No bubble up: notification: " + sbn.getKey()
                         + " doesn't have valid metadata");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index a4c8fc4..28ccaf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -113,6 +113,7 @@
     private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
     public CharSequence remoteInputText;
     private final List<Person> mAssociatedPeople = new ArrayList<>();
+    private Notification.BubbleMetadata mBubbleMetadata;
 
     /**
      * If {@link android.app.RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
@@ -199,6 +200,7 @@
         }
 
         mSbn = sbn;
+        mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
         updatePeopleList();
     }
 
@@ -340,7 +342,33 @@
      * Returns the data needed for a bubble for this notification, if it exists.
      */
     public Notification.BubbleMetadata getBubbleMetadata() {
-        return mSbn.getNotification().getBubbleMetadata();
+        return mBubbleMetadata;
+    }
+
+    /**
+     * Sets bubble metadata for this notification.
+     */
+    public void setBubbleMetadata(Notification.BubbleMetadata metadata) {
+        mBubbleMetadata = metadata;
+    }
+
+    /**
+     * Updates the {@link Notification#FLAG_BUBBLE} flag on this notification to indicate
+     * whether it is a bubble or not. If this entry is set to not bubble, or does not have
+     * the required info to bubble, the flag cannot be set to true.
+     *
+     * @param shouldBubble whether this notification should be flagged as a bubble.
+     * @return true if the value changed.
+     */
+    public boolean setFlagBubble(boolean shouldBubble) {
+        boolean wasBubble = isBubble();
+        if (!shouldBubble) {
+            mSbn.getNotification().flags &= ~FLAG_BUBBLE;
+        } else if (mBubbleMetadata != null && canBubble()) {
+            // wants to be bubble & can bubble, set flag
+            mSbn.getNotification().flags |= FLAG_BUBBLE;
+        }
+        return wasBubble != isBubble();
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index b1a6bc6..a7c0204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -78,6 +78,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -93,6 +94,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import dagger.Lazy;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -151,6 +154,8 @@
     ColorExtractor.GradientColors mGradientColors;
     @Mock
     private Resources mResources;
+    @Mock
+    private Lazy<ShadeController> mShadeController;
 
     private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private BubbleData mBubbleData;
@@ -158,7 +163,6 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
         mContext.addMockSystemService(FaceManager.class, mFaceManager);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
@@ -199,12 +203,15 @@
         mBubbleData = new BubbleData(mContext);
         mBubbleController = new TestableBubbleController(mContext,
                 mStatusBarWindowController,
+                mStatusBarStateController,
+                mShadeController,
                 mBubbleData,
                 mConfigurationController,
                 interruptionStateProvider,
                 mZenModeController,
                 mLockscreenUserManager,
-                mNotificationGroupManager);
+                mNotificationGroupManager,
+                mNotificationEntryManager);
         mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
@@ -686,15 +693,20 @@
     static class TestableBubbleController extends BubbleController {
         // Let's assume surfaces can be synchronized immediately.
         TestableBubbleController(Context context,
-                StatusBarWindowController statusBarWindowController, BubbleData data,
+                StatusBarWindowController statusBarWindowController,
+                StatusBarStateController statusBarStateController,
+                Lazy<ShadeController> shadeController,
+                BubbleData data,
                 ConfigurationController configurationController,
                 NotificationInterruptionStateProvider interruptionStateProvider,
                 ZenModeController zenModeController,
                 NotificationLockscreenUserManager lockscreenUserManager,
-                NotificationGroupManager groupManager) {
-            super(context, statusBarWindowController, data, Runnable::run,
-                    configurationController, interruptionStateProvider, zenModeController,
-                    lockscreenUserManager, groupManager);
+                NotificationGroupManager groupManager,
+                NotificationEntryManager entryManager) {
+            super(context,
+                    statusBarWindowController, statusBarStateController, shadeController,
+                    data, Runnable::run, configurationController, interruptionStateProvider,
+                    zenModeController, lockscreenUserManager, groupManager, entryManager);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 67f65e6..32361cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -888,7 +888,8 @@
 
     private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
         setPostTime(entry, postTime);
-        mBubbleData.notificationEntryUpdated(entry, /* suppressFlyout=*/ false);
+        mBubbleData.notificationEntryUpdated(entry, false /* suppressFlyout*/,
+                true /* showInShade */);
     }
 
     private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {