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

Commit b82e4119 authored by Neil Gu's avatar Neil Gu
Browse files

Add logs for animated notification click and visible.

Bug: 416647493
Flag: EXEMPT log only update
Change-Id: I0664c1af5cb30772e43074f70bef7f9f4b122cac
parent f3624726
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);
        }
@@ -10205,7 +10216,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;
@@ -10229,6 +10241,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 {
@@ -10282,7 +10331,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();
@@ -10305,6 +10354,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 {
@@ -10451,7 +10526,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                mNotificationRecordLogger.event(0));
    }
    @Test
    public void testLogSmartSuggestionsVisible_triggerOnExpandAndVisible() {
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -10493,6 +10567,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 =