Snoozing API changes
- No one can snooze for an undetermined amount of time
- Only the assistant can unsnooze
- Listeners can retrieve a list of snoozed notifications
Test: runtest systemui-notification, cts verifier
Change-Id: Idfaee6d8bc15a5d41630f86f7e852468b07dc7d0
diff --git a/api/current.txt b/api/current.txt
index 42242a7..ad28b94 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -35814,6 +35814,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+ method public final void unsnoozeNotification(java.lang.String);
method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -35829,6 +35830,7 @@
method public final int getCurrentInterruptionFilter();
method public final int getCurrentListenerHints();
method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
+ method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public android.os.IBinder onBind(android.content.Intent);
method public void onInterruptionFilterChanged(int);
method public void onListenerConnected();
@@ -35847,8 +35849,6 @@
method public final void setNotificationsShown(java.lang.String[]);
method public final void snoozeNotification(java.lang.String, java.lang.String);
method public final void snoozeNotification(java.lang.String, long);
- method public final void snoozeNotification(java.lang.String);
- method public final void unsnoozeNotification(java.lang.String);
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index c159938..b9e89f6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -38797,6 +38797,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+ method public final void unsnoozeNotification(java.lang.String);
method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -38814,6 +38815,7 @@
method public final int getCurrentInterruptionFilter();
method public final int getCurrentListenerHints();
method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
+ method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public android.os.IBinder onBind(android.content.Intent);
method public void onInterruptionFilterChanged(int);
method public void onListenerConnected();
@@ -38834,9 +38836,7 @@
method public final void setOnNotificationPostedTrim(int);
method public final void snoozeNotification(java.lang.String, java.lang.String);
method public final void snoozeNotification(java.lang.String, long);
- method public final void snoozeNotification(java.lang.String);
method public void unregisterAsSystemService() throws android.os.RemoteException;
- method public final void unsnoozeNotification(java.lang.String);
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
diff --git a/api/test-current.txt b/api/test-current.txt
index cc17d7f..0e6548e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -35935,6 +35935,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+ method public final void unsnoozeNotification(java.lang.String);
method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -35950,6 +35951,7 @@
method public final int getCurrentInterruptionFilter();
method public final int getCurrentListenerHints();
method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
+ method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public android.os.IBinder onBind(android.content.Intent);
method public void onInterruptionFilterChanged(int);
method public void onListenerConnected();
@@ -35968,8 +35970,6 @@
method public final void setNotificationsShown(java.lang.String[]);
method public final void snoozeNotification(java.lang.String, java.lang.String);
method public final void snoozeNotification(java.lang.String, long);
- method public final void snoozeNotification(java.lang.String);
- method public final void unsnoozeNotification(java.lang.String);
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index d674bfe..740af9c 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -75,8 +75,6 @@
void snoozeNotificationUntilContextFromListener(in INotificationListener token, String key, String snoozeCriterionId);
void snoozeNotificationUntilFromListener(in INotificationListener token, String key, long until);
- void snoozeNotificationFromListener(in INotificationListener token, String key);
- void unsnoozeNotificationFromListener(in INotificationListener token, String key);
void requestBindListener(in ComponentName component);
void requestUnbindListener(in INotificationListener token);
@@ -86,6 +84,7 @@
void setNotificationsShownFromListener(in INotificationListener token, in String[] keys);
ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim);
+ ParceledListSlice getSnoozedNotificationsFromListener(in INotificationListener token, int trim);
void requestHintsFromListener(in INotificationListener token, int hints);
int getHintsFromListener(in INotificationListener token);
void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
@@ -100,6 +99,7 @@
void updateNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel);
void deleteNotificationChannelFromAssistant(in INotificationListener token, String pkg, String channelId);
ParceledListSlice getNotificationChannelsFromAssistant(in INotificationListener token, String pkg);
+ void unsnoozeNotificationFromAssistant(in INotificationListener token, String key);
ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index d7a02a8..cecdbee 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -119,6 +119,24 @@
}
/**
+ * Inform the notification manager about un-snoozing a specific notification.
+ * <p>
+ * This should only be used for notifications snoozed by this listener using
+ * {@link #snoozeNotification(String, String)}. Once un-snoozed, you will get a
+ * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
+ * notification.
+ * @param key The key of the notification to snooze
+ */
+ public final void unsnoozeNotification(String key) {
+ if (!isBound()) return;
+ try {
+ getNotificationInterface().unsnoozeNotificationFromAssistant(mWrapper, key);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ }
+
+ /**
* Creates a notification channel that notifications can be posted to for a given package.
*
* @param pkg The package to create a channel for.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index d930689..517b305 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -562,43 +562,6 @@
}
}
- /**
- * Inform the notification manager about snoozing a specific notification.
- * <p>
- * Use this to snooze a notification for an indeterminate time. Upon being informed, the
- * notification manager will actually remove the notification and you will get an
- * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the
- * snoozing period expires, you will get a
- * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
- * notification. Use {@link #unsnoozeNotification(String)} to restore the notification.
- * @param key The key of the notification to snooze
- */
- public final void snoozeNotification(String key) {
- if (!isBound()) return;
- try {
- getNotificationInterface().snoozeNotificationFromListener(mWrapper, key);
- } catch (android.os.RemoteException ex) {
- Log.v(TAG, "Unable to contact notification manager", ex);
- }
- }
-
- /**
- * Inform the notification manager about un-snoozing a specific notification.
- * <p>
- * This should only be used for notifications snoozed by this listener using
- * {@link #snoozeNotification(String)}. Once un-snoozed, you will get a
- * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
- * notification.
- * @param key The key of the notification to snooze
- */
- public final void unsnoozeNotification(String key) {
- if (!isBound()) return;
- try {
- getNotificationInterface().unsnoozeNotificationFromListener(mWrapper, key);
- } catch (android.os.RemoteException ex) {
- Log.v(TAG, "Unable to contact notification manager", ex);
- }
- }
/**
* Inform the notification manager that these notifications have been viewed by the
@@ -663,6 +626,26 @@
}
/**
+ * Like {@link #getActiveNotifications()}, but returns the list of currently snoozed
+ * notifications, for all users this listener has access to.
+ *
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
+ * @return An array of active notifications, sorted in natural order.
+ */
+ public final StatusBarNotification[] getSnoozedNotifications() {
+ try {
+ ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
+ .getSnoozedNotificationsFromListener(mWrapper, TRIM_FULL);
+ return cleanUpNotificationList(parceledList);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ return null;
+ }
+
+ /**
* Request the list of outstanding notifications (that is, those that are visible to the
* current user). Useful when you don't know what's already been posted.
*
@@ -711,36 +694,41 @@
try {
ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
.getActiveNotificationsFromListener(mWrapper, keys, trim);
- List<StatusBarNotification> list = parceledList.getList();
- ArrayList<StatusBarNotification> corruptNotifications = null;
- int N = list.size();
- for (int i = 0; i < N; i++) {
- StatusBarNotification sbn = list.get(i);
- Notification notification = sbn.getNotification();
- try {
- // convert icon metadata to legacy format for older clients
- createLegacyIconExtras(notification);
- // populate remote views for older clients.
- maybePopulateRemoteViews(notification);
- } catch (IllegalArgumentException e) {
- if (corruptNotifications == null) {
- corruptNotifications = new ArrayList<>(N);
- }
- corruptNotifications.add(sbn);
- Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
- sbn.getPackageName());
- }
- }
- if (corruptNotifications != null) {
- list.removeAll(corruptNotifications);
- }
- return list.toArray(new StatusBarNotification[list.size()]);
+ return cleanUpNotificationList(parceledList);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
return null;
}
+ private StatusBarNotification[] cleanUpNotificationList(
+ ParceledListSlice<StatusBarNotification> parceledList) {
+ List<StatusBarNotification> list = parceledList.getList();
+ ArrayList<StatusBarNotification> corruptNotifications = null;
+ int N = list.size();
+ for (int i = 0; i < N; i++) {
+ StatusBarNotification sbn = list.get(i);
+ Notification notification = sbn.getNotification();
+ try {
+ // convert icon metadata to legacy format for older clients
+ createLegacyIconExtras(notification);
+ // populate remote views for older clients.
+ maybePopulateRemoteViews(notification);
+ } catch (IllegalArgumentException e) {
+ if (corruptNotifications == null) {
+ corruptNotifications = new ArrayList<>(N);
+ }
+ corruptNotifications.add(sbn);
+ Log.w(TAG, "get(Active/Snoozed)Notifications: can't rebuild notification from " +
+ sbn.getPackageName());
+ }
+ }
+ if (corruptNotifications != null) {
+ list.removeAll(corruptNotifications);
+ }
+ return list.toArray(new StatusBarNotification[list.size()]);
+ }
+
/**
* Gets the set of hints representing current state.
*
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 96459be..72faa81 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1944,31 +1944,16 @@
}
/**
- * Allow an INotificationListener to snooze a single notification.
+ * Allows the notification assistant to un-snooze a single notification.
*
- * @param token The binder for the listener, to check that the caller is allowed
+ * @param token The binder for the assistant, to check that the caller is allowed
*/
@Override
- public void snoozeNotificationFromListener(INotificationListener token, String key) {
+ public void unsnoozeNotificationFromAssistant(INotificationListener token, String key) {
long identity = Binder.clearCallingIdentity();
try {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, null, info);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
- * Allow an INotificationListener to un-snooze a single notification.
- *
- * @param token The binder for the listener, to check that the caller is allowed
- */
- @Override
- public void unsnoozeNotificationFromListener(INotificationListener token, String key) {
- long identity = Binder.clearCallingIdentity();
- try {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ final ManagedServiceInfo info =
+ mNotificationAssistants.checkServiceTokenLocked(token);
unsnoozeNotificationInt(key, info);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2039,6 +2024,36 @@
}
}
+ /**
+ * Allow an INotificationListener to request the list of outstanding snoozed notifications
+ * seen by the current user. Useful when starting up, after which point the listener
+ * callbacks should be used.
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ * @returns The return value will contain the notifications specified in keys, in that
+ * order, or if keys is null, all the notifications, in natural order.
+ */
+ @Override
+ public ParceledListSlice<StatusBarNotification> getSnoozedNotificationsFromListener(
+ INotificationListener token, int trim) {
+ synchronized (mNotificationLock) {
+ final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+ List<NotificationRecord> snoozedRecords = mSnoozeHelper.getSnoozed();
+ final int N = snoozedRecords.size();
+ final ArrayList<StatusBarNotification> list = new ArrayList<>(N);
+ for (int i=0; i < N; i++) {
+ final NotificationRecord r = snoozedRecords.get(i);
+ if (r == null) continue;
+ StatusBarNotification sbn = r.sbn;
+ if (!isVisibleToListener(sbn, info)) continue;
+ StatusBarNotification sbnToSend =
+ (trim == TRIM_FULL) ? sbn : sbn.cloneLight();
+ list.add(sbnToSend);
+ }
+ return new ParceledListSlice<>(list);
+ }
+ }
+
@Override
public void requestHintsFromListener(INotificationListener token, int hints) {
final long identity = Binder.clearCallingIdentity();
@@ -3982,14 +3997,14 @@
void snoozeNotificationInt(String key, long until, String snoozeCriterionId,
ManagedServiceInfo listener) {
String listenerName = listener == null ? null : listener.component.toShortString();
+ if (until < System.currentTimeMillis() && snoozeCriterionId == null) {
+ return;
+ }
// TODO: write to event log
if (DBG) {
Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, until, snoozeCriterionId,
listenerName));
}
- if (until != SNOOZE_UNTIL_UNSPECIFIED && until < System.currentTimeMillis()) {
- return;
- }
// Needs to post so that it can cancel notifications not yet enqueued.
mHandler.post(new Runnable() {
@Override
@@ -4002,8 +4017,6 @@
if (snoozeCriterionId != null) {
mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn,
snoozeCriterionId);
- }
- if (until == SNOOZE_UNTIL_UNSPECIFIED) {
mSnoozeHelper.snooze(r);
} else {
mSnoozeHelper.snooze(r, until);
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index e14700a..f2aff11 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -41,6 +41,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -98,6 +99,26 @@
return Collections.EMPTY_LIST;
}
+ protected List<NotificationRecord> getSnoozed() {
+ List<NotificationRecord> snoozedForUser = new ArrayList<>();
+ int[] userIds = mUserProfiles.getCurrentProfileIds();
+ final int N = userIds.length;
+ for (int i = 0; i < N; i++) {
+ final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
+ mSnoozedNotifications.get(userIds[i]);
+ if (snoozedPkgs != null) {
+ final int M = snoozedPkgs.size();
+ for (int j = 0; j < M; j++) {
+ final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
+ if (records != null) {
+ snoozedForUser.addAll(records.values());
+ }
+ }
+ }
+ }
+ return snoozedForUser;
+ }
+
/**
* Snoozes a notification and schedules an alarm to repost at that time.
*/
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 92d9810..250aab8 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -361,17 +361,4 @@
mBinderService.getActiveNotifications(sbn.getPackageName());
assertEquals(1, notifs.length);
}
-
- @Test
- @UiThreadTest
- public void testSnoozeNotificationImmediatelyAfterEnqueue() throws Exception {
- final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
- sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
- mBinderService.snoozeNotificationFromListener(null, sbn.getKey());
- waitForIdle();
- StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(sbn.getPackageName());
- assertEquals(0, notifs.length);
- }
}
diff --git a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
index b7931d4..69724f4 100644
--- a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
@@ -33,6 +33,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any;
@@ -42,6 +43,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
@SmallTest
@@ -190,6 +192,39 @@
verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
}
+ @Test
+ public void testGetSnoozedByUser() throws Exception {
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
+ NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT);
+ mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(r2, 1000);
+ mSnoozeHelper.snooze(r3, 1000);
+ mSnoozeHelper.snooze(r4, 1000);
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(
+ new int[] {UserHandle.USER_SYSTEM});
+ assertEquals(3, mSnoozeHelper.getSnoozed().size());
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(
+ new int[] {UserHandle.USER_CURRENT});
+ assertEquals(1, mSnoozeHelper.getSnoozed().size());
+ }
+
+ @Test
+ public void testGetSnoozedByUser_managedProfiles() throws Exception {
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(
+ new int[] {UserHandle.USER_SYSTEM, UserHandle.USER_CURRENT});
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+ NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
+ NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT);
+ mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(r2, 1000);
+ mSnoozeHelper.snooze(r3, 1000);
+ mSnoozeHelper.snooze(r4, 1000);
+ assertEquals(4, mSnoozeHelper.getSnoozed().size());
+ }
+
private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
UserHandle user) {
Notification n = new Notification.Builder(getContext())