diff options
8 files changed, 179 insertions, 51 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index be51491ef2d0..f963c10e6272 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5076,8 +5076,10 @@ package android.service.notification { method public final void adjustNotification(android.service.notification.Adjustment); method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); method public final android.os.IBinder onBind(android.content.Intent); + method public void onNotificationDirectReply(java.lang.String); method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification); method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel); + method public void onNotificationExpansionChanged(java.lang.String, boolean, boolean); method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int); method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String); method public void onNotificationsSeen(java.util.List<java.lang.String>); diff --git a/api/test-current.txt b/api/test-current.txt index 8f0d4b8fa5f3..0fa83f11a4ba 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1156,8 +1156,10 @@ package android.service.notification { method public final void adjustNotification(android.service.notification.Adjustment); method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); method public final android.os.IBinder onBind(android.content.Intent); + method public void onNotificationDirectReply(java.lang.String); method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification); method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel); + method public void onNotificationExpansionChanged(java.lang.String, boolean, boolean); method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String); method public void onNotificationsSeen(java.util.List<java.lang.String>); method public final void unsnoozeNotification(java.lang.String); diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index d8bd00281ba5..0988510f5503 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -47,4 +47,6 @@ oneway interface INotificationListener void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel); void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId); void onNotificationsSeen(in List<String> keys); + void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); + void onNotificationDirectReply(String key); } diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index c1a3c2b5f150..90f4792face9 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -160,6 +160,21 @@ public abstract class NotificationAssistantService extends NotificationListenerS } /** + * Implement this to know when a notification is expanded / collapsed. + * @param key the notification key + * @param isUserAction whether the expanded change is caused by user action. + * @param isExpanded whether the notification is expanded. + */ + public void onNotificationExpansionChanged( + String key, boolean isUserAction, boolean isExpanded) {} + + /** + * Implement this to know when a direct reply is sent from a notification. + * @param key the notification key + */ + public void onNotificationDirectReply(String key) {} + + /** * Updates a notification. N.B. this won’t cause * an existing notification to alert, but might allow a future update to * this notification to alert. @@ -255,12 +270,33 @@ public abstract class NotificationAssistantService extends NotificationListenerS mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATIONS_SEEN, args).sendToTarget(); } + + @Override + public void onNotificationExpansionChanged(String key, boolean isUserAction, + boolean isExpanded) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = key; + args.argi1 = isUserAction ? 1 : 0; + args.argi2 = isExpanded ? 1 : 0; + mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_EXPANSION_CHANGED, args) + .sendToTarget(); + } + + @Override + public void onNotificationDirectReply(String key) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = key; + mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT, args) + .sendToTarget(); + } } private final class MyHandler extends Handler { public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1; public static final int MSG_ON_NOTIFICATION_SNOOZED = 2; public static final int MSG_ON_NOTIFICATIONS_SEEN = 3; + public static final int MSG_ON_NOTIFICATION_EXPANSION_CHANGED = 4; + public static final int MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT = 5; public MyHandler(Looper looper) { super(looper, null, false); @@ -305,6 +341,22 @@ public abstract class NotificationAssistantService extends NotificationListenerS onNotificationsSeen(keys); break; } + case MSG_ON_NOTIFICATION_EXPANSION_CHANGED: { + SomeArgs args = (SomeArgs) msg.obj; + String key = (String) args.arg1; + boolean isUserAction = args.argi1 == 1; + boolean isExpanded = args.argi2 == 1; + args.recycle(); + onNotificationExpansionChanged(key, isUserAction, isExpanded); + break; + } + case MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT: { + SomeArgs args = (SomeArgs) msg.obj; + String key = (String) args.arg1; + args.recycle(); + onNotificationDirectReply(key); + break; + } } } } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 64eae0cf4635..a4db4517bd9a 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1366,6 +1366,17 @@ public abstract class NotificationListenerService extends Service { } @Override + public void onNotificationExpansionChanged( + String key, boolean isUserAction, boolean isExpanded) { + // no-op in the listener + } + + @Override + public void onNotificationDirectReply(String key) { + // no-op in the listener + } + + @Override public void onNotificationChannelModification(String pkgName, UserHandle user, NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) { diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java index 60153fcbc26b..0e4900ab2d37 100644 --- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java +++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java @@ -355,6 +355,21 @@ public class Assistant extends NotificationAssistantService { } @Override + public void onNotificationExpansionChanged(String key, boolean isUserAction, + boolean isExpanded) { + if (DEBUG) { + Log.i(TAG, + "onNotificationExpansionChanged " + key + ", isUserAction =" + isUserAction + + ", isExpanded = isExpanded"); + } + } + + @Override + public void onNotificationDirectReply(String key) { + if (DEBUG) Log.i(TAG, "onNotificationDirectReply " + key); + } + + @Override public void onListenerConnected() { if (DEBUG) Log.i(TAG, "CONNECTED"); try { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 84577e22c41f..2048d5fc9890 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -246,6 +246,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import java.util.function.Predicate; /** {@hide} */ @@ -887,6 +888,7 @@ public class NotificationManagerService extends SystemService { EventLogTags.writeNotificationExpansion(key, userAction ? 1 : 0, expanded ? 1 : 0, r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now)); + mAssistants.notifyAssistantExpansionChangedLocked(r.sbn, userAction, expanded); } } } @@ -902,6 +904,7 @@ public class NotificationManagerService extends SystemService { .setCategory(MetricsEvent.NOTIFICATION_DIRECT_REPLY_ACTION) .setType(MetricsEvent.TYPE_ACTION)); reportUserInteraction(r); + mAssistants.notifyAssistantNotificationDirectReplyLocked(r.sbn); } } } @@ -4730,7 +4733,7 @@ public class NotificationManagerService extends SystemService { mRankingHelper.extractSignals(r); // tell the assistant service about the notification if (mAssistants.isEnabled()) { - mAssistants.onNotificationEnqueued(r); + mAssistants.onNotificationEnqueuedLocked(r); mHandler.postDelayed(new PostNotificationRunnable(r.getKey()), DELAY_FOR_ASSISTANT_TIME); } else { @@ -6847,69 +6850,104 @@ public class NotificationManagerService extends SystemService { } } - public void onNotificationEnqueued(final NotificationRecord r) { + @GuardedBy("mNotificationLock") + private void onNotificationEnqueuedLocked(final NotificationRecord r) { final StatusBarNotification sbn = r.sbn; - TrimCache trimCache = new TrimCache(sbn); - - // There should be only one, but it's a list, so while we enforce - // singularity elsewhere, we keep it general here, to avoid surprises. - for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { - boolean sbnVisible = isVisibleToListener(sbn, info) - && info.isSameUser(r.getUserId()); - if (!sbnVisible) { - continue; - } + notifyAssistantLocked( + sbn, + true /* sameUserOnly */, + (assistant, sbnHolder) -> { + try { + assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel()); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex); + } + }); + } - final StatusBarNotification sbnToPost = trimCache.ForListener(info); - mHandler.post(new Runnable() { - @Override - public void run() { - notifyEnqueued(info, sbnToPost, r.getChannel()); - } - }); - } + @GuardedBy("mNotificationLock") + void notifyAssistantExpansionChangedLocked( + final StatusBarNotification sbn, + final boolean isUserAction, + final boolean isExpanded) { + final String key = sbn.getKey(); + notifyAssistantLocked( + sbn, + false /* sameUserOnly */, + (assistant, sbnHolder) -> { + try { + assistant.onNotificationExpansionChanged(key, isUserAction, isExpanded); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify assistant (expanded): " + assistant, ex); + } + }); } - private void notifyEnqueued(final ManagedServiceInfo info, - final StatusBarNotification sbn, final NotificationChannel channel) { - final INotificationListener assistant = (INotificationListener) info.service; - StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn); - try { - assistant.onNotificationEnqueuedWithChannel(sbnHolder, channel); - } catch (RemoteException ex) { - Log.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex); - } + @GuardedBy("mNotificationLock") + void notifyAssistantNotificationDirectReplyLocked( + final StatusBarNotification sbn) { + final String key = sbn.getKey(); + notifyAssistantLocked( + sbn, + false /* sameUserOnly */, + (assistant, sbnHolder) -> { + try { + assistant.onNotificationDirectReply(key); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify assistant (expanded): " + assistant, ex); + } + }); } + /** * asynchronously notify the assistant that a notification has been snoozed until a * context */ @GuardedBy("mNotificationLock") - public void notifyAssistantSnoozedLocked(final StatusBarNotification sbn, - final String snoozeCriterionId) { - TrimCache trimCache = new TrimCache(sbn); - for (final ManagedServiceInfo info : getServices()) { - boolean sbnVisible = isVisibleToListener(sbn, info); - if (!sbnVisible) { - continue; - } - final StatusBarNotification sbnToPost = trimCache.ForListener(info); - mHandler.post(new Runnable() { - @Override - public void run() { - final INotificationListener assistant = - (INotificationListener) info.service; - StatusBarNotificationHolder sbnHolder - = new StatusBarNotificationHolder(sbnToPost); + private void notifyAssistantSnoozedLocked( + final StatusBarNotification sbn, final String snoozeCriterionId) { + notifyAssistantLocked( + sbn, + false /* sameUserOnly */, + (assistant, sbnHolder) -> { try { assistant.onNotificationSnoozedUntilContext( sbnHolder, snoozeCriterionId); } catch (RemoteException ex) { Log.e(TAG, "unable to notify assistant (snoozed): " + assistant, ex); } - } - }); + }); + } + + /** + * Notifies the assistant something about the specified notification, only assistant + * that is visible to the notification will be notified. + * + * @param sbn the notification object that the update is about. + * @param sameUserOnly should the update be sent to the assistant in the same user only. + * @param callback the callback that provides the assistant to be notified, executed + * in WorkerHandler. + */ + @GuardedBy("mNotificationLock") + private void notifyAssistantLocked( + final StatusBarNotification sbn, + boolean sameUserOnly, + BiConsumer<INotificationListener, StatusBarNotificationHolder> callback) { + TrimCache trimCache = new TrimCache(sbn); + // There should be only one, but it's a list, so while we enforce + // singularity elsewhere, we keep it general here, to avoid surprises. + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + boolean sbnVisible = isVisibleToListener(sbn, info) + && (!sameUserOnly || info.isSameUser(sbn.getUserId())); + if (!sbnVisible) { + continue; + } + final INotificationListener assistant = (INotificationListener) info.service; + final StatusBarNotification sbnToPost = trimCache.ForListener(info); + final StatusBarNotificationHolder sbnHolder = + new StatusBarNotificationHolder(sbnToPost); + mHandler.post(() -> callback.accept(assistant, sbnHolder)); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 5eda14f7f387..f11492aa59e5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -38,10 +38,8 @@ import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.P; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_NEGATIVE; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_NEUTRAL; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -2508,6 +2506,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey()); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasDirectReplied()); + verify(mAssistants).notifyAssistantNotificationDirectReplyLocked(eq(r.sbn)); } @Test @@ -2516,8 +2515,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(r); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true); + verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((true))); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false); + verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((false))); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); } @@ -2528,8 +2530,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true); assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); + verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(false), eq((true))); + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false); assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); + verify(mAssistants).notifyAssistantExpansionChangedLocked( + eq(r.sbn), eq(false), eq((false))); } @Test |