Save usercreated bubbles and re-bubble next time; add experiment debug logs

* Maintain a list of user-created bubbles in BubbleController
* Experiment code will allow any notif with a matching key in that list
  to be created as a bubble, does not adjust any channel level setting,
  does not maintain across reboots
* Add some debug logs (turned on by default, only triggers if experiment
  via set prop is running)

This is more of a stop-gap for testing until we have proper way of saving
that this person/notification from app should be bubbled or not.

Test: manual
Bug: 143173197
Change-Id: Id247e361740cdf95ae511287ad58c547825f1e47
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 044ea54..24ee9695 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -321,20 +321,6 @@
         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;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index db1185f..ed21e14 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -31,6 +31,7 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
 import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_EXPERIMENTS;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -92,6 +93,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -145,6 +147,10 @@
     // Saves notification keys of active bubbles when users are switched.
     private final SparseSetArray<String> mSavedBubbleKeysPerUser;
 
+    // Saves notification keys of user created "fake" bubbles so that we can allow notifications
+    // like these to bubble by default. Doesn't persist across reboots, not a long-term solution.
+    private final HashSet<String> mUserCreatedBubbles;
+
     // Bubbles get added to the status bar view
     private final StatusBarWindowController mStatusBarWindowController;
     private final ZenModeController mZenModeController;
@@ -312,6 +318,8 @@
                     restoreBubbles(newUserId);
                     mCurrentUserId = newUserId;
                 });
+
+        mUserCreatedBubbles = new HashSet<>();
     }
 
     /**
@@ -535,10 +543,13 @@
      * @param entry the notification to show as a bubble.
      */
     public void onUserCreatedBubbleFromNotification(NotificationEntry entry) {
+        if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
+            Log.d(TAG, "onUserCreatedBubble: " + entry.getKey());
+        }
         mShadeController.get().collapsePanel(true);
         entry.setFlagBubble(true);
         updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
-        mBubbleData.getBubbleWithKey(entry.getKey()).setUserCreated(true);
+        mUserCreatedBubbles.add(entry.getKey());
     }
 
     /**
@@ -548,8 +559,19 @@
      * @param entry the notification to no longer show as a bubble.
      */
     public void onUserDemotedBubbleFromNotification(NotificationEntry entry) {
+        if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
+            Log.d(TAG, "onUserDemotedBubble: " + entry.getKey());
+        }
         entry.setFlagBubble(false);
         removeBubble(entry.getKey(), DISMISS_BLOCKED);
+        mUserCreatedBubbles.remove(entry.getKey());
+    }
+
+    /**
+     * Whether this bubble was explicitly created by the user via a SysUI affordance.
+     */
+    boolean isUserCreatedBubble(String key) {
+        return mUserCreatedBubbles.contains(key);
     }
 
     /**
@@ -616,7 +638,8 @@
                     mNotificationEntryManager.updateNotifications(
                             "BubbleController.onNotificationRemoveRequested");
                     return true;
-                } else if (!userRemovedNotif && entry != null && !bubble.isUserCreated()) {
+                } else if (!userRemovedNotif && entry != null
+                        && !isUserCreatedBubble(bubble.getKey())) {
                     // This wasn't a user removal so we should remove the bubble as well
                     mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
                     return false;
@@ -676,8 +699,8 @@
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
         public void onPendingEntryAdded(NotificationEntry entry) {
-            Bubble b = mBubbleData.getBubbleWithKey(entry.getKey());
-            BubbleExperimentConfig.adjustForExperiments(mContext, entry, b);
+            boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
+            BubbleExperimentConfig.adjustForExperiments(mContext, entry, previouslyUserCreated);
 
             if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry)) {
@@ -687,8 +710,8 @@
 
         @Override
         public void onPreEntryUpdated(NotificationEntry entry) {
-            Bubble b = mBubbleData.getBubbleWithKey(entry.getKey());
-            BubbleExperimentConfig.adjustForExperiments(mContext, entry, b);
+            boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
+            BubbleExperimentConfig.adjustForExperiments(mContext, entry, previouslyUserCreated);
 
             boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
index b702d06..a912ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
@@ -37,5 +37,6 @@
     static final boolean DEBUG_BUBBLE_DATA = false;
     static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
     static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
+    static final boolean DEBUG_EXPERIMENTS = true;
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index ea51f4a..512b38e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -189,12 +189,10 @@
                         + " mActivityViewStatus=" + mActivityViewStatus
                         + " bubble=" + getBubbleKey());
             }
-            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));
-                }
+            if (mBubble != null && !mBubbleController.isUserCreatedBubble(mBubble.getKey())) {
+                // 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
index 6eeb5c3..17d6737 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -22,6 +22,9 @@
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 
 import static com.android.systemui.bubbles.BubbleController.canLaunchIntentInActivityView;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_EXPERIMENTS;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -35,6 +38,7 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.Log;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -47,6 +51,7 @@
  * Common class for experiments controlled via secure settings.
  */
 public class BubbleExperimentConfig {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
     private static final String SHORTCUT_DUMMY_INTENT = "bubble_experiment_shortcut_intent";
     private static PendingIntent sDummyShortcutIntent;
@@ -104,8 +109,7 @@
      * the notification has necessary info for BubbleMetadata.
      */
     static void adjustForExperiments(Context context, NotificationEntry entry,
-            Bubble previousBubble) {
-
+            boolean previouslyUserCreated) {
         Notification.BubbleMetadata metadata = null;
         boolean addedMetadata = false;
 
@@ -118,6 +122,19 @@
         boolean useShortcutInfo = useShortcutInfoToBubble(context);
         String shortcutId = entry.getSbn().getNotification().getShortcutId();
 
+        boolean hasMetadata = entry.getBubbleMetadata() != null;
+        if ((!hasMetadata && (previouslyUserCreated || bubbleNotifForExperiment))
+                || useShortcutInfo) {
+            if (DEBUG_EXPERIMENTS) {
+                Log.d(TAG, "Adjusting " + entry.getKey() + " for bubble experiment."
+                        + " allowMessages=" + allowMessageNotifsToBubble(context)
+                        + " isMessage=" + isMessage
+                        + " allowNotifs=" + allowAnyNotifToBubble(context)
+                        + " useShortcutInfo=" + useShortcutInfo
+                        + " previouslyUserCreated=" + previouslyUserCreated);
+            }
+        }
+
         if (useShortcutInfo && shortcutId != null) {
             // We don't actually get anything useful from ShortcutInfo so just check existence
             ShortcutInfo info = getShortcutInfo(context, entry.getSbn().getPackageName(),
@@ -127,26 +144,37 @@
             }
 
             // Replace existing metadata with shortcut, or we're bubbling for experiment
-            boolean shouldBubble = entry.getBubbleMetadata() != null || bubbleNotifForExperiment;
-
+            boolean shouldBubble = entry.getBubbleMetadata() != null
+                    || bubbleNotifForExperiment
+                    || previouslyUserCreated;
             if (shouldBubble && metadata != null) {
+                if (DEBUG_EXPERIMENTS) {
+                    Log.d(TAG, "Adding experimental shortcut bubble for: " + entry.getKey());
+                }
                 entry.setBubbleMetadata(metadata);
                 addedMetadata = true;
             }
         }
 
         // Didn't get metadata from a shortcut & we're bubbling for experiment
-        if (entry.getBubbleMetadata() == null && bubbleNotifForExperiment) {
+        if (entry.getBubbleMetadata() == null
+                && (bubbleNotifForExperiment || previouslyUserCreated)) {
             metadata = createFromNotif(context, entry);
             if (metadata != null) {
+                if (DEBUG_EXPERIMENTS) {
+                    Log.d(TAG, "Adding experimental notification bubble for: " + entry.getKey());
+                }
                 entry.setBubbleMetadata(metadata);
                 addedMetadata = true;
             }
         }
 
-        if (previousBubble != null && addedMetadata) {
-            // Update to a previously bubble, set its flag now so the update goes
+        if (previouslyUserCreated && addedMetadata) {
+            // Update to a previous bubble, set its flag now so the update goes
             // to the bubble.
+            if (DEBUG_EXPERIMENTS) {
+                Log.d(TAG, "Setting FLAG_BUBBLE for: " + entry.getKey());
+            }
             entry.setFlagBubble(true);
         }
     }