Loading services/core/java/com/android/server/notification/NotificationManagerService.java +68 −1 Original line number Diff line number Diff line Loading @@ -224,6 +224,7 @@ import android.app.ITransientNotification; import android.app.ITransientNotificationCallback; import android.app.IUriGrantsManager; import android.app.Notification; import android.app.Notification.Action; import android.app.Notification.MessagingStyle; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; Loading Loading @@ -351,6 +352,8 @@ import android.view.Display; import android.view.accessibility.AccessibilityManager; import android.widget.RemoteViews; import android.widget.Toast; import android.text.Spanned; import android.text.Annotation; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -1434,6 +1437,11 @@ public class NotificationManagerService extends SystemService { return; } final long now = System.currentTimeMillis(); boolean isAnimatedAction = false; if (action != null && action.getExtras() != null) { isAnimatedAction = action.getExtras() .getBoolean(Notification.Action.EXTRA_IS_ANIMATED, false); } MetricsLogger.action(r.getLogMaker(now) .setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION) .setType(MetricsEvent.TYPE_ACTION) Loading @@ -1449,7 +1457,8 @@ public class NotificationManagerService extends SystemService { nv.location.toMetricsEventEnum())); mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent.fromAction(actionIndex, generatedByAssistant, action.isContextual()), r); generatedByAssistant, action.isContextual(), isAnimatedAction), r); EventLogTags.writeNotificationActionClicked(key, action.actionIntent.getTarget().toString(), action.actionIntent.getIntent().toString(), actionIndex, Loading Loading @@ -1747,6 +1756,13 @@ public class NotificationManagerService extends SystemService { MetricsEvent.NOTIFICATION_SMART_REPLY_MODIFIED_BEFORE_SENDING, modifiedBeforeSending ? 1 : 0); mMetricsLogger.write(logMaker); if (r.getSmartReplies() != null && r.getSmartReplies().size() > replyIndex && isAnimatedReply(r.getSmartReplies().get(replyIndex))) { mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_REPLIED, r); } mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED, r); Loading Loading @@ -1967,6 +1983,22 @@ public class NotificationManagerService extends SystemService { notificationUpdate); } private static boolean isAnimatedReply(CharSequence reply) { if (reply instanceof Spanned) { Spanned spanned = (Spanned) reply; Annotation[] annotations = spanned.getSpans(0, reply.length(), Annotation.class); if (annotations != null) { // Add null check for (Annotation annotation : annotations) { if ("isAnimatedReply".equals(annotation.getKey()) && "1".equals(annotation.getValue())) { return true; } } } } return false; } @VisibleForTesting void unclassifyNotification(final String key) { if (!(notificationClassificationUi() && notificationRegroupOnClassification())) { Loading Loading @@ -2134,6 +2166,8 @@ public class NotificationManagerService extends SystemService { // then log that the user has seen them. if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0) && !r.hasSeenSmartReplies()) { boolean isAnimatedReply = false; boolean isAnimatedAction = false; r.setSeenSmartReplies(true); LogMaker logMaker = r.getLogMaker() .setCategory(MetricsEvent.SMART_REPLY_VISIBLE) Loading @@ -2151,6 +2185,39 @@ public class NotificationManagerService extends SystemService { MetricsEvent.NOTIFICATION_SMART_REPLY_EDIT_BEFORE_SENDING, r.getEditChoicesBeforeSending() ? 1 : 0); mMetricsLogger.write(logMaker); // TODO (b/421296838): notification metrics log not accurate. if (r.getSmartReplies() != null) { for (CharSequence reply : r.getSmartReplies()) { if (isAnimatedReply(reply)) { isAnimatedReply = true; break; } } } if (r.getNotification() != null && r.getNotification().actions != null) { for (int actionIndex = 0; actionIndex < r.getNotification().actions.length; actionIndex++) { Action action = r.getNotification().actions[actionIndex]; if (action != null && action.getExtras() != null && action.getExtras() .getBoolean(Notification.Action.EXTRA_IS_ANIMATED, false)) { isAnimatedAction = true; break; } } } if (isAnimatedReply) { mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_REPLY_VISIBLE, r); } if (isAnimatedAction) { mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_ACTION_VISIBLE, r); } mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLY_VISIBLE, r); Loading services/core/java/com/android/server/notification/NotificationRecordLogger.java +29 −2 Original line number Diff line number Diff line Loading @@ -334,7 +334,19 @@ interface NotificationRecordLogger { @UiEvent(doc = "Notification was force autogrouped.") NOTIFICATION_FORCE_GROUP(1843), @UiEvent(doc = "Notification summary was force autogrouped.") NOTIFICATION_FORCE_GROUP_SUMMARY(1844); NOTIFICATION_FORCE_GROUP_SUMMARY(1844), @UiEvent(doc = "Notification animated reply was used.") NOTIFICATION_ANIMATED_REPLIED(2331), @UiEvent(doc = "Notification animated reply was visible.") NOTIFICATION_ANIMATED_REPLY_VISIBLE(2332), @UiEvent(doc = "App-generated animated notification action at position 0 was clicked.") NOTIFICATION_ANIMATED_ACTION_CLICKED_0(2333), @UiEvent(doc = "App-generated animated notification action at position 1 was clicked.") NOTIFICATION_ANIMATED_ACTION_CLICKED_1(2334), @UiEvent(doc = "App-generated animated notification action at position 2 was clicked.") NOTIFICATION_ANIMATED_ACTION_CLICKED_2(2335), @UiEvent(doc = "Notification animated action was visible.") NOTIFICATION_ANIMATED_ACTION_VISIBLE(2336); private final int mId; NotificationEvent(int id) { Loading @@ -354,10 +366,25 @@ interface NotificationRecordLogger { return expanded ? NOTIFICATION_DETAIL_OPEN_SYSTEM : NOTIFICATION_DETAIL_CLOSE_SYSTEM; } public static NotificationEvent fromAction(int index, boolean isAssistant, boolean isContextual) { boolean isContextual, boolean isAnimatedAction) { if (index < 0 || index > 2) { return NOTIFICATION_ACTION_CLICKED; } if (isAnimatedAction) { NotificationEvent event = NOTIFICATION_ANIMATED_ACTION_CLICKED_0; switch (index) { case 0 -> { event = NOTIFICATION_ANIMATED_ACTION_CLICKED_0; } case 1 -> { event = NOTIFICATION_ANIMATED_ACTION_CLICKED_1; } case 2 -> { event = NOTIFICATION_ANIMATED_ACTION_CLICKED_2; } } return event; } if (isAssistant) { // Assistant actions are contextual by definition return NotificationEvent.values()[ NOTIFICATION_ASSIST_ACTION_CLICKED_0.ordinal() + index]; Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +173 −3 Original line number Diff line number Diff line Loading @@ -218,6 +218,7 @@ import android.app.INotificationManager; import android.app.ITransientNotification; import android.app.IUriGrantsManager; import android.app.Notification; import android.app.Notification.Action; import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; Loading Loading @@ -309,7 +310,10 @@ import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestablePermissions; import android.testing.TestableResources; import android.text.Annotation; import android.text.Html; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; Loading Loading @@ -1172,6 +1176,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private NotificationRecord generateNotificationRecord(NotificationChannel channel, Notification.TvExtender extender) { return generateNotificationRecord(channel, extender, null); } private NotificationRecord generateNotificationRecord(NotificationChannel channel, Notification.TvExtender extender, Action action) { if (channel == null) { channel = mTestNotificationChannel; } Loading @@ -1181,6 +1189,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .addAction(new Notification.Action.Builder(null, "test", mActivityIntent).build()) .addAction(new Notification.Action.Builder( null, "test", mActivityIntentImmutable).build()); if (action != null) { nb.addAction(action); } if (extender != null) { nb.extend(extender); } Loading Loading @@ -10227,7 +10238,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testOnNotificationSmartReplySent() { public void testOnNotificationSmartReplySent_isSmartReply_hasSmartReplyAndSendsSmartReplyLogs() { final int replyIndex = 2; final String reply = "Hello"; final boolean modifiedBeforeSending = true; Loading @@ -10251,6 +10263,43 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(r.getStats().hasSmartReplied()).isTrue(); } @Test @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testOnNotificationSmartReplySent_isAnimatedReply_sendsAnimatedReplyLogs() { final int replyIndex = 0; final String reply = "Hello"; final boolean modifiedBeforeSending = true; final boolean generatedByAssistant = true; final SpannableStringBuilder animatedReplyString = new SpannableStringBuilder(reply); final Annotation animatedReplyAnnotation = new Annotation("isAnimatedReply", "1"); final ArrayList<CharSequence> smartReplies = new ArrayList<>(); animatedReplyString.setSpan( animatedReplyAnnotation, 0, reply.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); smartReplies.add(animatedReplyString); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.setSuggestionsGeneratedByAssistant(generatedByAssistant); r.setSmartReplies(smartReplies); mService.addNotification(r); mService.mNotificationDelegate.onNotificationSmartReplySent( r.getKey(), replyIndex, reply, NOTIFICATION_LOCATION_UNKNOWN, modifiedBeforeSending); verify(mAssistants).notifyAssistantSuggestedReplySent( eq(r.getSbn()), eq(FLAG_FILTER_TYPE_ALERTING), eq(reply), eq(generatedByAssistant)); assertEquals(2, mNotificationRecordLogger.numCalls()); assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_ANIMATED_REPLIED, mNotificationRecordLogger.event(0)); assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED, mNotificationRecordLogger.event(1)); assertThat(r.getSbn().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) .isGreaterThan(0); assertThat(r.getStats().hasSmartReplied()).isTrue(); } @Test @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testStats_SmartReplyAlreadyLifetimeExtendedPostsUpdate() throws Exception { Loading Loading @@ -10304,7 +10353,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test public void testOnNotificationActionClick() { public void testOnNotificationActionClick_isNotificationAction_sendsNotificationActionLogs() { final int actionIndex = 2; final Notification.Action action = new Notification.Action.Builder(null, "text", mActivityIntent).build(); Loading @@ -10327,6 +10376,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mNotificationRecordLogger.event(0)); } @Test public void testOnNotificationActionClick_isAnimatedAction_sendsAnimatedActionLogs() { final int actionIndex = 2; final Bundle bundle = new Bundle(); bundle.putBoolean(Notification.Action.EXTRA_IS_ANIMATED, true); final Notification.Action action = new Notification.Action.Builder(null, "text", mActivityIntent) .addExtras(bundle).build(); final boolean generatedByAssistant = false; NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); NotificationVisibility notificationVisibility = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.addNotification(r); mService.mNotificationDelegate.onNotificationActionClick( 10, 10, r.getKey(), actionIndex, action, notificationVisibility, generatedByAssistant); verify(mAssistants).notifyAssistantActionClicked( eq(r), eq(action), eq(generatedByAssistant)); assertEquals(1, mNotificationRecordLogger.numCalls()); assertEquals( NotificationRecordLogger.NotificationEvent.NOTIFICATION_ANIMATED_ACTION_CLICKED_2, mNotificationRecordLogger.event(0)); } @Test @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testActionClickLifetimeExtendedCancel() throws Exception { Loading Loading @@ -10473,7 +10548,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mNotificationRecordLogger.event(0)); } @Test public void testLogSmartSuggestionsVisible_triggerOnExpandAndVisible() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); Loading Loading @@ -10515,6 +10589,102 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(0, mService.countLogSmartSuggestionsVisible); } @Test public void testLogSmartSuggestionsVisible_hasAnimatedReply_triggersAnimatedReplyLogs() { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); final SpannableStringBuilder animatedReplyString = new SpannableStringBuilder("smart reply"); final Annotation animatedReplyAnnotation = new Annotation("isAnimatedReply", "1"); final ArrayList<CharSequence> smartReplies = new ArrayList<>(); animatedReplyString.setSpan( animatedReplyAnnotation, 0, animatedReplyString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); smartReplies.add(animatedReplyString); r.setNumSmartRepliesAdded(1); r.setSmartReplies(smartReplies); mService.addNotification(r); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, NOTIFICATION_LOCATION_UNKNOWN); NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { NotificationVisibility.obtain(r.getKey(), 0, 0, true) }; mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, new NotificationVisibility[0]); assertEquals(3, mNotificationRecordLogger.numCalls()); assertEquals(NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_REPLY_VISIBLE, mNotificationRecordLogger.event(1)); } @Test public void testLogSmartSuggestionsVisible_withoutSmartReplies_notTriggerAnimatedReplyLogs() { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.setNumSmartRepliesAdded(1); r.setSmartReplies(new ArrayList<>()); mService.addNotification(r); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, NOTIFICATION_LOCATION_UNKNOWN); NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { NotificationVisibility.obtain(r.getKey(), 0, 0, true) }; mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, new NotificationVisibility[0]); assertEquals(2, mNotificationRecordLogger.numCalls()); assertNotEquals(NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_REPLY_VISIBLE, mNotificationRecordLogger.event(1)); } @Test public void testLogSmartSuggestionsVisible_hasAnimatedAction_triggersAnimatedActionLogs() { final Bundle bundle = new Bundle(); bundle.putBoolean(Notification.Action.EXTRA_IS_ANIMATED, true); final Notification.Action action = new Notification.Action.Builder(null, "text", mActivityIntent) .addExtras(bundle).build(); final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, null, action); r.setNumSmartActionsAdded(1); mService.addNotification(r); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, NOTIFICATION_LOCATION_UNKNOWN); NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { NotificationVisibility.obtain(r.getKey(), 0, 0, true) }; mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, new NotificationVisibility[0]); assertEquals(3, mNotificationRecordLogger.numCalls()); assertEquals(NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_ACTION_VISIBLE, mNotificationRecordLogger.event(1)); } @Test public void testLogSmartSuggestionsVisible_withoutActionExtra_notTriggerAnimatedActionLogs() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.setNumSmartActionsAdded(1); mService.addNotification(r); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, NOTIFICATION_LOCATION_UNKNOWN); NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { NotificationVisibility.obtain(r.getKey(), 0, 0, true) }; mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, new NotificationVisibility[0]); assertEquals(2, mNotificationRecordLogger.numCalls()); assertNotEquals(NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_ACTION_VISIBLE, mNotificationRecordLogger.event(1)); } @Test public void testReportSeen_delegated() { Notification.Builder nb = Loading Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +68 −1 Original line number Diff line number Diff line Loading @@ -224,6 +224,7 @@ import android.app.ITransientNotification; import android.app.ITransientNotificationCallback; import android.app.IUriGrantsManager; import android.app.Notification; import android.app.Notification.Action; import android.app.Notification.MessagingStyle; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; Loading Loading @@ -351,6 +352,8 @@ import android.view.Display; import android.view.accessibility.AccessibilityManager; import android.widget.RemoteViews; import android.widget.Toast; import android.text.Spanned; import android.text.Annotation; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -1434,6 +1437,11 @@ public class NotificationManagerService extends SystemService { return; } final long now = System.currentTimeMillis(); boolean isAnimatedAction = false; if (action != null && action.getExtras() != null) { isAnimatedAction = action.getExtras() .getBoolean(Notification.Action.EXTRA_IS_ANIMATED, false); } MetricsLogger.action(r.getLogMaker(now) .setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION) .setType(MetricsEvent.TYPE_ACTION) Loading @@ -1449,7 +1457,8 @@ public class NotificationManagerService extends SystemService { nv.location.toMetricsEventEnum())); mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent.fromAction(actionIndex, generatedByAssistant, action.isContextual()), r); generatedByAssistant, action.isContextual(), isAnimatedAction), r); EventLogTags.writeNotificationActionClicked(key, action.actionIntent.getTarget().toString(), action.actionIntent.getIntent().toString(), actionIndex, Loading Loading @@ -1747,6 +1756,13 @@ public class NotificationManagerService extends SystemService { MetricsEvent.NOTIFICATION_SMART_REPLY_MODIFIED_BEFORE_SENDING, modifiedBeforeSending ? 1 : 0); mMetricsLogger.write(logMaker); if (r.getSmartReplies() != null && r.getSmartReplies().size() > replyIndex && isAnimatedReply(r.getSmartReplies().get(replyIndex))) { mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_REPLIED, r); } mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED, r); Loading Loading @@ -1967,6 +1983,22 @@ public class NotificationManagerService extends SystemService { notificationUpdate); } private static boolean isAnimatedReply(CharSequence reply) { if (reply instanceof Spanned) { Spanned spanned = (Spanned) reply; Annotation[] annotations = spanned.getSpans(0, reply.length(), Annotation.class); if (annotations != null) { // Add null check for (Annotation annotation : annotations) { if ("isAnimatedReply".equals(annotation.getKey()) && "1".equals(annotation.getValue())) { return true; } } } } return false; } @VisibleForTesting void unclassifyNotification(final String key) { if (!(notificationClassificationUi() && notificationRegroupOnClassification())) { Loading Loading @@ -2134,6 +2166,8 @@ public class NotificationManagerService extends SystemService { // then log that the user has seen them. if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0) && !r.hasSeenSmartReplies()) { boolean isAnimatedReply = false; boolean isAnimatedAction = false; r.setSeenSmartReplies(true); LogMaker logMaker = r.getLogMaker() .setCategory(MetricsEvent.SMART_REPLY_VISIBLE) Loading @@ -2151,6 +2185,39 @@ public class NotificationManagerService extends SystemService { MetricsEvent.NOTIFICATION_SMART_REPLY_EDIT_BEFORE_SENDING, r.getEditChoicesBeforeSending() ? 1 : 0); mMetricsLogger.write(logMaker); // TODO (b/421296838): notification metrics log not accurate. if (r.getSmartReplies() != null) { for (CharSequence reply : r.getSmartReplies()) { if (isAnimatedReply(reply)) { isAnimatedReply = true; break; } } } if (r.getNotification() != null && r.getNotification().actions != null) { for (int actionIndex = 0; actionIndex < r.getNotification().actions.length; actionIndex++) { Action action = r.getNotification().actions[actionIndex]; if (action != null && action.getExtras() != null && action.getExtras() .getBoolean(Notification.Action.EXTRA_IS_ANIMATED, false)) { isAnimatedAction = true; break; } } } if (isAnimatedReply) { mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_REPLY_VISIBLE, r); } if (isAnimatedAction) { mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_ACTION_VISIBLE, r); } mNotificationRecordLogger.log( NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLY_VISIBLE, r); Loading
services/core/java/com/android/server/notification/NotificationRecordLogger.java +29 −2 Original line number Diff line number Diff line Loading @@ -334,7 +334,19 @@ interface NotificationRecordLogger { @UiEvent(doc = "Notification was force autogrouped.") NOTIFICATION_FORCE_GROUP(1843), @UiEvent(doc = "Notification summary was force autogrouped.") NOTIFICATION_FORCE_GROUP_SUMMARY(1844); NOTIFICATION_FORCE_GROUP_SUMMARY(1844), @UiEvent(doc = "Notification animated reply was used.") NOTIFICATION_ANIMATED_REPLIED(2331), @UiEvent(doc = "Notification animated reply was visible.") NOTIFICATION_ANIMATED_REPLY_VISIBLE(2332), @UiEvent(doc = "App-generated animated notification action at position 0 was clicked.") NOTIFICATION_ANIMATED_ACTION_CLICKED_0(2333), @UiEvent(doc = "App-generated animated notification action at position 1 was clicked.") NOTIFICATION_ANIMATED_ACTION_CLICKED_1(2334), @UiEvent(doc = "App-generated animated notification action at position 2 was clicked.") NOTIFICATION_ANIMATED_ACTION_CLICKED_2(2335), @UiEvent(doc = "Notification animated action was visible.") NOTIFICATION_ANIMATED_ACTION_VISIBLE(2336); private final int mId; NotificationEvent(int id) { Loading @@ -354,10 +366,25 @@ interface NotificationRecordLogger { return expanded ? NOTIFICATION_DETAIL_OPEN_SYSTEM : NOTIFICATION_DETAIL_CLOSE_SYSTEM; } public static NotificationEvent fromAction(int index, boolean isAssistant, boolean isContextual) { boolean isContextual, boolean isAnimatedAction) { if (index < 0 || index > 2) { return NOTIFICATION_ACTION_CLICKED; } if (isAnimatedAction) { NotificationEvent event = NOTIFICATION_ANIMATED_ACTION_CLICKED_0; switch (index) { case 0 -> { event = NOTIFICATION_ANIMATED_ACTION_CLICKED_0; } case 1 -> { event = NOTIFICATION_ANIMATED_ACTION_CLICKED_1; } case 2 -> { event = NOTIFICATION_ANIMATED_ACTION_CLICKED_2; } } return event; } if (isAssistant) { // Assistant actions are contextual by definition return NotificationEvent.values()[ NOTIFICATION_ASSIST_ACTION_CLICKED_0.ordinal() + index]; Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +173 −3 Original line number Diff line number Diff line Loading @@ -218,6 +218,7 @@ import android.app.INotificationManager; import android.app.ITransientNotification; import android.app.IUriGrantsManager; import android.app.Notification; import android.app.Notification.Action; import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; Loading Loading @@ -309,7 +310,10 @@ import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestablePermissions; import android.testing.TestableResources; import android.text.Annotation; import android.text.Html; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; Loading Loading @@ -1172,6 +1176,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private NotificationRecord generateNotificationRecord(NotificationChannel channel, Notification.TvExtender extender) { return generateNotificationRecord(channel, extender, null); } private NotificationRecord generateNotificationRecord(NotificationChannel channel, Notification.TvExtender extender, Action action) { if (channel == null) { channel = mTestNotificationChannel; } Loading @@ -1181,6 +1189,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .addAction(new Notification.Action.Builder(null, "test", mActivityIntent).build()) .addAction(new Notification.Action.Builder( null, "test", mActivityIntentImmutable).build()); if (action != null) { nb.addAction(action); } if (extender != null) { nb.extend(extender); } Loading Loading @@ -10227,7 +10238,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testOnNotificationSmartReplySent() { public void testOnNotificationSmartReplySent_isSmartReply_hasSmartReplyAndSendsSmartReplyLogs() { final int replyIndex = 2; final String reply = "Hello"; final boolean modifiedBeforeSending = true; Loading @@ -10251,6 +10263,43 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(r.getStats().hasSmartReplied()).isTrue(); } @Test @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testOnNotificationSmartReplySent_isAnimatedReply_sendsAnimatedReplyLogs() { final int replyIndex = 0; final String reply = "Hello"; final boolean modifiedBeforeSending = true; final boolean generatedByAssistant = true; final SpannableStringBuilder animatedReplyString = new SpannableStringBuilder(reply); final Annotation animatedReplyAnnotation = new Annotation("isAnimatedReply", "1"); final ArrayList<CharSequence> smartReplies = new ArrayList<>(); animatedReplyString.setSpan( animatedReplyAnnotation, 0, reply.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); smartReplies.add(animatedReplyString); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.setSuggestionsGeneratedByAssistant(generatedByAssistant); r.setSmartReplies(smartReplies); mService.addNotification(r); mService.mNotificationDelegate.onNotificationSmartReplySent( r.getKey(), replyIndex, reply, NOTIFICATION_LOCATION_UNKNOWN, modifiedBeforeSending); verify(mAssistants).notifyAssistantSuggestedReplySent( eq(r.getSbn()), eq(FLAG_FILTER_TYPE_ALERTING), eq(reply), eq(generatedByAssistant)); assertEquals(2, mNotificationRecordLogger.numCalls()); assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_ANIMATED_REPLIED, mNotificationRecordLogger.event(0)); assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED, mNotificationRecordLogger.event(1)); assertThat(r.getSbn().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) .isGreaterThan(0); assertThat(r.getStats().hasSmartReplied()).isTrue(); } @Test @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testStats_SmartReplyAlreadyLifetimeExtendedPostsUpdate() throws Exception { Loading Loading @@ -10304,7 +10353,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test public void testOnNotificationActionClick() { public void testOnNotificationActionClick_isNotificationAction_sendsNotificationActionLogs() { final int actionIndex = 2; final Notification.Action action = new Notification.Action.Builder(null, "text", mActivityIntent).build(); Loading @@ -10327,6 +10376,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mNotificationRecordLogger.event(0)); } @Test public void testOnNotificationActionClick_isAnimatedAction_sendsAnimatedActionLogs() { final int actionIndex = 2; final Bundle bundle = new Bundle(); bundle.putBoolean(Notification.Action.EXTRA_IS_ANIMATED, true); final Notification.Action action = new Notification.Action.Builder(null, "text", mActivityIntent) .addExtras(bundle).build(); final boolean generatedByAssistant = false; NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); NotificationVisibility notificationVisibility = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.addNotification(r); mService.mNotificationDelegate.onNotificationActionClick( 10, 10, r.getKey(), actionIndex, action, notificationVisibility, generatedByAssistant); verify(mAssistants).notifyAssistantActionClicked( eq(r), eq(action), eq(generatedByAssistant)); assertEquals(1, mNotificationRecordLogger.numCalls()); assertEquals( NotificationRecordLogger.NotificationEvent.NOTIFICATION_ANIMATED_ACTION_CLICKED_2, mNotificationRecordLogger.event(0)); } @Test @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void testActionClickLifetimeExtendedCancel() throws Exception { Loading Loading @@ -10473,7 +10548,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mNotificationRecordLogger.event(0)); } @Test public void testLogSmartSuggestionsVisible_triggerOnExpandAndVisible() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); Loading Loading @@ -10515,6 +10589,102 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(0, mService.countLogSmartSuggestionsVisible); } @Test public void testLogSmartSuggestionsVisible_hasAnimatedReply_triggersAnimatedReplyLogs() { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); final SpannableStringBuilder animatedReplyString = new SpannableStringBuilder("smart reply"); final Annotation animatedReplyAnnotation = new Annotation("isAnimatedReply", "1"); final ArrayList<CharSequence> smartReplies = new ArrayList<>(); animatedReplyString.setSpan( animatedReplyAnnotation, 0, animatedReplyString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); smartReplies.add(animatedReplyString); r.setNumSmartRepliesAdded(1); r.setSmartReplies(smartReplies); mService.addNotification(r); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, NOTIFICATION_LOCATION_UNKNOWN); NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { NotificationVisibility.obtain(r.getKey(), 0, 0, true) }; mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, new NotificationVisibility[0]); assertEquals(3, mNotificationRecordLogger.numCalls()); assertEquals(NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_REPLY_VISIBLE, mNotificationRecordLogger.event(1)); } @Test public void testLogSmartSuggestionsVisible_withoutSmartReplies_notTriggerAnimatedReplyLogs() { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.setNumSmartRepliesAdded(1); r.setSmartReplies(new ArrayList<>()); mService.addNotification(r); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, NOTIFICATION_LOCATION_UNKNOWN); NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { NotificationVisibility.obtain(r.getKey(), 0, 0, true) }; mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, new NotificationVisibility[0]); assertEquals(2, mNotificationRecordLogger.numCalls()); assertNotEquals(NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_REPLY_VISIBLE, mNotificationRecordLogger.event(1)); } @Test public void testLogSmartSuggestionsVisible_hasAnimatedAction_triggersAnimatedActionLogs() { final Bundle bundle = new Bundle(); bundle.putBoolean(Notification.Action.EXTRA_IS_ANIMATED, true); final Notification.Action action = new Notification.Action.Builder(null, "text", mActivityIntent) .addExtras(bundle).build(); final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, null, action); r.setNumSmartActionsAdded(1); mService.addNotification(r); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, NOTIFICATION_LOCATION_UNKNOWN); NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { NotificationVisibility.obtain(r.getKey(), 0, 0, true) }; mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, new NotificationVisibility[0]); assertEquals(3, mNotificationRecordLogger.numCalls()); assertEquals(NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_ACTION_VISIBLE, mNotificationRecordLogger.event(1)); } @Test public void testLogSmartSuggestionsVisible_withoutActionExtra_notTriggerAnimatedActionLogs() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.setNumSmartActionsAdded(1); mService.addNotification(r); mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, NOTIFICATION_LOCATION_UNKNOWN); NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { NotificationVisibility.obtain(r.getKey(), 0, 0, true) }; mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, new NotificationVisibility[0]); assertEquals(2, mNotificationRecordLogger.numCalls()); assertNotEquals(NotificationRecordLogger.NotificationEvent .NOTIFICATION_ANIMATED_ACTION_VISIBLE, mNotificationRecordLogger.event(1)); } @Test public void testReportSeen_delegated() { Notification.Builder nb = Loading