OSDN Git Service

Let NoMan know when something is no longer a bubble
authorMady Mellor <madym@google.com>
Thu, 18 Apr 2019 20:26:18 +0000 (13:26 -0700)
committerMady Mellor <madym@google.com>
Tue, 23 Apr 2019 18:22:00 +0000 (11:22 -0700)
This is needed for some of the notification group summary stuff to work.

More importantly, this is needed so that we can properly report to apps
if their notification is bubbled or not, e.g. if the user dismisses the
bubble & the notification is in the shade, that shouldn't be reported as
'FLAG_BUBBLE' as that notification is not actually being shown as a bubble.

* Adds onBubbleNotificationChanged to NotificationDelegate to pipe through
  changes in bubble state, currently we only ever change it to 'false' but
  this CL includes ability to flip it to 'true' (and also checks if the
  notif should actually be able to bubble)
* Factors code that indicates something is approved to bubble into own
  method
* Adds way to set BubbleMetadata on a notification (hidden !!)

Bug: 130250809
Test: atest NotificationManagerServiceTest
Change-Id: I8df4cc1231ed5d078ce4d50a70d2631f82fd2306

core/java/com/android/internal/statusbar/IStatusBarService.aidl
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
services/core/java/com/android/server/notification/NotificationDelegate.java
services/core/java/com/android/server/notification/NotificationManagerService.java
services/core/java/com/android/server/statusbar/StatusBarManagerService.java
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java

index 8f8e4d8..f22b6cd 100644 (file)
@@ -76,6 +76,7 @@ interface IStatusBarService
             in int notificationLocation, boolean modifiedBeforeSending);
     void onNotificationSettingsViewed(String key);
     void setSystemUiVisibility(int displayId, int vis, int mask, String cause);
+    void onNotificationBubbleChanged(String key, boolean isBubble);
 
     void onGlobalActionsShown();
     void onGlobalActionsHidden();
index 744f88d..ef383ad 100644 (file)
@@ -32,7 +32,6 @@ import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
-import android.app.INotificationManager;
 import android.app.Notification;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
@@ -52,6 +51,7 @@ import androidx.annotation.IntDef;
 import androidx.annotation.MainThread;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -131,8 +131,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
     private StatusBarStateListener mStatusBarStateListener;
 
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
-
-    private INotificationManager mNotificationManagerService;
+    private IStatusBarService mBarService;
 
     // Used for determining view rect for touch interaction
     private Rect mTempRect = new Rect();
@@ -207,13 +206,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
         mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
 
-        try {
-            mNotificationManagerService = INotificationManager.Stub.asInterface(
-                    ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
-        } catch (ServiceManager.ServiceNotFoundException e) {
-            e.printStackTrace();
-        }
-
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarStateListener = new StatusBarStateListener();
         Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
@@ -231,6 +223,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
         mBubbleData = data;
         mBubbleData.setListener(mBubbleDataListener);
         mSurfaceSynchronizer = synchronizer;
+
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
     /**
@@ -462,6 +457,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
             if (mStackView != null) {
                 mStackView.removeBubble(bubble);
             }
+            if (!bubble.entry.showInShadeWhenBubble()) {
+                // The notification is gone & bubble is gone, time to actually remove it
+                mNotificationEntryManager.performRemoveNotification(bubble.entry.notification);
+            } else {
+                // The notification is still in the shade but we've removed the bubble so
+                // lets make sure NoMan knows it's not a bubble anymore
+                try {
+                    mBarService.onNotificationBubbleChanged(bubble.getKey(), false /* isBubble */);
+                } catch (RemoteException e) {
+                    // Bad things have happened
+                }
+            }
         }
 
         public void onBubbleUpdated(Bubble bubble) {
index b85abd9..61be1f5 100644 (file)
@@ -46,6 +46,7 @@ public interface NotificationDelegate {
             int notificationLocation);
     void onNotificationDirectReplied(String key);
     void onNotificationSettingsViewed(String key);
+    void onNotificationBubbleChanged(String key, boolean isBubble);
 
     /**
      * Notifies that smart replies and actions have been added to the UI.
index e5ecd49..9fc30eb 100644 (file)
@@ -1020,6 +1020,24 @@ public class NotificationManagerService extends SystemService {
                 }
             }
         }
+
+        @Override
+        public void onNotificationBubbleChanged(String key, boolean isBubble) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    final StatusBarNotification n = r.sbn;
+                    final int callingUid = n.getUid();
+                    final String pkg = n.getPackageName();
+                    if (isBubble && isNotificationAppropriateToBubble(r, pkg, callingUid,
+                            null /* oldEntry */)) {
+                        r.getNotification().flags |= FLAG_BUBBLE;
+                    } else {
+                        r.getNotification().flags &= ~FLAG_BUBBLE;
+                    }
+                }
+            }
+        }
     };
 
     @VisibleForTesting
@@ -4782,6 +4800,19 @@ public class NotificationManagerService extends SystemService {
     private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId,
             NotificationRecord oldRecord) {
         Notification notification = r.getNotification();
+        if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord)) {
+            notification.flags |= FLAG_BUBBLE;
+        } else {
+            notification.flags &= ~FLAG_BUBBLE;
+        }
+    }
+
+    /**
+     * @return whether the provided notification record is allowed to be represented as a bubble.
+     */
+    private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
+            NotificationRecord oldRecord) {
+        Notification notification = r.getNotification();
 
         // Does the app want to bubble & have permission to bubble?
         boolean canBubble = notification.getBubbleMetadata() != null
@@ -4807,12 +4838,7 @@ public class NotificationManagerService extends SystemService {
         // OR something that was previously a bubble & still exists
         boolean bubbleUpdate = oldRecord != null
                 && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0;
-
-        if (canBubble && (notificationAppropriateToBubble || appIsForeground || bubbleUpdate)) {
-            notification.flags |= FLAG_BUBBLE;
-        } else {
-            notification.flags &= ~FLAG_BUBBLE;
-        }
+        return canBubble && (notificationAppropriateToBubble || appIsForeground || bubbleUpdate);
     }
 
     private void doChannelWarningToast(CharSequence toastText) {
index 9cbf00b..af009ec 100644 (file)
@@ -1314,6 +1314,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
     }
 
     @Override
+    public void onNotificationBubbleChanged(String key, boolean isBubble) {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationBubbleChanged(key, isBubble);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
         (new StatusBarShellCommand(this, mContext)).exec(
index 2d101dd..34bb0a8 100644 (file)
@@ -71,7 +71,6 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
@@ -5047,4 +5046,112 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
         mBinderService.areBubblesAllowedForPackage(mContext.getPackageName(),
                 mUid + UserHandle.PER_USER_RANGE);
     }
+
+    @Test
+    public void testNotificationBubbleChanged_false() throws Exception {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Notif with bubble metadata but not our other misc requirements
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+
+        // Say we're foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // First we were a bubble
+        StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsBefore.length);
+        assertTrue((notifsBefore[0].getNotification().flags & FLAG_BUBBLE) != 0);
+
+        // Notify we're not a bubble
+        mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false);
+        waitForIdle();
+
+        // Now we are not a bubble
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+        assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
+    }
+
+    @Test
+    public void testNotificationBubbleChanged_true() throws Exception {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Plain notification that has bubble metadata
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // Would be a normal notification because wouldn't have met requirements to bubble
+        StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsBefore.length);
+        assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0);
+
+        // Make the package foreground so that we're allowed to be a bubble
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        // Notify we are now a bubble
+        mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true);
+        waitForIdle();
+
+        // Make sure we are a bubble
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+        assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
+    }
+
+    @Test
+    public void testNotificationBubbleChanged_true_notAllowed() throws Exception {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Notif that is not a bubble
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // Would be a normal notification because wouldn't have met requirements to bubble
+        StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsBefore.length);
+        assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0);
+
+        // Notify we are now a bubble
+        mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true);
+        waitForIdle();
+
+        // We still wouldn't be a bubble because the notification didn't meet requirements
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+        assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
+    }
 }