diff options
3 files changed, 173 insertions, 1 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 5549d6b80ea6..11fa343df0d3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -617,6 +617,13 @@ public class Notification implements Parcelable */ public static final int FLAG_CAN_COLORIZE = 0x00000800; + /** + * Bit to be bitswised-ored into the {@link #flags} field that should be + * set if this notification can be shown as a bubble. + * @hide + */ + public static final int FLAG_BUBBLE = 0x00001000; + public int flags; /** @hide */ @@ -6243,6 +6250,15 @@ public class Notification implements Parcelable return false; } + /** + * @return true if this is a notification that can show as a bubble. + * + * @hide + */ + public boolean isBubbleNotification() { + return (flags & Notification.FLAG_BUBBLE) != 0; + } + private boolean hasLargeIcon() { return mLargeIcon != null || largeIcon != null; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 20b898760389..9ccd681b17c2 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4740,6 +4740,20 @@ public class NotificationManagerService extends SystemService { } } + /** + * Updates the flags for this notification to reflect whether it is a bubble or not. + */ + private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId) { + Notification notification = r.getNotification(); + boolean canBubble = mPreferencesHelper.areBubblesAllowed(pkg, userId) + && r.getChannel().canBubble(); + if (notification.getBubbleMetadata() != null && canBubble) { + notification.flags |= Notification.FLAG_BUBBLE; + } else { + notification.flags &= ~Notification.FLAG_BUBBLE; + } + } + private void doChannelWarningToast(CharSequence toastText) { Binder.withCleanCallingIdentity(() -> { final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0; @@ -5095,6 +5109,9 @@ public class NotificationManagerService extends SystemService { final int id = n.getId(); final String tag = n.getTag(); + // We need to fix the notification up a little for bubbles + flagNotificationForBubbles(r, pkg, callingUid); + // Handle grouped notifications and bail out early if we // can to avoid extracting signals. handleGroupedNotificationLocked(r, old, callingUid, callingPid); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index a9eb6ec5db1e..8f74571ac8ae 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -79,6 +79,7 @@ import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; @@ -92,6 +93,7 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.drawable.Icon; import android.media.AudioManager; import android.net.Uri; import android.os.Binder; @@ -149,7 +151,6 @@ import org.mockito.stubbing.Answer; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; @@ -507,6 +508,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { false); } + private Notification.BubbleMetadata.Builder getBasicBubbleMetadataBuilder() { + PendingIntent pi = PendingIntent.getActivity(mContext, 0, new Intent(), 0); + return new Notification.BubbleMetadata.Builder() + .setIntent(pi) + .setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon)); + } + @Test public void testCreateNotificationChannels_SingleChannel() throws Exception { final NotificationChannel channel = @@ -4290,6 +4298,137 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .onGranted(eq(xmlConfig), eq(0), eq(true)); } + @Test + public void testFlagBubbleNotifs_flagIfAllowed() throws RemoteException { + // 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 + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // yes allowed, yes bubble + assertTrue(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlagIfNotAllowed() throws RemoteException { + // Bubbles are NOT allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(false); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Notif with bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // Post the notification + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // not allowed, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlagIfNotBubble() throws RemoteException { + // 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 WITHOUT bubble metadata + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // Post the notification + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // no bubble metadata, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlagIfChannelNotBubble() throws RemoteException { + // 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()); + + // But not on this channel! + mTestNotificationChannel.setAllowBubbles(false); + + // Notif with bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // Post the notification + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // channel not allowed, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } public void testGetAllowedAssistantCapabilities() throws Exception { |