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

Commit c65071d6 authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Limit the number of notis an app can enqueue" into oc-dr1-dev

parents 8b94ef69 6ad0aecf
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -2541,6 +2541,22 @@ public class Notification implements Parcelable
        }
    }

    /**
     * @hide
     */
    public boolean hasCompletedProgress() {
        // not a progress notification; can't be complete
        if (!extras.containsKey(EXTRA_PROGRESS)
                || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
            return false;
        }
        // many apps use max 0 for 'indeterminate'; not complete
        if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
            return false;
        }
        return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
    }

    /** @removed */
    @Deprecated
    public String getChannel() {
+39 −0
Original line number Diff line number Diff line
@@ -99,6 +99,45 @@ public class NotificationTest {
        assertTrue(satisfiesTextContrast(secondaryTextColor, backgroundColor));
    }

    @Test
    public void testHasCompletedProgress_noProgress() {
        Notification n = new Notification.Builder(mContext).build();

        assertFalse(n.hasCompletedProgress());
    }

    @Test
    public void testHasCompletedProgress_complete() {
        Notification n = new Notification.Builder(mContext)
                .setProgress(100, 100, true)
                .build();
        Notification n2 = new Notification.Builder(mContext)
                .setProgress(10, 10, false)
                .build();
        assertTrue(n.hasCompletedProgress());
        assertTrue(n2.hasCompletedProgress());
    }

    @Test
    public void testHasCompletedProgress_notComplete() {
        Notification n = new Notification.Builder(mContext)
                .setProgress(100, 99, true)
                .build();
        Notification n2 = new Notification.Builder(mContext)
                .setProgress(10, 4, false)
                .build();
        assertFalse(n.hasCompletedProgress());
        assertFalse(n2.hasCompletedProgress());
    }

    @Test
    public void testHasCompletedProgress_zeroMax() {
        Notification n = new Notification.Builder(mContext)
                .setProgress(0, 0, true)
                .build();
        assertFalse(n.hasCompletedProgress());
    }

    private Notification.Builder getMediaNotification() {
        MediaSession session = new MediaSession(mContext, "test");
        return new Notification.Builder(mContext, "color")
+47 −28
Original line number Diff line number Diff line
@@ -209,7 +209,7 @@ public class NotificationManagerService extends SystemService {
            = SystemProperties.getBoolean("debug.child_notifs", true);

    static final int MAX_PACKAGE_NOTIFICATIONS = 50;
    static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 10f;
    static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 5f;

    // message codes
    static final int MESSAGE_TIMEOUT = 2;
@@ -3292,8 +3292,19 @@ public class NotificationManagerService extends SystemService {
        // package or a registered listener can enqueue.  Prevents DOS attacks and deals with leaks.
        if (!isSystemNotification && !isNotificationFromListener) {
            synchronized (mNotificationLock) {
                if (mNotificationsByKey.get(r.sbn.getKey()) != null) {
                    // this is an update, rate limit updates only
                if (mNotificationsByKey.get(r.sbn.getKey()) == null && isCallerInstantApp(pkg)) {
                    // Ephemeral apps have some special constraints for notifications.
                    // They are not allowed to create new notifications however they are allowed to
                    // update notifications created by the system (e.g. a foreground service
                    // notification).
                    throw new SecurityException("Instant app " + pkg
                            + " cannot create notifications");
                }

                // rate limit updates that aren't completed progress notifications
                if (mNotificationsByKey.get(r.sbn.getKey()) != null
                        && !r.getNotification().hasCompletedProgress()) {

                    final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
                    if (appEnqueueRate > mMaxPackageEnqueueRate) {
                        mUsageStats.registerOverRateQuota(pkg);
@@ -3305,36 +3316,18 @@ public class NotificationManagerService extends SystemService {
                        }
                        return false;
                    }
                } else if (isCallerInstantApp(pkg)) {
                    // Ephemeral apps have some special constraints for notifications.
                    // They are not allowed to create new notifications however they are allowed to
                    // update notifications created by the system (e.g. a foreground service
                    // notification).
                    throw new SecurityException("Instant app " + pkg
                            + " cannot create notifications");
                }

                int count = 0;
                final int N = mNotificationList.size();
                for (int i=0; i<N; i++) {
                    final NotificationRecord existing = mNotificationList.get(i);
                    if (existing.sbn.getPackageName().equals(pkg)
                            && existing.sbn.getUserId() == userId) {
                        if (existing.sbn.getId() == id
                                && TextUtils.equals(existing.sbn.getTag(), tag)) {
                            break;  // Allow updating existing notification
                        }
                        count++;
                // limit the number of outstanding notificationrecords an app can have
                int count = getNotificationCountLocked(pkg, userId, id, tag);
                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                    mUsageStats.registerOverCountQuota(pkg);
                            Slog.e(TAG, "Package has already posted " + count
                    Slog.e(TAG, "Package has already posted or enqueued " + count
                            + " notifications.  Not showing more.  package=" + pkg);
                    return false;
                }
            }
        }
            }
        }

        // snoozed apps
        if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
@@ -3358,6 +3351,32 @@ public class NotificationManagerService extends SystemService {
        return true;
    }

    protected int getNotificationCountLocked(String pkg, int userId, int excludedId,
            String excludedTag) {
        int count = 0;
        final int N = mNotificationList.size();
        for (int i = 0; i < N; i++) {
            final NotificationRecord existing = mNotificationList.get(i);
            if (existing.sbn.getPackageName().equals(pkg)
                    && existing.sbn.getUserId() == userId) {
                if (existing.sbn.getId() == excludedId
                        && TextUtils.equals(existing.sbn.getTag(), excludedTag)) {
                    continue;
                }
                count++;
            }
        }
        final int M = mEnqueuedNotifications.size();
        for (int i = 0; i < M; i++) {
            final NotificationRecord existing = mEnqueuedNotifications.get(i);
            if (existing.sbn.getPackageName().equals(pkg)
                    && existing.sbn.getUserId() == userId) {
                count++;
            }
        }
        return count;
    }

    protected boolean isBlocked(NotificationRecord r, NotificationUsageStats usageStats) {
        final String pkg = r.sbn.getPackageName();
        final int callingUid = r.sbn.getUid();
+33 −0
Original line number Diff line number Diff line
@@ -984,4 +984,37 @@ public class NotificationManagerServiceTest extends NotificationTestCase {

        assertFalse(posted.getNotification().isColorized());
    }

    @Test
    public void testGetNotificationCountLocked() throws Exception {
        for (int i = 0; i < 20; i++) {
            NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, i, null, false);
            mNotificationManagerService.addEnqueuedNotification(r);
        }
        for (int i = 0; i < 20; i++) {
            NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, i, null, false);
            mNotificationManagerService.addNotification(r);
        }

        // another package
        Notification n =
                new Notification.Builder(mContext, mTestNotificationChannel.getId())
                .setSmallIcon(android.R.drawable.sym_def_app_icon)
                .build();

        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "tag", uid, 0,
                n, new UserHandle(uid), null, 0);
        NotificationRecord otherPackage =
                new NotificationRecord(mContext, sbn, mTestNotificationChannel);
        mNotificationManagerService.addEnqueuedNotification(otherPackage);
        mNotificationManagerService.addNotification(otherPackage);

        // Same notifications are enqueued as posted, everything counts b/c id and tag don't match
        assertEquals(40, mNotificationManagerService.getNotificationCountLocked(PKG, new UserHandle(uid).getIdentifier(), 0, null));
        assertEquals(40, mNotificationManagerService.getNotificationCountLocked(PKG, new UserHandle(uid).getIdentifier(), 0, "tag2"));
        assertEquals(2, mNotificationManagerService.getNotificationCountLocked("a", new UserHandle(uid).getIdentifier(), 0, "banana"));

        // exclude a known notification - it's excluded from only the posted list, not enqueued
        assertEquals(39, mNotificationManagerService.getNotificationCountLocked(PKG, new UserHandle(uid).getIdentifier(), 0, "tag"));
    }
}
+631 −632
Original line number Diff line number Diff line
@@ -1212,4 +1212,3 @@ public class NotificationTestList extends TestActivity
        return Bitmap.createBitmap(bd.getBitmap());
    }
}