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

Commit 6b3a9e92 authored by Alexander Roederer's avatar Alexander Roederer
Browse files

Add Latency logging for Classification adjustments

Records notification lifetime when an adjustment of KEY_TYPE is
received, as well as whether the notification was already posted when
the adjustment occurred, the type received, and whether the notification
would have been alerting when it was adjusted.

Bug: 375462619
Test: atest NotificationManagerServiceTest
Flag: android.service.notification.notification_classification
Change-Id: I5b8ba5edb72caa9b4e1cf4c7dace22a1022f5b87
parent e2434c29
Loading
Loading
Loading
Loading
+15 −2
Original line number Diff line number Diff line
@@ -82,6 +82,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BA
import static android.app.NotificationManager.zenModeFromInterruptionFilter;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
@@ -162,8 +164,6 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static com.android.server.notification.Flags.expireBitmaps;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
@@ -1929,6 +1929,12 @@ public class NotificationManagerService extends SystemService {
                hasSensitiveContent, lifespanMs);
    }
    protected void logClassificationChannelAdjustmentReceived(boolean hasPosted, boolean isAlerting,
                                                              int classification, int lifespanMs) {
        FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_CHANNEL_CLASSIFICATION,
                hasPosted, isAlerting, classification, lifespanMs);
    }
    protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -6988,8 +6994,15 @@ public class NotificationManagerService extends SystemService {
                if (newChannel == null || newChannel.getId().equals(r.getChannel().getId())) {
                    adjustments.remove(KEY_TYPE);
                } else {
                    // Save the app-provided type for logging.
                    int classification = adjustments.getInt(KEY_TYPE);
                    // swap app provided type with the real thing
                    adjustments.putParcelable(KEY_TYPE, newChannel);
                    // Note that this value of isAlerting does not fully indicate whether a notif
                    // would make a sound or HUN on device; it is an approximation for metrics.
                    boolean isAlerting = r.getChannel().getImportance() >= IMPORTANCE_DEFAULT;
                    logClassificationChannelAdjustmentReceived(isPosted, isAlerting, classification,
                            r.getLifespanMs(System.currentTimeMillis()));
                }
            }
            r.addAdjustment(adjustment);
+57 −2
Original line number Diff line number Diff line
@@ -78,7 +78,6 @@ import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -366,7 +365,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -7441,6 +7439,63 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertTrue(mService.checkLastSensitiveLog(true, false, 2));
    }
    @Test
    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
    public void testClassificationChannelAdjustmentsLogged() throws Exception {
        NotificationManagerService.WorkerHandler handler = mock(
                NotificationManagerService.WorkerHandler.class);
        mService.setHandler(handler);
        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
        // Set up notifications that will be adjusted
        final NotificationRecord r1 = spy(generateNotificationRecord(
                mTestNotificationChannel, 1, null, true));
        when(r1.getLifespanMs(anyLong())).thenReturn(234);
        r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
        // Enqueues the notification to be posted, so hasPosted will be false.
        mService.addEnqueuedNotification(r1);
        // Test an adjustment for an enqueued notification
        Bundle signals = new Bundle();
        signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
        Adjustment adjustment1 = new Adjustment(
                r1.getSbn().getPackageName(), r1.getKey(), signals, "",
                r1.getUser().getIdentifier());
        mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment1);
        assertTrue(mService.checkLastClassificationChannelLog(false /*hasPosted*/,
                true /*isAlerting*/, 3 /*TYPE_NEWS*/, 234));
        // Set up notifications that will be adjusted
        // This notification starts on a low importance channel, so isAlerting is false.
        NotificationChannel mLowImportanceNotificationChannel = new NotificationChannel(
                TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_LOW);
        final NotificationRecord r2 = spy(generateNotificationRecord(
                mLowImportanceNotificationChannel, 1, null, true));
        when(r2.getLifespanMs(anyLong())).thenReturn(345);
        r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
        // Adds the notification as already posted, so hasPosted will be true.
        mService.addNotification(r2);
        // The signal is removed when used so it has to be readded.
        signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
        Adjustment adjustment2 = new Adjustment(
                r2.getSbn().getPackageName(), r2.getKey(), signals, "",
                r2.getUser().getIdentifier());
        mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment2);
        assertTrue(mService.checkLastClassificationChannelLog(true /*hasPosted*/,
                false /*isAlerting*/, 3 /*TYPE_NEWS*/, 345)); // currently failing
        signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_PROMOTION);
        Adjustment adjustment3 = new Adjustment(
                r2.getSbn().getPackageName(), r2.getKey(), signals, "",
                r2.getUser().getIdentifier());
        mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment3);
        assertTrue(mService.checkLastClassificationChannelLog(true /*hasPosted*/,
                false /*isAlerting*/, 1 /*TYPE_PROMOTION*/, 345));
    }
    @Test
    public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception {
        NotificationManagerService.WorkerHandler handler = mock(
+33 −0
Original line number Diff line number Diff line
@@ -52,6 +52,14 @@ public class TestableNotificationManagerService extends NotificationManagerServi
    }
    public SensitiveLog lastSensitiveLog = null;

    private static class ClassificationChannelLog {
        public boolean hasPosted;
        public boolean isAlerting;
        public long classification;
        public long lifetime;
    }
    public ClassificationChannelLog  lastClassificationChannelLog = null;

    TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
            InstanceIdSequence notificationInstanceIdSequence) {
        super(context, logger, notificationInstanceIdSequence);
@@ -211,4 +219,29 @@ public class TestableNotificationManagerService extends NotificationManagerServi
    public interface ComponentPermissionChecker {
        int check(String permission, int uid, int owningUid, boolean exported);
    }

    @Override
    protected void logClassificationChannelAdjustmentReceived(boolean hasPosted, boolean isAlerting,
                                                              int classification, int lifetimeMs) {
        lastClassificationChannelLog = new ClassificationChannelLog();
        lastClassificationChannelLog.hasPosted = hasPosted;
        lastClassificationChannelLog.isAlerting = isAlerting;
        lastClassificationChannelLog.classification = classification;
        lastClassificationChannelLog.lifetime = lifetimeMs;
    }

    /**
     * Returns true if the last recorded classification channel log has all the values specified.
     */
    public boolean checkLastClassificationChannelLog(boolean hasPosted, boolean isAlerting,
                                                     int classification, int lifetime) {
        if (lastClassificationChannelLog == null) {
            return false;
        }

        return hasPosted == lastClassificationChannelLog.hasPosted
                && isAlerting == lastClassificationChannelLog.isAlerting
                && classification == lastClassificationChannelLog.classification
                && lifetime == lastClassificationChannelLog.lifetime;
    }
}