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
This commit is contained in:
Julia Reynolds
2017-01-24 13:55:48 -05:00
parent 52faea7acb
commit cf63ff1532
10 changed files with 169 additions and 107 deletions

View File

@@ -35814,6 +35814,7 @@ package android.service.notification {
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 @@ package android.service.notification {
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 @@ package android.service.notification {
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

View File

@@ -38797,6 +38797,7 @@ package android.service.notification {
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 @@ package android.service.notification {
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 @@ package android.service.notification {
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

View File

@@ -35935,6 +35935,7 @@ package android.service.notification {
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 @@ package android.service.notification {
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 @@ package android.service.notification {
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

View File

@@ -75,8 +75,6 @@ interface INotificationManager
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 @@ interface INotificationManager
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 @@ interface INotificationManager
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);

View File

@@ -118,6 +118,24 @@ public abstract class NotificationAssistantService extends NotificationListenerS
}
}
/**
* 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.
*

View File

@@ -562,43 +562,6 @@ public abstract class NotificationListenerService extends Service {
}
}
/**
* 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
@@ -662,6 +625,26 @@ public abstract class NotificationListenerService extends Service {
return getActiveNotifications(null, TRIM_FULL);
}
/**
* 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 @@ public abstract class NotificationListenerService extends Service {
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.
*

View File

@@ -1944,31 +1944,16 @@ public class NotificationManagerService extends SystemService {
}
/**
* 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 @@ public class NotificationManagerService extends SystemService {
}
}
/**
* 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 @@ public class NotificationManagerService extends SystemService {
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 @@ public class NotificationManagerService extends SystemService {
if (snoozeCriterionId != null) {
mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn,
snoozeCriterionId);
}
if (until == SNOOZE_UNTIL_UNSPECIFIED) {
mSnoozeHelper.snooze(r);
} else {
mSnoozeHelper.snooze(r, until);

View File

@@ -41,6 +41,7 @@ import java.util.ArrayList;
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 @@ public class SnoozeHelper {
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.
*/

View File

@@ -361,17 +361,4 @@ public class NotificationManagerServiceTest {
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);
}
}

View File

@@ -33,6 +33,7 @@ import android.support.test.InstrumentationRegistry;
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.Matchers.eq;
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 @@ public class SnoozeHelperTest {
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())