Enable 'real bubbles' and fix our tests

Previously the test was marking entries as 'isBubble' automatically which
isn't quite correct.

This CL fixes that and has the side effect that enables bubbles for real.
This doesn't auto-bubble any notification, however, if the notification
is on an appropriately noisy channel & has an app overlay intent we will
bubble it.

Bug: 111236845
Test: atest BubbleControllerTest
Change-Id: I26c67ce002eec808591d7864b2366bb4140be386
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 9f3ff78..79ee4b8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -22,12 +22,16 @@
 
 import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
 
+import android.annotation.Nullable;
+import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
@@ -35,8 +39,6 @@
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -88,6 +90,8 @@
     // Bubbles get added to the status bar view
     private final StatusBarWindowController mStatusBarWindowController;
 
+    private INotificationManager mNotificationManagerService;
+
     // Used for determining view rect for touch interaction
     private Rect mTempRect = new Rect();
 
@@ -124,6 +128,13 @@
         mStatusBarWindowController = statusBarWindowController;
 
         mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
+
+        try {
+            mNotificationManagerService = INotificationManager.Stub.asInterface(
+                    ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
+        } catch (ServiceManager.ServiceNotFoundException e) {
+            e.printStackTrace();
+        }
     }
 
     /**
@@ -268,7 +279,7 @@
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
         public void onPendingEntryAdded(NotificationEntry entry) {
-            if (shouldAutoBubble(mContext, entry)) {
+            if (shouldAutoBubble(mContext, entry) || shouldBubble(entry)) {
                 entry.setIsBubble(true);
             }
         }
@@ -368,18 +379,37 @@
     }
 
     /**
+     * Whether the notification has been developer configured to bubble and is allowed by the user.
+     */
+    private boolean shouldBubble(NotificationEntry entry) {
+        StatusBarNotification n = entry.notification;
+        boolean canAppOverlay = false;
+        try {
+            canAppOverlay = mNotificationManagerService.areAppOverlaysAllowedForPackage(
+                    n.getPackageName(), n.getUid());
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error calling NoMan to determine if app can overlay", e);
+        }
+
+        boolean canChannelOverlay = mNotificationEntryManager.getNotificationData().getChannel(
+                entry.key).canOverlayApps();
+        boolean hasOverlayIntent = n.getNotification().getAppOverlayIntent() != null;
+        return hasOverlayIntent && canChannelOverlay && canAppOverlay;
+    }
+
+    /**
      * Whether the notification should bubble or not.
      */
-    private static boolean shouldAutoBubble(Context context, NotificationEntry entry) {
+    private boolean shouldAutoBubble(Context context, NotificationEntry entry) {
         if (entry.isBubbleDismissed()) {
             return false;
         }
+        StatusBarNotification n = entry.notification;
 
         boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
         boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
         boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
 
-        StatusBarNotification n = entry.notification;
         boolean hasRemoteInput = false;
         if (n.getNotification().actions != null) {
             for (Notification.Action action : n.getNotification().actions) {
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 21d3652..702a641 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -74,7 +74,8 @@
     private ExpandableNotificationRow mRow;
     private ExpandableNotificationRow mRow2;
 
-    private final NotificationData mNotificationData = new NotificationData();
+    @Mock
+    private NotificationData mNotificationData;
 
     @Before
     public void setUp() throws Exception {
@@ -93,6 +94,7 @@
 
         // Return non-null notification data from the NEM
         when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
+        when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel);
 
         mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController);
 
@@ -103,11 +105,6 @@
     }
 
     @Test
-    public void testIsBubble() {
-        assertTrue(mRow.getEntry().isBubble());
-    }
-
-    @Test
     public void testAddBubble() {
         mBubbleController.addBubble(mRow.getEntry());
         assertTrue(mBubbleController.hasBubbles());
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 529da82..2b13f86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -17,13 +17,16 @@
 package com.android.systemui.statusbar;
 
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.support.test.InstrumentationRegistry;
@@ -86,8 +89,7 @@
      * @throws Exception
      */
     public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception {
-        return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */,
-                false /* isBubble */);
+        return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */);
     }
 
     /**
@@ -98,8 +100,7 @@
      * @throws Exception
      */
     public ExpandableNotificationRow createRow(Notification notification) throws Exception {
-        return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */,
-                false /* isBubble */);
+        return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */);
     }
 
     /**
@@ -112,8 +113,7 @@
      */
     public ExpandableNotificationRow createRow(@InflationFlag int extraInflationFlags)
             throws Exception {
-        return generateRow(createNotification(), PKG, UID, extraInflationFlags,
-                false /* isBubble */);
+        return generateRow(createNotification(), PKG, UID, extraInflationFlags);
     }
 
     /**
@@ -134,20 +134,21 @@
         return createGroup(2);
     }
 
-    /**
-     * Retursn an {@link ExpandableNotificationRow} that should be a bubble.
-     */
-    public ExpandableNotificationRow createBubble() throws Exception {
-        return createRow(PKG, UID, false /* isGroupSummary */, null /* groupKey */,
-                true /* isBubble */);
-    }
-
     private ExpandableNotificationRow createGroupSummary(String groupkey) throws Exception {
-        return createRow(PKG, UID, true /* isGroupSummary */, groupkey, false);
+        return createRow(PKG, UID, true /* isGroupSummary */, groupkey);
     }
 
     private ExpandableNotificationRow createGroupChild(String groupkey) throws Exception {
-        return createRow(PKG, UID, false /* isGroupSummary */, groupkey, false);
+        return createRow(PKG, UID, false /* isGroupSummary */, groupkey);
+    }
+
+    /**
+     * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
+     */
+    public ExpandableNotificationRow createBubble() throws Exception {
+        Notification n = createNotification(false /* isGroupSummary */,
+                null /* groupKey */, true /* isBubble */);
+        return generateRow(n, PKG, UID, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
     }
 
     /**
@@ -157,7 +158,6 @@
      * @param uid uid used for creating a {@link StatusBarNotification}
      * @param isGroupSummary whether the notification row is a group summary
      * @param groupKey the group key for the notification group used across notifications
-     * @param isBubble
      * @return a row with that's either a standalone notification or a group notification if the
      *         groupKey is non-null
      * @throws Exception
@@ -166,10 +166,10 @@
             String pkg,
             int uid,
             boolean isGroupSummary,
-            @Nullable String groupKey, boolean isBubble)
+            @Nullable String groupKey)
             throws Exception {
         Notification notif = createNotification(isGroupSummary, groupKey);
-        return generateRow(notif, pkg, uid, 0 /* inflationFlags */, isBubble);
+        return generateRow(notif, pkg, uid, 0 /* inflationFlags */);
     }
 
     /**
@@ -188,8 +188,20 @@
      * @param groupKey the group key for the notification group used across notifications
      * @return a notification that is in the group specified or standalone if unspecified
      */
+    private Notification createNotification(boolean isGroupSummary, @Nullable String groupKey) {
+        return createNotification(isGroupSummary, groupKey, false /* isBubble */);
+    }
+
+    /**
+     * Creates a notification with the given parameters.
+     *
+     * @param isGroupSummary whether the notification is a group summary
+     * @param groupKey the group key for the notification group used across notifications
+     * @param isBubble whether this notification should bubble
+     * @return a notification that is in the group specified or standalone if unspecified
+     */
     private Notification createNotification(boolean isGroupSummary,
-            @Nullable String groupKey) {
+            @Nullable String groupKey, boolean isBubble) {
         Notification publicVersion = new Notification.Builder(mContext).setSmallIcon(
                 R.drawable.ic_person)
                 .setCustomContentView(new RemoteViews(mContext.getPackageName(),
@@ -207,6 +219,10 @@
         if (!TextUtils.isEmpty(groupKey)) {
             notificationBuilder.setGroup(groupKey);
         }
+        if (isBubble) {
+            PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+            notificationBuilder.setAppOverlayIntent(bubbleIntent);
+        }
         return notificationBuilder.build();
     }
 
@@ -214,7 +230,17 @@
             Notification notification,
             String pkg,
             int uid,
-            @InflationFlag int extraInflationFlags, boolean isBubble)
+            @InflationFlag int extraInflationFlags)
+            throws Exception {
+        return generateRow(notification, pkg, uid, extraInflationFlags, IMPORTANCE_DEFAULT);
+    }
+
+    private ExpandableNotificationRow generateRow(
+            Notification notification,
+            String pkg,
+            int uid,
+            @InflationFlag int extraInflationFlags,
+            int importance)
             throws Exception {
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                 mContext.LAYOUT_INFLATER_SERVICE);
@@ -242,9 +268,8 @@
         entry.setRow(row);
         entry.createIcons(mContext, sbn);
         entry.channel = new NotificationChannel(
-                notification.getChannelId(), notification.getChannelId(), IMPORTANCE_DEFAULT);
+                notification.getChannelId(), notification.getChannelId(), importance);
         entry.channel.setBlockableSystem(true);
-        entry.setIsBubble(isBubble);
         row.setEntry(entry);
         row.getNotificationInflater().addInflationFlags(extraInflationFlags);
         NotificationInflaterTest.runThenWaitForInflation(
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c68e0f9..2d97726 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2313,8 +2313,8 @@
 
         @Override
         public boolean areAppOverlaysAllowedForPackage(String pkg, int uid) {
-            checkCallerIsSystemOrSameApp(pkg);
-
+            enforceSystemOrSystemUIOrSamePackage("Caller not system or systemui or same package",
+                    pkg);
             return mPreferencesHelper.areAppOverlaysAllowed(pkg, uid);
         }