Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e7b8da37 authored by Neil Gu's avatar Neil Gu Committed by Android (Google) Code Review
Browse files

Merge "Add logs for animated notification click and visible." into main

parents 561c7908 b82e4119
Loading
Loading
Loading
Loading
+68 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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)
@@ -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,
@@ -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);
@@ -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())) {
@@ -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)
@@ -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);
+29 −2
Original line number Diff line number Diff line
@@ -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) {
@@ -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];
+173 −3
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
        }
@@ -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);
        }
@@ -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;
@@ -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 {
@@ -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();
@@ -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 {
@@ -10473,7 +10548,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                mNotificationRecordLogger.event(0));
    }
    @Test
    public void testLogSmartSuggestionsVisible_triggerOnExpandAndVisible() {
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -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 =