summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Notification.java16
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java17
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java141
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 {