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

Commit d13c8ca6 authored by Matías Hernández's avatar Matías Hernández Committed by Android (Google) Code Review
Browse files

Merge "Notification throttling: allow progress -> no progress update" into main

parents abfdc572 0cb2c040
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -4358,6 +4358,7 @@ public class Notification implements Parcelable
    /**
     * @hide
     */
    // TODO: b/425364383 - Remove when inlining
    public boolean hasCompletedProgress() {
        // not a progress notification; can't be complete
        if (!extras.containsKey(EXTRA_PROGRESS)
@@ -4371,6 +4372,30 @@ public class Notification implements Parcelable
        return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
    }
    /** @hide */
    public static final int PROGRESS_STATE_NONE = 0;
    /** @hide */
    public static final int PROGRESS_STATE_ONGOING = 1;
    /** @hide */
    public static final int PROGRESS_STATE_COMPLETE = 2;
    /** @hide */
    @FlaggedApi(Flags.FLAG_NOTIFICATION_UPDATE_SHEDDING_ALLOW_PROGRESS_COMPLETION)
    public int getProgressState() {
        final int progress = extras.getInt(EXTRA_PROGRESS, 0);
        final int max = extras.getInt(EXTRA_PROGRESS_MAX, 0);
        final boolean ind = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
        if (max != 0 || ind) {
            if (progress == max && !ind) {
                return PROGRESS_STATE_COMPLETE;
            } else {
                return PROGRESS_STATE_ONGOING;
            }
        } else {
            return PROGRESS_STATE_NONE;
        }
    }
    /** @removed */
    @Deprecated
    public String getChannel() {
+33 −14
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.Set;
import java.util.concurrent.Executor;

@@ -688,8 +689,10 @@ public class NotificationManager {
    private final RateLimiter mUnnecessaryCancelRateLimiter = new RateLimiter("cancel (dupe)",
            "notifications.value_client_throttled_cancel_duplicate",
            MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE);
    // Value is KNOWN_STATUS_ENQUEUED/_CANCELLED
    private final LruCache<NotificationKey, Integer> mKnownNotifications = new LruCache<>(100);
    // KnownStatus is KNOWN_STATUS_ENQUEUED/_CANCELLED
    private record KnownNotification(int knownStatus, OptionalInt progressState) {}
    private final LruCache<NotificationKey, KnownNotification> mKnownNotifications =
            new LruCache<>(100);
    private final Object mThrottleLock = new Object();

    @UnsupportedAppUsage
@@ -848,8 +851,20 @@ public class NotificationManager {
        if (Flags.nmBinderPerfThrottleNotify()) {
            NotificationKey key = new NotificationKey(user, pkg, tag, id);
            synchronized (mThrottleLock) {
                Integer status = mKnownNotifications.get(key);
                if (status != null && status == KNOWN_STATUS_ENQUEUED
                KnownNotification status = mKnownNotifications.get(key);
                if (Flags.notificationUpdateSheddingAllowProgressCompletion()) {
                    if (status != null && status.knownStatus == KNOWN_STATUS_ENQUEUED
                            && status.progressState.orElse(-1) == notification.getProgressState()) {
                        if (mUpdateRateLimiter.eventExceedsRate()) {
                            mUpdateRateLimiter.recordRejected(key);
                            return true;
                        }
                        mUpdateRateLimiter.recordAccepted();
                    }
                    mKnownNotifications.put(key, new KnownNotification(KNOWN_STATUS_ENQUEUED,
                                OptionalInt.of(notification.getProgressState())));
                } else {
                    if (status != null && status.knownStatus == KNOWN_STATUS_ENQUEUED
                            && !notification.hasCompletedProgress()) {
                        if (mUpdateRateLimiter.eventExceedsRate()) {
                            mUpdateRateLimiter.recordRejected(key);
@@ -857,7 +872,9 @@ public class NotificationManager {
                        }
                        mUpdateRateLimiter.recordAccepted();
                    }
                mKnownNotifications.put(key, KNOWN_STATUS_ENQUEUED);
                    mKnownNotifications.put(key, new KnownNotification(KNOWN_STATUS_ENQUEUED,
                            OptionalInt.empty()));
                }
            }
        }

@@ -1047,15 +1064,16 @@ public class NotificationManager {
        if (Flags.nmBinderPerfThrottleNotify()) {
            NotificationKey key = new NotificationKey(user, pkg, tag, id);
            synchronized (mThrottleLock) {
                Integer status = mKnownNotifications.get(key);
                if (status != null && status == KNOWN_STATUS_CANCELLED) {
                KnownNotification status = mKnownNotifications.get(key);
                if (status != null && status.knownStatus == KNOWN_STATUS_CANCELLED) {
                    if (mUnnecessaryCancelRateLimiter.eventExceedsRate()) {
                        mUnnecessaryCancelRateLimiter.recordRejected(key);
                        return true;
                    }
                    mUnnecessaryCancelRateLimiter.recordAccepted();
                }
                mKnownNotifications.put(key, KNOWN_STATUS_CANCELLED);
                mKnownNotifications.put(key, new KnownNotification(KNOWN_STATUS_CANCELLED,
                        OptionalInt.empty()));
            }
        }

@@ -1076,7 +1094,8 @@ public class NotificationManager {
            synchronized (mThrottleLock) {
                for (NotificationKey key : mKnownNotifications.snapshot().keySet()) {
                    if (key.pkg.equals(pkg) && key.user.equals(user)) {
                        mKnownNotifications.put(key, KNOWN_STATUS_CANCELLED);
                        mKnownNotifications.put(key, new KnownNotification(KNOWN_STATUS_CANCELLED,
                                OptionalInt.empty()));
                    }
                }
            }
+9 −0
Original line number Diff line number Diff line
@@ -253,6 +253,15 @@ flag {
  }
}

flag {
  name: "notification_update_shedding_allow_progress_completion"
  namespace: "systemui"
  description: "When throttling notification updates, specifically allow the progress -> no_progress transition"
  bug: "425364383"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}

# Start: exported flags that cannot be removed

+75 −3
Original line number Diff line number Diff line
@@ -141,6 +141,78 @@ public class NotificationManagerTest {
                eq("some.package.name"), any(), any(), anyInt(), any(), anyInt());
    }

    @Test
    @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
            Flags.FLAG_NOTIFICATION_UPDATE_SHEDDING_ALLOW_PROGRESS_COMPLETION})
    public void notifyAsPackage_advanceProgress_isThrottled() throws Exception {
        for (int i = 0; i < 100; i++) {
            Notification advance = new Notification.Builder(mContext, "channel")
                    .setSmallIcon(android.R.drawable.star_big_on)
                    .setProgress(200, i, false)
                    .build();
            mNotificationManager.notifyAsPackage("some.package.name", "tag", 1, advance);
            mClock.advanceByMillis(5);
        }

        verify(mNotificationManager.mBackendService, atLeast(20)).enqueueNotificationWithTag(
                eq("some.package.name"), any(), any(), anyInt(), any(), anyInt());
        verify(mNotificationManager.mBackendService, atMost(30)).enqueueNotificationWithTag(
                eq("some.package.name"), any(), any(), anyInt(), any(), anyInt());
    }

    @Test
    @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
            Flags.FLAG_NOTIFICATION_UPDATE_SHEDDING_ALLOW_PROGRESS_COMPLETION})
    public void notifyAsPackage_completesProgress_isNotThrottled() throws Exception {
        for (int i = 0; i < 100; i++) {
            Notification advance = new Notification.Builder(mContext, "channel")
                    .setSmallIcon(android.R.drawable.star_big_on)
                    .setProgress(200, i, false)
                    .build();
            mNotificationManager.notifyAsPackage("some.package.name", "tag", 1, advance);
            mClock.advanceByMillis(5);
        }

        verify(mNotificationManager.mBackendService, atMost(30)).enqueueNotificationWithTag(
                eq("some.package.name"), any(), any(), anyInt(), any(), anyInt());

        // Now, post one more notification that brings the progress bar to the end.
        Notification finished = new Notification.Builder(mContext, "channel")
                .setProgress(200, 200, false)
                .setSmallIcon(android.R.drawable.star_big_on)
                .build();
        mNotificationManager.notifyAsPackage("some.package.name", "tag", 1, finished);

        verify(mNotificationManager.mBackendService).enqueueNotificationWithTag(
                eq("some.package.name"), any(), any(), anyInt(), eq(finished), anyInt());
    }

    @Test
    @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
            Flags.FLAG_NOTIFICATION_UPDATE_SHEDDING_ALLOW_PROGRESS_COMPLETION})
    public void notifyAsPackage_removesProgress_isNotThrottled() throws Exception {
        for (int i = 0; i < 100; i++) {
            Notification advance = new Notification.Builder(mContext, "channel")
                    .setSmallIcon(android.R.drawable.star_big_on)
                    .setProgress(200, i, false)
                    .build();
            mNotificationManager.notifyAsPackage("some.package.name", "tag", 1, advance);
            mClock.advanceByMillis(5);
        }

        verify(mNotificationManager.mBackendService, atMost(30)).enqueueNotificationWithTag(
                eq("some.package.name"), any(), any(), anyInt(), any(), anyInt());

        // Now, post one more notification that removes the progress bar.
        Notification noProgress = new Notification.Builder(mContext, "channel")
                .setSmallIcon(android.R.drawable.star_big_on)
                .build();
        mNotificationManager.notifyAsPackage("some.package.name", "tag", 1, noProgress);

        verify(mNotificationManager.mBackendService).enqueueNotificationWithTag(
                eq("some.package.name"), any(), any(), anyInt(), eq(noProgress), anyInt());
    }

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY)
    public void cancel_unnecessaryAndRapid_isThrottled() throws Exception {
@@ -206,7 +278,7 @@ public class NotificationManagerTest {

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY)
    public void enqueue_afterCancel_isNotUpdateAndIsNotThrottled() throws Exception {
    public void notify_afterCancel_isNotUpdateAndIsNotThrottled() throws Exception {
        // First, hit the enqueue threshold.
        Notification n = exampleNotification();
        for (int i = 0; i < 100; i++) {
@@ -226,7 +298,7 @@ public class NotificationManagerTest {

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY)
    public void enqueue_afterCancelAsPackage_isNotUpdateAndIsNotThrottled() throws Exception {
    public void notify_afterCancelAsPackage_isNotUpdateAndIsNotThrottled() throws Exception {
        // First, hit the enqueue threshold.
        Notification n = exampleNotification();
        for (int i = 0; i < 100; i++) {
@@ -246,7 +318,7 @@ public class NotificationManagerTest {

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY)
    public void enqueue_afterCancelAll_isNotUpdateAndIsNotThrottled() throws Exception {
    public void notify_afterCancelAll_isNotUpdateAndIsNotThrottled() throws Exception {
        // First, hit the enqueue threshold.
        Notification n = exampleNotification();
        for (int i = 0; i < 100; i++) {
+38 −3
Original line number Diff line number Diff line
@@ -214,11 +214,46 @@ public class NotificationTest {
    }

    @Test
    public void testHasCompletedProgress_zeroMax() {
        Notification n = new Notification.Builder(mContext)
    @EnableFlags(Flags.FLAG_NOTIFICATION_UPDATE_SHEDDING_ALLOW_PROGRESS_COMPLETION)
    public void getProgressState_indeterminate_ongoing() {
        Notification n1 = new Notification.Builder(mContext)
                .setProgress(0, 0, true)
                .build();
        assertFalse(n.hasCompletedProgress());
        assertThat(n1.getProgressState()).isEqualTo(Notification.PROGRESS_STATE_ONGOING);

        // Ignores max and progress.
        Notification n2 = new Notification.Builder(mContext)
                .setProgress(100, 100, true)
                .build();
        Notification n3 = new Notification.Builder(mContext)
                .setProgress(100, 50, true)
                .build();
        assertThat(n2.getProgressState()).isEqualTo(Notification.PROGRESS_STATE_ONGOING);
        assertThat(n3.getProgressState()).isEqualTo(Notification.PROGRESS_STATE_ONGOING);
    }

    @Test
    @EnableFlags(Flags.FLAG_NOTIFICATION_UPDATE_SHEDDING_ALLOW_PROGRESS_COMPLETION)
    public void getProgressState_noProgress_none() {
        Notification n = new Notification.Builder(mContext).build();
        assertThat(n.getProgressState()).isEqualTo(Notification.PROGRESS_STATE_NONE);
    }

    @Test
    @EnableFlags(Flags.FLAG_NOTIFICATION_UPDATE_SHEDDING_ALLOW_PROGRESS_COMPLETION)
    public void getProgressState_atMax_complete() {
        Notification n = new Notification.Builder(mContext)
                .setProgress(10, 10, false)
                .build();
        assertThat(n.getProgressState()).isEqualTo(Notification.PROGRESS_STATE_COMPLETE);
    }

    @Test
    public void getProgressState_notAtMax_ongoing() {
        Notification n = new Notification.Builder(mContext)
                .setProgress(10, 4, false)
                .build();
        assertThat(n.getProgressState()).isEqualTo(Notification.PROGRESS_STATE_ONGOING);
    }

    @Test
Loading