Bubble API: Allow developers to create BubbleMetadata from a shortcut id

Adds an option to specify a bubble via a shortcutId. If the builder had
icon / intent previously specified, the shortcutId will clobber those
and vice versa.

Unfortunately, this means that BubbleMetadata#getIcon and #getIntent may
return null when previously they were non-null. I've deprecated these
getters and introduced new ones along with new setters so that the naming
remains consistent.

NoMan will check if that shortcut exists before applying FLAG_BUBBLE.

Update SystemUI to use BubbleMetadata builder with shortcut id for the
shortcut experiment.

Adds test to ensure the launcher apps callback is added / removed
appropriately & that notifs with shortcut bubbles are properly flagged
or unflagged.

Test: atest NotificationManagerServiceTest
Test: atest NotificationTest NotificationManagerTest (see CTS CL)
Bug: 138116133
Bug: 144352570
Change-Id: I2e8155edc7fd70d6978fc80310f071bf6510e0d2
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 77c8e0b..b89305e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -346,14 +346,14 @@
      * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
      */
     boolean usingShortcutInfo() {
-        return BubbleExperimentConfig.isShortcutIntent(getBubbleIntent());
+        return mEntry.getBubbleMetadata().getShortcutId() != null;
     }
 
     @Nullable
     PendingIntent getBubbleIntent() {
         Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
         if (data != null) {
-            return data.getIntent();
+            return data.getBubbleIntent();
         }
         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 644d8c4..e642d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -1075,8 +1075,12 @@
      */
     static boolean canLaunchInActivityView(Context context, NotificationEntry entry) {
         PendingIntent intent = entry.getBubbleMetadata() != null
-                ? entry.getBubbleMetadata().getIntent()
+                ? entry.getBubbleMetadata().getBubbleIntent()
                 : null;
+        if (entry.getBubbleMetadata() != null
+                && entry.getBubbleMetadata().getShortcutId() != null) {
+            return true;
+        }
         if (intent == null) {
             Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey());
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index c1705db..5c46496 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -357,7 +357,7 @@
 
             if (isNew) {
                 mBubbleIntent = mBubble.getBubbleIntent();
-                if (mBubbleIntent != null) {
+                if (mBubbleIntent != null || mBubble.getShortcutInfo() != null) {
                     setContentVisibility(false);
                     mActivityView.setVisibility(VISIBLE);
                 }
@@ -543,7 +543,8 @@
     }
 
     private boolean usingActivityView() {
-        return mBubbleIntent != null && mActivityView != null;
+        return (mBubbleIntent != null || mBubble.getShortcutInfo() != null)
+                && mActivityView != null;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 4c1cf49..4252f72 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -29,7 +29,6 @@
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Color;
@@ -182,7 +181,7 @@
             ShortcutInfo info = getShortcutInfo(context, entry.getSbn().getPackageName(),
                     entry.getSbn().getUser(), shortcutId);
             if (info != null) {
-                metadata = createForShortcut(context, entry);
+                metadata = createForShortcut(shortcutId);
             }
 
             // Replace existing metadata with shortcut, or we're bubbling for experiment
@@ -259,30 +258,17 @@
         }
         if (intent != null) {
             return new Notification.BubbleMetadata.Builder()
+                    .createIntentBubble(intent, icon)
                     .setDesiredHeight(BUBBLE_HEIGHT)
-                    .setIcon(icon)
-                    .setIntent(intent)
                     .build();
         }
         return null;
     }
 
-    static Notification.BubbleMetadata createForShortcut(Context context, NotificationEntry entry) {
-        // ShortcutInfo does not return an icon, instead a Drawable, lets just use
-        // notification icon for BubbleMetadata.
-        Icon icon = entry.getSbn().getNotification().getSmallIcon();
-
-        // ShortcutInfo does not return the intent, lets make a fake but identifiable
-        // intent so we can still add bubbleMetadata
-        if (sDummyShortcutIntent == null) {
-            Intent i = new Intent(SHORTCUT_DUMMY_INTENT);
-            sDummyShortcutIntent = PendingIntent.getActivity(context, 0, i,
-                    PendingIntent.FLAG_UPDATE_CURRENT);
-        }
+    static Notification.BubbleMetadata createForShortcut(String shortcutId) {
         return new Notification.BubbleMetadata.Builder()
                 .setDesiredHeight(BUBBLE_HEIGHT)
-                .setIcon(icon)
-                .setIntent(sDummyShortcutIntent)
+                .createShortcutBubble(shortcutId)
                 .build();
     }
 
@@ -304,10 +290,6 @@
                 : null;
     }
 
-    static boolean isShortcutIntent(PendingIntent intent) {
-        return intent != null && intent.equals(sDummyShortcutIntent);
-    }
-
     static List<Person> getPeopleFromNotification(NotificationEntry entry) {
         Bundle extras = entry.getSbn().getNotification().extras;
         ArrayList<Person> personList = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
index b32dbb7..3b818db 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -18,6 +18,7 @@
 import android.app.Notification;
 import android.content.Context;
 import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
@@ -48,16 +49,19 @@
     /**
      * Returns the drawable that the developer has provided to display in the bubble.
      */
-    Drawable getBubbleDrawable(Bubble b, Context context) {
-        if (b.getShortcutInfo() != null && b.usingShortcutInfo()) {
+    Drawable getBubbleDrawable(Context context, ShortcutInfo shortcutInfo,
+            Notification.BubbleMetadata metadata) {
+        if (shortcutInfo != null) {
             LauncherApps launcherApps =
                     (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
             int density = context.getResources().getConfiguration().densityDpi;
-            return launcherApps.getShortcutIconDrawable(b.getShortcutInfo(), density);
+            return launcherApps.getShortcutIconDrawable(shortcutInfo, density);
         } else {
-            Notification.BubbleMetadata metadata = b.getEntry().getBubbleMetadata();
-            Icon ic = metadata.getIcon();
-            return ic.loadDrawable(context);
+            Icon ic = metadata.getBubbleIcon();
+            if (ic != null) {
+                return ic.loadDrawable(context);
+            }
+            return null;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 41f5028..8924cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -126,15 +126,22 @@
             StatusBarNotification sbn = b.getEntry().getSbn();
             String packageName = sbn.getPackageName();
 
-            // Shortcut info for this bubble
-            String shortcutId = sbn.getNotification().getShortcutId();
-            if (BubbleExperimentConfig.useShortcutInfoToBubble(c)
-                    && shortcutId != null) {
-                info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c,
-                        packageName,
-                        sbn.getUser(), shortcutId);
+            // Real shortcut info for this bubble
+            String bubbleShortcutId =  b.getEntry().getBubbleMetadata().getShortcutId();
+            if (bubbleShortcutId != null) {
+                info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c, packageName,
+                        sbn.getUser(), bubbleShortcutId);
+            } else {
+                // Check for experimental shortcut
+                String shortcutId = sbn.getNotification().getShortcutId();
+                if (BubbleExperimentConfig.useShortcutInfoToBubble(c) && shortcutId != null) {
+                    info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c,
+                            packageName,
+                            sbn.getUser(), shortcutId);
+                }
             }
 
+
             // App name & app icon
             PackageManager pm = c.getPackageManager();
             ApplicationInfo appInfo;
@@ -158,7 +165,8 @@
             }
 
             // Badged bubble image
-            Drawable bubbleDrawable = iconFactory.getBubbleDrawable(b, c);
+            Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
+                    b.getEntry().getBubbleMetadata());
             BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
             info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
                     badgeBitmapInfo).icon;
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 66ed864..bbf2dde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -188,7 +188,9 @@
             return false;
         }
 
-        if (entry.getBubbleMetadata() == null || entry.getBubbleMetadata().getIntent() == null) {
+        if (entry.getBubbleMetadata() == null
+                || (entry.getBubbleMetadata().getShortcutId() == null
+                    && entry.getBubbleMetadata().getBubbleIntent() == null)) {
             if (DEBUG) {
                 Log.d(TAG, "No bubble up: notification: " + sbn.getKey()
                         + " doesn't have valid metadata");
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 c4ae409..7daf922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -952,9 +952,8 @@
             long postTime) {
         // BubbleMetadata
         Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder()
-                .setIntent(mExpandIntent)
+                .createIntentBubble(mExpandIntent, Icon.createWithResource("", 0))
                 .setDeleteIntent(mDeleteIntent)
-                .setIcon(Icon.createWithResource("", 0))
                 .build();
         // Notification -> BubbleMetadata
         Notification notification = mNotificationTestHelper.createNotification(false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
index 677a6fc..1693e7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -557,8 +557,8 @@
 
     private NotificationEntry createBubble() {
         Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
-                .setIntent(PendingIntent.getActivity(mContext, 0, new Intent(), 0))
-                .setIcon(Icon.createWithResource(mContext.getResources(), R.drawable.android))
+                .createIntentBubble(PendingIntent.getActivity(mContext, 0, new Intent(), 0),
+                        Icon.createWithResource(mContext.getResources(), R.drawable.android))
                 .build();
         Notification n = new Notification.Builder(getContext(), "a")
                 .setContentTitle("title")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 61e43b0..457bbe2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -383,9 +383,9 @@
         PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0);
 
         return new BubbleMetadata.Builder()
-                .setIntent(bubbleIntent)
+                .createIntentBubble(bubbleIntent,
+                        Icon.createWithResource(mContext, R.drawable.android))
                 .setDeleteIntent(deleteIntent)
-                .setIcon(Icon.createWithResource(mContext, R.drawable.android))
                 .setDesiredHeight(314)
                 .build();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 9ae477e..7be7da3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -195,8 +195,8 @@
                 new Intent(mContext, BubblesTestActivity.class), 0);
         mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata(
                 new Notification.BubbleMetadata.Builder()
-                        .setIntent(bubbleIntent)
-                        .setIcon(Icon.createWithResource(mContext, R.drawable.android)).build())
+                        .createIntentBubble(bubbleIntent,
+                                Icon.createWithResource(mContext, R.drawable.android)).build())
                 .build();
         mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();