Limit the number of notis an app can enqueue
In addition to the number they can post. Also decrease the number of posts per second to 5, but allow finished progress notifications through. Fixes: 63173849 Fixes: 63167456 Test: runtests systemui-notification & simultaneously downloading 5 files. Change-Id: I0c8ea35b3d9d38b000ea5fe383515e1d5a26bac7
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,33 +3316,15 @@ 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++;
|
||||
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
|
||||
mUsageStats.registerOverCountQuota(pkg);
|
||||
Slog.e(TAG, "Package has already posted " + count
|
||||
+ " notifications. Not showing more. package=" + pkg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 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 or enqueued " + count
|
||||
+ " notifications. Not showing more. package=" + pkg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user