From 6ad0aecf99d4dc823935987dc2daff07ef465ac1 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Wed, 5 Jul 2017 08:47:03 -0400 Subject: [PATCH] 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 --- core/java/android/app/Notification.java | 16 + .../src/android/app/NotificationTest.java | 39 + .../NotificationManagerService.java | 75 +- .../NotificationManagerServiceTest.java | 33 + .../statusbartest/NotificationTestList.java | 1267 ++++++++--------- 5 files changed, 768 insertions(+), 662 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 32f01a0d885bd..46e597aada56a 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -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() { diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index bc41922b7df11..5457713112fe6 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -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") diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 861c6d5bdf7b5..57605bb73d503 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -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= 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(); diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index 77953fa0fa304..2e96f6fb888b4 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -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")); + } } diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java index fc68183b5970e..82104034ca6ea 100644 --- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java +++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java @@ -62,16 +62,16 @@ public class NotificationTestList extends TestActivity boolean mProgressDone = true; final int[] kNumberedIconResIDs = { - R.drawable.notification0, - R.drawable.notification1, - R.drawable.notification2, - R.drawable.notification3, - R.drawable.notification4, - R.drawable.notification5, - R.drawable.notification6, - R.drawable.notification7, - R.drawable.notification8, - R.drawable.notification9 + R.drawable.notification0, + R.drawable.notification1, + R.drawable.notification2, + R.drawable.notification3, + R.drawable.notification4, + R.drawable.notification5, + R.drawable.notification6, + R.drawable.notification7, + R.drawable.notification8, + R.drawable.notification9 }; final int kUnnumberedIconResID = R.drawable.notificationx; @@ -438,85 +438,85 @@ public class NotificationTestList extends TestActivity mNM.notify("cancel_madness", 7014, n); } }, - new Test("Off") { - public void run() { - PowerManager pm = (PowerManager) NotificationTestList.this.getSystemService( - Context.POWER_SERVICE); - PowerManager.WakeLock wl = + new Test("Off") { + public void run() { + PowerManager pm = (PowerManager) NotificationTestList.this.getSystemService( + Context.POWER_SERVICE); + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "sound"); - wl.acquire(); + wl.acquire(); - pm.goToSleep(SystemClock.uptimeMillis()); + pm.goToSleep(SystemClock.uptimeMillis()); - Notification n = new Notification.Builder(NotificationTestList.this, "default") - .setSmallIcon(R.drawable.stat_sys_phone) - .setContentTitle(name) - .build(); - Log.d(TAG, "n.sound=" + n.sound); + Notification n = new Notification.Builder(NotificationTestList.this, "default") + .setSmallIcon(R.drawable.stat_sys_phone) + .setContentTitle(name) + .build(); + Log.d(TAG, "n.sound=" + n.sound); - mNM.notify(1, n); + mNM.notify(1, n); - Log.d(TAG, "releasing wake lock"); - wl.release(); - Log.d(TAG, "released wake lock"); - } - }, + Log.d(TAG, "releasing wake lock"); + wl.release(); + Log.d(TAG, "released wake lock"); + } + }, - new Test("Cancel #1") { - public void run() - { - mNM.cancel(1); - } - }, + new Test("Cancel #1") { + public void run() + { + mNM.cancel(1); + } + }, - new Test("Custom Button") { - public void run() { - RemoteViews view = new RemoteViews(getPackageName(), R.layout.button_notification); - view.setOnClickPendingIntent(R.id.button, makeIntent2()); - Notification n = new Notification.Builder(NotificationTestList.this, "default") - .setSmallIcon(R.drawable.icon1) - .setWhen(mActivityCreateTime) - .setContentTitle(name) - .setOngoing(true) - .setCustomContentView(view) - .build(); + new Test("Custom Button") { + public void run() { + RemoteViews view = new RemoteViews(getPackageName(), R.layout.button_notification); + view.setOnClickPendingIntent(R.id.button, makeIntent2()); + Notification n = new Notification.Builder(NotificationTestList.this, "default") + .setSmallIcon(R.drawable.icon1) + .setWhen(mActivityCreateTime) + .setContentTitle(name) + .setOngoing(true) + .setCustomContentView(view) + .build(); - mNM.notify(1, n); - } - }, + mNM.notify(1, n); + } + }, - new Test("Action Button") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "default") - .setSmallIcon(R.drawable.icon1) - .setWhen(mActivityCreateTime) - .setContentTitle(name) - .setOngoing(true) - .addAction(new Notification.Action.Builder( - Icon.createWithResource(NotificationTestList.this, - R.drawable.ic_statusbar_chat), - "Button", makeIntent2()) - .build()) - .build(); + new Test("Action Button") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "default") + .setSmallIcon(R.drawable.icon1) + .setWhen(mActivityCreateTime) + .setContentTitle(name) + .setOngoing(true) + .addAction(new Notification.Action.Builder( + Icon.createWithResource(NotificationTestList.this, + R.drawable.ic_statusbar_chat), + "Button", makeIntent2()) + .build()) + .build(); - mNM.notify(1, n); - } - }, + mNM.notify(1, n); + } + }, - new Test("with intent") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "default") - .setSmallIcon(R.drawable.icon1) - .setWhen(mActivityCreateTime) - .setContentTitle("Persistent #1") - .setContentText("This is a notification!!!") - .setContentIntent(makeIntent2()) - .setOngoing(true) - .build(); + new Test("with intent") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "default") + .setSmallIcon(R.drawable.icon1) + .setWhen(mActivityCreateTime) + .setContentTitle("Persistent #1") + .setContentText("This is a notification!!!") + .setContentIntent(makeIntent2()) + .setOngoing(true) + .build(); - mNM.notify(1, n); - } - }, + mNM.notify(1, n); + } + }, new Test("Is blocked?") { public void run() { @@ -534,484 +534,484 @@ public class NotificationTestList extends TestActivity } }, - new Test("Whens") { - public void run() - { - Notification.Builder n = new Notification.Builder( - NotificationTestList.this, "default") - .setSmallIcon(R.drawable.icon1) - .setContentTitle(name) - .setOngoing(true); + new Test("Whens") { + public void run() + { + Notification.Builder n = new Notification.Builder( + NotificationTestList.this, "default") + .setSmallIcon(R.drawable.icon1) + .setContentTitle(name) + .setOngoing(true); - mNM.notify(1, n.setContentTitle("(453) 123-2328") - .setWhen(System.currentTimeMillis()-(1000*60*60*24)) - .build()); + mNM.notify(1, n.setContentTitle("(453) 123-2328") + .setWhen(System.currentTimeMillis()-(1000*60*60*24)) + .build()); - mNM.notify(1, n.setContentTitle("Mark Willem, Me (2)") - .setWhen(System.currentTimeMillis()) - .build()); + mNM.notify(1, n.setContentTitle("Mark Willem, Me (2)") + .setWhen(System.currentTimeMillis()) + .build()); - mNM.notify(1, n.setContentTitle("Sophia Winterlanden") - .setWhen(System.currentTimeMillis() + (1000 * 60 * 60 * 24)) - .build()); - } - }, - - new Test("Bad Icon #1 (when=create)") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.layout.chrono_notification /* not an icon */) - .setWhen(mActivityCreateTime) - .setContentTitle("Persistent #1") - .setContentText("This is the same notification!!") - .setContentIntent(makeIntent()) - .build(); - mNM.notify(1, n); - } - }, - - new Test("Bad Icon #1 (when=now)") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.layout.chrono_notification /* not an icon */) - .setWhen(System.currentTimeMillis()) - .setContentTitle("Persistent #1") - .setContentText("This is the same notification!!") - .setContentIntent(makeIntent()) - .build(); - mNM.notify(1, n); - } - }, - - new Test("Null Icon #1 (when=now)") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(0) - .setWhen(System.currentTimeMillis()) - .setContentTitle("Persistent #1") - .setContentText("This is the same notification!!") - .setContentIntent(makeIntent()) - .build(); - mNM.notify(1, n); - } - }, - - new Test("Bad resource #1 (when=create)") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon2) - .setWhen(mActivityCreateTime) - .setContentTitle("Persistent #1") - .setContentText("This is the same notification!!") - .setContentIntent(makeIntent()) - .build(); - n.contentView.setInt(1 /*bogus*/, "bogus method", 666); - mNM.notify(1, n); - } - }, - - new Test("Bad resource #1 (when=now)") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon2) - .setWhen(System.currentTimeMillis()) - .setContentTitle("Persistent #1") - .setContentText("This is the same notification!!") - .setContentIntent(makeIntent()) - .build(); - n.contentView.setInt(1 /*bogus*/, "bogus method", 666); - mNM.notify(1, n); - } - }, - - new Test("Times") { - public void run() - { - long now = System.currentTimeMillis(); - - timeNotification(7, "24 hours from now", now+(1000*60*60*24)); - timeNotification(6, "12:01:00 from now", now+(1000*60*60*12)+(60*1000)); - timeNotification(5, "12 hours from now", now+(1000*60*60*12)); - timeNotification(4, "now", now); - timeNotification(3, "11:59:00 ago", now-((1000*60*60*12)-(60*1000))); - timeNotification(2, "12 hours ago", now-(1000*60*60*12)); - timeNotification(1, "24 hours ago", now-(1000*60*60*24)); - } - }, - new StateStress("Stress - Ongoing / Latest", 100, 100, new Runnable[] { - new Runnable() { - public void run() { - Log.d(TAG, "Stress - Ongoing/Latest 0"); - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon3) - .setWhen(System.currentTimeMillis()) - .setContentTitle("Stress - Ongoing") - .setContentText("Notify me!!!") - .setOngoing(true) - .build(); - mNM.notify(1, n); - } - }, - new Runnable() { - public void run() { - Log.d(TAG, "Stress - Ongoing/Latest 1"); - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon4) - .setWhen(System.currentTimeMillis()) - .setContentTitle("Stress - Latest") - .setContentText("Notify me!!!") - .build(); - mNM.notify(1, n); - } + mNM.notify(1, n.setContentTitle("Sophia Winterlanden") + .setWhen(System.currentTimeMillis() + (1000 * 60 * 60 * 24)) + .build()); } - }), + }, - new Test("Long") { - public void run() - { - NotificationChannel channel = new NotificationChannel("v. noisy", - "channel for sound and a custom vibration", IMPORTANCE_DEFAULT); - channel.enableVibration(true); - channel.setVibrationPattern(new long[] { - 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, - 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, - 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 }); - mNM.createNotificationChannel(channel); + new Test("Bad Icon #1 (when=create)") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.layout.chrono_notification /* not an icon */) + .setWhen(mActivityCreateTime) + .setContentTitle("Persistent #1") + .setContentText("This is the same notification!!") + .setContentIntent(makeIntent()) + .build(); + mNM.notify(1, n); + } + }, - Notification n = new Notification.Builder(NotificationTestList.this, "v. noisy") - .setSmallIcon(R.drawable.icon1) - .setContentTitle(name) - .build(); - mNM.notify(1, n); - } - }, + new Test("Bad Icon #1 (when=now)") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.layout.chrono_notification /* not an icon */) + .setWhen(System.currentTimeMillis()) + .setContentTitle("Persistent #1") + .setContentText("This is the same notification!!") + .setContentIntent(makeIntent()) + .build(); + mNM.notify(1, n); + } + }, - new Test("Progress #1") { - public void run() { - final boolean PROGRESS_UPDATES_WHEN = true; - if (!mProgressDone) return; - mProgressDone = false; - Thread t = new Thread() { - public void run() { - int x = 0; - final Notification.Builder n = new Notification.Builder( - NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon1) - .setContentTitle(name) - .setOngoing(true); + new Test("Null Icon #1 (when=now)") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(0) + .setWhen(System.currentTimeMillis()) + .setContentTitle("Persistent #1") + .setContentText("This is the same notification!!") + .setContentIntent(makeIntent()) + .build(); + mNM.notify(1, n); + } + }, - while (!mProgressDone) { - n.setWhen(PROGRESS_UPDATES_WHEN - ? System.currentTimeMillis() - : mActivityCreateTime); - n.setProgress(100, x, false); - n.setContentText("Progress: " + x + "%"); + new Test("Bad resource #1 (when=create)") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon2) + .setWhen(mActivityCreateTime) + .setContentTitle("Persistent #1") + .setContentText("This is the same notification!!") + .setContentIntent(makeIntent()) + .build(); + n.contentView.setInt(1 /*bogus*/, "bogus method", 666); + mNM.notify(1, n); + } + }, - mNM.notify(500, n.build()); - x = (x + 7) % 100; + new Test("Bad resource #1 (when=now)") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon2) + .setWhen(System.currentTimeMillis()) + .setContentTitle("Persistent #1") + .setContentText("This is the same notification!!") + .setContentIntent(makeIntent()) + .build(); + n.contentView.setInt(1 /*bogus*/, "bogus method", 666); + mNM.notify(1, n); + } + }, - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - break; - } + new Test("Times") { + public void run() + { + long now = System.currentTimeMillis(); + + timeNotification(7, "24 hours from now", now+(1000*60*60*24)); + timeNotification(6, "12:01:00 from now", now+(1000*60*60*12)+(60*1000)); + timeNotification(5, "12 hours from now", now+(1000*60*60*12)); + timeNotification(4, "now", now); + timeNotification(3, "11:59:00 ago", now-((1000*60*60*12)-(60*1000))); + timeNotification(2, "12 hours ago", now-(1000*60*60*12)); + timeNotification(1, "24 hours ago", now-(1000*60*60*24)); + } + }, + new StateStress("Stress - Ongoing / Latest", 100, 100, new Runnable[] { + new Runnable() { + public void run() { + Log.d(TAG, "Stress - Ongoing/Latest 0"); + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon3) + .setWhen(System.currentTimeMillis()) + .setContentTitle("Stress - Ongoing") + .setContentText("Notify me!!!") + .setOngoing(true) + .build(); + mNM.notify(1, n); + } + }, + new Runnable() { + public void run() { + Log.d(TAG, "Stress - Ongoing/Latest 1"); + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon4) + .setWhen(System.currentTimeMillis()) + .setContentTitle("Stress - Latest") + .setContentText("Notify me!!!") + .build(); + mNM.notify(1, n); } } - }; - t.start(); - } - }, + }), - new Test("Stop Progress") { - public void run() { - mProgressDone = true; - mNM.cancel(500); - } - }, + new Test("Long") { + public void run() + { + NotificationChannel channel = new NotificationChannel("v. noisy", + "channel for sound and a custom vibration", IMPORTANCE_DEFAULT); + channel.enableVibration(true); + channel.setVibrationPattern(new long[] { + 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, + 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, + 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 }); + mNM.createNotificationChannel(channel); - new Test("Blue Lights") { - public void run() - { - NotificationChannel channel = new NotificationChannel("blue", - "blue", IMPORTANCE_DEFAULT); - channel.enableLights(true); - channel.setLightColor(0xff0000ff); - mNM.createNotificationChannel(channel); + Notification n = new Notification.Builder(NotificationTestList.this, "v. noisy") + .setSmallIcon(R.drawable.icon1) + .setContentTitle(name) + .build(); + mNM.notify(1, n); + } + }, - Notification n = new Notification.Builder(NotificationTestList.this, "blue") - .setSmallIcon(R.drawable.icon2) - .setContentTitle(name) - .build(); - mNM.notify(1, n); - } - }, + new Test("Progress #1") { + public void run() { + final boolean PROGRESS_UPDATES_WHEN = true; + if (!mProgressDone) return; + mProgressDone = false; + Thread t = new Thread() { + public void run() { + int x = 0; + final Notification.Builder n = new Notification.Builder( + NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon1) + .setContentTitle(name) + .setOngoing(true); - new Test("Red Lights") { - public void run() - { - NotificationChannel channel = new NotificationChannel("red", - "red", IMPORTANCE_DEFAULT); - channel.enableLights(true); - channel.setLightColor(0xffff0000); - mNM.createNotificationChannel(channel); + while (!mProgressDone) { + n.setWhen(PROGRESS_UPDATES_WHEN + ? System.currentTimeMillis() + : mActivityCreateTime); + n.setProgress(100, x, false); + n.setContentText("Progress: " + x + "%"); - Notification n = new Notification.Builder(NotificationTestList.this, "red") - .setSmallIcon(R.drawable.icon2) - .setContentTitle(name) - .build(); - mNM.notify(1, n); - } - }, + mNM.notify(500, n.build()); + x = (x + 7) % 100; - new Test("Lights off") { - public void run() - { - Notification n = new Notification.Builder(NotificationTestList.this, "default") - .setSmallIcon(R.drawable.icon2) - .setContentTitle(name) - .build(); - mNM.notify(1, n); - } - }, - - new Test("Alert once") { - public void run() - { - Notification n = new Notification.Builder(NotificationTestList.this, "high") - .setSmallIcon(R.drawable.icon2) - .setContentTitle(name) - .setOnlyAlertOnce(true) - .build(); - mNM.notify(1, n); - } - }, - - new Test("Resource Sound") { - public void run() - { - NotificationChannel channel = new NotificationChannel("res_sound", - "resource sound", IMPORTANCE_DEFAULT); - channel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + - getPackageName() + "/raw/ringer"), Notification.AUDIO_ATTRIBUTES_DEFAULT); - mNM.createNotificationChannel(channel); - - Notification n = new Notification.Builder(NotificationTestList.this, "res_sound") - .setSmallIcon(R.drawable.stat_sys_phone) - .setContentTitle(name) - .build(); - Log.d(TAG, "n.sound=" + n.sound); - - mNM.notify(1, n); - } - }, - - new Test("Sound and Cancel") { - public void run() - { - NotificationChannel channel = new NotificationChannel("res_sound", - "resource sound", IMPORTANCE_DEFAULT); - channel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + - getPackageName() + "/raw/ringer"), Notification.AUDIO_ATTRIBUTES_DEFAULT); - mNM.createNotificationChannel(channel); - - Notification n = new Notification.Builder(NotificationTestList.this, "res_sound") - .setSmallIcon(R.drawable.stat_sys_phone) - .setContentTitle(name) - .build(); - - mNM.notify(1, n); - SystemClock.sleep(600); - mNM.cancel(1); - } - }, - - new Test("Vibrate and cancel") { - public void run() - { - NotificationChannel channel = new NotificationChannel("vibrate", - "vibrate", IMPORTANCE_DEFAULT); - channel.enableVibration(true); - channel.setVibrationPattern(new long[] {0, 700, 500, 1000, 0, 700, 500, 1000, - 0, 700, 500, 1000, 0, 700, 500, 1000, 0, 700, 500, 1000, 0, 700, 500, 1000, - 0, 700, 500, 1000, 0, 700, 500, 1000}); - mNM.createNotificationChannel(channel); - - Notification n = new Notification.Builder(NotificationTestList.this, "vibrate") - .setSmallIcon(R.drawable.stat_sys_phone) - .setContentTitle(name) - .build(); - - mNM.notify(1, n); - SystemClock.sleep(500); - mNM.cancel(1); - } - }, - - new Test("Vibrate pattern") { - public void run() - { - mVibrator.vibrate(new long[] { 250, 1000, 500, 2000 }, -1); - } - }, - - new Test("Vibrate pattern repeating") { - public void run() - { - mVibrator.vibrate(new long[] { 250, 1000, 500 }, 1); - } - }, - - new Test("Vibrate 3s") { - public void run() - { - mVibrator.vibrate(3000); - } - }, - - new Test("Vibrate 100s") { - public void run() - { - mVibrator.vibrate(100000); - } - }, - - new Test("Vibrate off") { - public void run() - { - mVibrator.cancel(); - } - }, - - new Test("Cancel #1") { - public void run() { - mNM.cancel(1); - } - }, - - new Test("Cancel #1 in 3 sec") { - public void run() { - mHandler.postDelayed(new Runnable() { - public void run() { - Log.d(TAG, "Cancelling now..."); - mNM.cancel(1); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } } - }, 3000); - } - }, + } + }; + t.start(); + } + }, - new Test("Cancel #2") { - public void run() { - mNM.cancel(2); - } - }, + new Test("Stop Progress") { + public void run() { + mProgressDone = true; + mNM.cancel(500); + } + }, - new Test("Persistent #1") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this) - .setSmallIcon(R.drawable.icon1) - .setWhen(mActivityCreateTime) - .setContentTitle(name) - .setContentText("This is a notification!!!") - .setContentIntent(makeIntent()) - .build(); - mNM.notify(1, n); - } - }, + new Test("Blue Lights") { + public void run() + { + NotificationChannel channel = new NotificationChannel("blue", + "blue", IMPORTANCE_DEFAULT); + channel.enableLights(true); + channel.setLightColor(0xff0000ff); + mNM.createNotificationChannel(channel); - new Test("Persistent #1 in 3 sec") { - public void run() { - mHandler.postDelayed(new Runnable() { - public void run() { - String message = " " - + "tick tock tick tock\n\nSometimes notifications can " - + "be really long and wrap to more than one line.\n" - + "Sometimes." - + "Ohandwhathappensifwehaveonereallylongstringarewesure" - + "thatwesegmentitcorrectly?\n"; - Notification n = new Notification.Builder( - NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon1) - .setContentTitle(name) - .setContentText("This is still a notification!!!") - .setContentIntent(makeIntent()) - .setStyle(new Notification.BigTextStyle().bigText(message)) - .build(); - mNM.notify(1, n); - } - }, 3000); - } - }, + Notification n = new Notification.Builder(NotificationTestList.this, "blue") + .setSmallIcon(R.drawable.icon2) + .setContentTitle(name) + .build(); + mNM.notify(1, n); + } + }, - new Test("Persistent #2") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon1) - .setWhen(mActivityCreateTime) - .setContentTitle(name) - .setContentText("This is a notification!!!") - .setContentIntent(makeIntent()) - .build(); - mNM.notify(2, n); - } - }, + new Test("Red Lights") { + public void run() + { + NotificationChannel channel = new NotificationChannel("red", + "red", IMPORTANCE_DEFAULT); + channel.enableLights(true); + channel.setLightColor(0xffff0000); + mNM.createNotificationChannel(channel); - new Test("Persistent #3") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon1) - .setWhen(mActivityCreateTime) - .setContentTitle(name) - .setContentText("This is a notification!!!") - .setContentIntent(makeIntent()) - .build(); - mNM.notify(3, n); - } - }, + Notification n = new Notification.Builder(NotificationTestList.this, "red") + .setSmallIcon(R.drawable.icon2) + .setContentTitle(name) + .build(); + mNM.notify(1, n); + } + }, - new Test("Persistent #2 Vibrate") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon1) - .setWhen(mActivityCreateTime) - .setContentTitle(name) - .setContentText("This is a notification!!!") - .setContentIntent(makeIntent()) - .setDefaults(Notification.DEFAULT_VIBRATE) - .build(); - mNM.notify(2, n); - } - }, + new Test("Lights off") { + public void run() + { + Notification n = new Notification.Builder(NotificationTestList.this, "default") + .setSmallIcon(R.drawable.icon2) + .setContentTitle(name) + .build(); + mNM.notify(1, n); + } + }, - new Test("Persistent #1 - different icon") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon2) - .setWhen(mActivityCreateTime) - .setContentTitle(name) - .setContentText("This is a notification!!!") - .setContentIntent(makeIntent()) - .build(); - mNM.notify(1, n); - } - }, + new Test("Alert once") { + public void run() + { + Notification n = new Notification.Builder(NotificationTestList.this, "high") + .setSmallIcon(R.drawable.icon2) + .setContentTitle(name) + .setOnlyAlertOnce(true) + .build(); + mNM.notify(1, n); + } + }, - new Test("Chronometer Start") { - public void run() { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(R.drawable.icon1) - .setWhen(System.currentTimeMillis()) - .setContentTitle(name) - .setContentIntent(makeIntent()) - .setOngoing(true) - .setUsesChronometer(true) - .build(); - mNM.notify(2, n); - } - }, + new Test("Resource Sound") { + public void run() + { + NotificationChannel channel = new NotificationChannel("res_sound", + "resource sound", IMPORTANCE_DEFAULT); + channel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + + getPackageName() + "/raw/ringer"), Notification.AUDIO_ATTRIBUTES_DEFAULT); + mNM.createNotificationChannel(channel); - new Test("Chronometer Stop") { - public void run() { - mHandler.postDelayed(new Runnable() { + Notification n = new Notification.Builder(NotificationTestList.this, "res_sound") + .setSmallIcon(R.drawable.stat_sys_phone) + .setContentTitle(name) + .build(); + Log.d(TAG, "n.sound=" + n.sound); + + mNM.notify(1, n); + } + }, + + new Test("Sound and Cancel") { + public void run() + { + NotificationChannel channel = new NotificationChannel("res_sound", + "resource sound", IMPORTANCE_DEFAULT); + channel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + + getPackageName() + "/raw/ringer"), Notification.AUDIO_ATTRIBUTES_DEFAULT); + mNM.createNotificationChannel(channel); + + Notification n = new Notification.Builder(NotificationTestList.this, "res_sound") + .setSmallIcon(R.drawable.stat_sys_phone) + .setContentTitle(name) + .build(); + + mNM.notify(1, n); + SystemClock.sleep(600); + mNM.cancel(1); + } + }, + + new Test("Vibrate and cancel") { + public void run() + { + NotificationChannel channel = new NotificationChannel("vibrate", + "vibrate", IMPORTANCE_DEFAULT); + channel.enableVibration(true); + channel.setVibrationPattern(new long[] {0, 700, 500, 1000, 0, 700, 500, 1000, + 0, 700, 500, 1000, 0, 700, 500, 1000, 0, 700, 500, 1000, 0, 700, 500, 1000, + 0, 700, 500, 1000, 0, 700, 500, 1000}); + mNM.createNotificationChannel(channel); + + Notification n = new Notification.Builder(NotificationTestList.this, "vibrate") + .setSmallIcon(R.drawable.stat_sys_phone) + .setContentTitle(name) + .build(); + + mNM.notify(1, n); + SystemClock.sleep(500); + mNM.cancel(1); + } + }, + + new Test("Vibrate pattern") { + public void run() + { + mVibrator.vibrate(new long[] { 250, 1000, 500, 2000 }, -1); + } + }, + + new Test("Vibrate pattern repeating") { + public void run() + { + mVibrator.vibrate(new long[] { 250, 1000, 500 }, 1); + } + }, + + new Test("Vibrate 3s") { + public void run() + { + mVibrator.vibrate(3000); + } + }, + + new Test("Vibrate 100s") { + public void run() + { + mVibrator.vibrate(100000); + } + }, + + new Test("Vibrate off") { + public void run() + { + mVibrator.cancel(); + } + }, + + new Test("Cancel #1") { + public void run() { + mNM.cancel(1); + } + }, + + new Test("Cancel #1 in 3 sec") { + public void run() { + mHandler.postDelayed(new Runnable() { + public void run() { + Log.d(TAG, "Cancelling now..."); + mNM.cancel(1); + } + }, 3000); + } + }, + + new Test("Cancel #2") { + public void run() { + mNM.cancel(2); + } + }, + + new Test("Persistent #1") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this) + .setSmallIcon(R.drawable.icon1) + .setWhen(mActivityCreateTime) + .setContentTitle(name) + .setContentText("This is a notification!!!") + .setContentIntent(makeIntent()) + .build(); + mNM.notify(1, n); + } + }, + + new Test("Persistent #1 in 3 sec") { + public void run() { + mHandler.postDelayed(new Runnable() { + public void run() { + String message = " " + + "tick tock tick tock\n\nSometimes notifications can " + + "be really long and wrap to more than one line.\n" + + "Sometimes." + + "Ohandwhathappensifwehaveonereallylongstringarewesure" + + "thatwesegmentitcorrectly?\n"; + Notification n = new Notification.Builder( + NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon1) + .setContentTitle(name) + .setContentText("This is still a notification!!!") + .setContentIntent(makeIntent()) + .setStyle(new Notification.BigTextStyle().bigText(message)) + .build(); + mNM.notify(1, n); + } + }, 3000); + } + }, + + new Test("Persistent #2") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon1) + .setWhen(mActivityCreateTime) + .setContentTitle(name) + .setContentText("This is a notification!!!") + .setContentIntent(makeIntent()) + .build(); + mNM.notify(2, n); + } + }, + + new Test("Persistent #3") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon1) + .setWhen(mActivityCreateTime) + .setContentTitle(name) + .setContentText("This is a notification!!!") + .setContentIntent(makeIntent()) + .build(); + mNM.notify(3, n); + } + }, + + new Test("Persistent #2 Vibrate") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon1) + .setWhen(mActivityCreateTime) + .setContentTitle(name) + .setContentText("This is a notification!!!") + .setContentIntent(makeIntent()) + .setDefaults(Notification.DEFAULT_VIBRATE) + .build(); + mNM.notify(2, n); + } + }, + + new Test("Persistent #1 - different icon") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon2) + .setWhen(mActivityCreateTime) + .setContentTitle(name) + .setContentText("This is a notification!!!") + .setContentIntent(makeIntent()) + .build(); + mNM.notify(1, n); + } + }, + + new Test("Chronometer Start") { + public void run() { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(R.drawable.icon1) + .setWhen(System.currentTimeMillis()) + .setContentTitle(name) + .setContentIntent(makeIntent()) + .setOngoing(true) + .setUsesChronometer(true) + .build(); + mNM.notify(2, n); + } + }, + + new Test("Chronometer Stop") { + public void run() { + mHandler.postDelayed(new Runnable() { public void run() { Log.d(TAG, "Chronometer Stop"); Notification n = new Notification.Builder( @@ -1024,121 +1024,121 @@ public class NotificationTestList extends TestActivity mNM.notify(2, n); } }, 3000); - } - }, - - new Test("Sequential Persistent") { - public void run() { - mNM.notify(1, notificationWithNumbers(name, 1)); - mNM.notify(2, notificationWithNumbers(name, 2)); - } - }, - - new Test("Replace Persistent") { - public void run() { - mNM.notify(1, notificationWithNumbers(name, 1)); - mNM.notify(1, notificationWithNumbers(name, 1)); - } - }, - - new Test("Run and Cancel (n=1)") { - public void run() { - mNM.notify(1, notificationWithNumbers(name, 1)); - mNM.cancel(1); - } - }, - - new Test("Run an Cancel (n=2)") { - public void run() { - mNM.notify(1, notificationWithNumbers(name, 1)); - mNM.notify(2, notificationWithNumbers(name, 2)); - mNM.cancel(2); - } - }, - - // Repeatedly notify and cancel -- triggers bug #670627 - new Test("Bug 670627") { - public void run() { - for (int i = 0; i < 10; i++) { - Log.d(TAG, "Add two notifications"); - mNM.notify(1, notificationWithNumbers(name, 1)); - mNM.notify(2, notificationWithNumbers(name, 2)); - Log.d(TAG, "Cancel two notifications"); - mNM.cancel(1); - mNM.cancel(2); } - } - }, + }, - new Test("Ten Notifications") { - public void run() { - for (int i = 0; i < 10; i++) { - Notification n = new Notification.Builder(NotificationTestList.this, "low") - .setSmallIcon(kNumberedIconResIDs[i]) - .setContentTitle("Persistent #" + i) - .setContentText("Notify me!!!" + i) - .setOngoing(i < 2) - .setNumber(i) - .build(); - mNM.notify((i+1)*10, n); + new Test("Sequential Persistent") { + public void run() { + mNM.notify(1, notificationWithNumbers(name, 1)); + mNM.notify(2, notificationWithNumbers(name, 2)); } - } - }, - - new Test("Cancel eight notifications") { - public void run() { - for (int i = 1; i < 9; i++) { - mNM.cancel((i+1)*10); + }, + + new Test("Replace Persistent") { + public void run() { + mNM.notify(1, notificationWithNumbers(name, 1)); + mNM.notify(1, notificationWithNumbers(name, 1)); } - } - }, - - new Test("Cancel the other two notifications") { - public void run() { - mNM.cancel(10); - mNM.cancel(100); - } - }, - - new Test("Persistent with numbers 1") { - public void run() { - mNM.notify(1, notificationWithNumbers(name, 1)); - } - }, + }, - new Test("Persistent with numbers 22") { - public void run() { - mNM.notify(1, notificationWithNumbers(name, 22)); - } - }, + new Test("Run and Cancel (n=1)") { + public void run() { + mNM.notify(1, notificationWithNumbers(name, 1)); + mNM.cancel(1); + } + }, - new Test("Persistent with numbers 333") { - public void run() { - mNM.notify(1, notificationWithNumbers(name, 333)); - } - }, + new Test("Run an Cancel (n=2)") { + public void run() { + mNM.notify(1, notificationWithNumbers(name, 1)); + mNM.notify(2, notificationWithNumbers(name, 2)); + mNM.cancel(2); + } + }, - new Test("Persistent with numbers 4444") { - public void run() { - mNM.notify(1, notificationWithNumbers(name, 4444)); - } - }, + // Repeatedly notify and cancel -- triggers bug #670627 + new Test("Bug 670627") { + public void run() { + for (int i = 0; i < 10; i++) { + Log.d(TAG, "Add two notifications"); + mNM.notify(1, notificationWithNumbers(name, 1)); + mNM.notify(2, notificationWithNumbers(name, 2)); + Log.d(TAG, "Cancel two notifications"); + mNM.cancel(1); + mNM.cancel(2); + } + } + }, - new Test("Crash") { - public void run() - { - PowerManager.WakeLock wl = - ((PowerManager) NotificationTestList.this.getSystemService(Context.POWER_SERVICE)) - .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "crasher"); - wl.acquire(); - mHandler.postDelayed(new Runnable() { - public void run() { - throw new RuntimeException("Die!"); - } - }, 10000); + new Test("Ten Notifications") { + public void run() { + for (int i = 0; i < 10; i++) { + Notification n = new Notification.Builder(NotificationTestList.this, "low") + .setSmallIcon(kNumberedIconResIDs[i]) + .setContentTitle("Persistent #" + i) + .setContentText("Notify me!!!" + i) + .setOngoing(i < 2) + .setNumber(i) + .build(); + mNM.notify((i+1)*10, n); + } + } + }, - } - }, + new Test("Cancel eight notifications") { + public void run() { + for (int i = 1; i < 9; i++) { + mNM.cancel((i+1)*10); + } + } + }, + + new Test("Cancel the other two notifications") { + public void run() { + mNM.cancel(10); + mNM.cancel(100); + } + }, + + new Test("Persistent with numbers 1") { + public void run() { + mNM.notify(1, notificationWithNumbers(name, 1)); + } + }, + + new Test("Persistent with numbers 22") { + public void run() { + mNM.notify(1, notificationWithNumbers(name, 22)); + } + }, + + new Test("Persistent with numbers 333") { + public void run() { + mNM.notify(1, notificationWithNumbers(name, 333)); + } + }, + + new Test("Persistent with numbers 4444") { + public void run() { + mNM.notify(1, notificationWithNumbers(name, 4444)); + } + }, + + new Test("Crash") { + public void run() + { + PowerManager.WakeLock wl = + ((PowerManager) NotificationTestList.this.getSystemService(Context.POWER_SERVICE)) + .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "crasher"); + wl.acquire(); + mHandler.postDelayed(new Runnable() { + public void run() { + throw new RuntimeException("Die!"); + } + }, 10000); + + } + }, }; @@ -1212,4 +1212,3 @@ public class NotificationTestList extends TestActivity return Bitmap.createBitmap(bd.getBitmap()); } } -