Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7eb18efc authored by Mady Mellor's avatar Mady Mellor
Browse files

Introduce FLAG_BUBBLE & mark notifs allowed to bubble with it

- It's a hidden flag
- This just marks the notifs but doesn't actually do anything else
- Tests that the flag is added appropriately

Bug: 128459529
Test: atest NotificationManagerServiceTest
Change-Id: I291911ead540a9c6e7f99716d0730b1faaf5533b
parent e456bdf2
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -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;
    }
+17 −0
Original line number Diff line number Diff line
@@ -4728,6 +4728,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;
@@ -5083,6 +5097,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);
+140 −1
Original line number Diff line number Diff line
@@ -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 {