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

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

Merge "Fix rate-limiting of enqueued notifications" into udc-qpr-dev

parents 77ccd8bc a18b884e
Loading
Loading
Loading
Loading
+18 −46
Original line number Diff line number Diff line
@@ -6746,6 +6746,8 @@ public class NotificationManagerService extends SystemService {
            return false;
        }
        mUsageStats.registerEnqueuedByAppAndAccepted(pkg);
        if (info != null) {
            // Cache the shortcut synchronously after the associated notification is posted in case
            // the app unpublishes this shortcut immediately after posting the notification. If the
@@ -7184,11 +7186,12 @@ public class NotificationManagerService extends SystemService {
                            + " cannot create notifications");
                }
                // rate limit updates that aren't completed progress notifications
                if (mNotificationsByKey.get(r.getSbn().getKey()) != null
                        && !r.getNotification().hasCompletedProgress()
                        && !isAutogroup) {
                // Rate limit updates that aren't completed progress notifications
                // Search for the original one in the posted and not-yet-posted (enqueued) lists.
                boolean isUpdate = mNotificationsByKey.get(r.getSbn().getKey()) != null
                        || findNotificationByListLocked(mEnqueuedNotifications, r.getSbn().getKey())
                        != null;
                if (isUpdate && !r.getNotification().hasCompletedProgress() && !isAutogroup) {
                    final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
                    if (appEnqueueRate > mMaxPackageEnqueueRate) {
                        mUsageStats.registerOverRateQuota(pkg);
@@ -7813,15 +7816,8 @@ public class NotificationManagerService extends SystemService {
            boolean posted = false;
            synchronized (mNotificationLock) {
                try {
                    NotificationRecord r = null;
                    int N = mEnqueuedNotifications.size();
                    for (int i = 0; i < N; i++) {
                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                        if (Objects.equals(key, enqueued.getKey())) {
                            r = enqueued;
                            break;
                        }
                    }
                    NotificationRecord r = findNotificationByListLocked(mEnqueuedNotifications,
                            key);
                    if (r == null) {
                        Slog.i(TAG, "Cannot find enqueued record for key: " + key);
                        return false;
@@ -9486,7 +9482,7 @@ public class NotificationManagerService extends SystemService {
     * Determine whether the userId applies to the notification in question, either because
     * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
     */
    private boolean notificationMatchesUserId(NotificationRecord r, int userId) {
    private static boolean notificationMatchesUserId(NotificationRecord r, int userId) {
        return
                // looking for USER_ALL notifications? match everything
                   userId == UserHandle.USER_ALL
@@ -9845,9 +9841,9 @@ public class NotificationManagerService extends SystemService {
        return null;
    }
    @GuardedBy("mNotificationLock")
    private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
            String pkg, String tag, int id, int userId) {
    @Nullable
    private static NotificationRecord findNotificationByListLocked(
            ArrayList<NotificationRecord> list, String pkg, String tag, int id, int userId) {
        final int len = list.size();
        for (int i = 0; i < len; i++) {
            NotificationRecord r = list.get(i);
@@ -9860,8 +9856,7 @@ public class NotificationManagerService extends SystemService {
        return null;
    }
    @GuardedBy("mNotificationLock")
    private List<NotificationRecord> findNotificationsByListLocked(
    private static List<NotificationRecord> findNotificationsByListLocked(
            ArrayList<NotificationRecord> list, String pkg, String tag, int id, int userId) {
        List<NotificationRecord> matching = new ArrayList<>();
        final int len = list.size();
@@ -9876,9 +9871,9 @@ public class NotificationManagerService extends SystemService {
        return matching;
    }
    @GuardedBy("mNotificationLock")
    private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
            String key) {
    @Nullable
    private static NotificationRecord findNotificationByListLocked(
            ArrayList<NotificationRecord> list, String key) {
        final int N = list.size();
        for (int i = 0; i < N; i++) {
            if (key.equals(list.get(i).getKey())) {
@@ -9888,29 +9883,6 @@ public class NotificationManagerService extends SystemService {
        return null;
    }
    /**
     * There may be multiple records that match your criteria. For instance if there have been
     * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This
     * method will find all of them in the given list
     * @return
     */
    @GuardedBy("mNotificationLock")
    private List<NotificationRecord> findEnqueuedNotificationsForCriteria(
            String pkg, String tag, int id, int userId) {
        final ArrayList<NotificationRecord> records = new ArrayList<>();
        final int n = mEnqueuedNotifications.size();
        for (int i = 0; i < n; i++) {
            NotificationRecord r = mEnqueuedNotifications.get(i);
            if (notificationMatchesUserId(r, userId)
                    && r.getSbn().getId() == id
                    && TextUtils.equals(r.getSbn().getTag(), tag)
                    && r.getSbn().getPackageName().equals(pkg)) {
                records.add(r);
            }
        }
        return records;
    }
    @GuardedBy("mNotificationLock")
    int indexOfNotificationLocked(String key) {
        final int N = mNotificationList.size();
+30 −30
Original line number Diff line number Diff line
@@ -16,23 +16,16 @@

package com.android.server.notification;

import static android.app.NotificationManager.IMPORTANCE_HIGH;

import android.app.Notification;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteFullException;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.server.notification.NotificationManagerService.DumpFilter;

@@ -42,8 +35,6 @@ import org.json.JSONObject;

import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -63,7 +54,6 @@ public class NotificationUsageStats {
    private static final String TAG = "NotificationUsageStats";

    private static final boolean ENABLE_AGGREGATED_IN_MEMORY_STATS = true;
    private static final boolean ENABLE_SQLITE_LOG = true;
    private static final AggregatedStats[] EMPTY_AGGREGATED_STATS = new AggregatedStats[0];
    private static final String DEVICE_GLOBAL_STATS = "__global"; // packages start with letters
    private static final int MSG_EMIT = 1;
@@ -73,12 +63,15 @@ public class NotificationUsageStats {
    public static final int FOUR_HOURS = 1000 * 60 * 60 * 4;
    private static final long EMIT_PERIOD = DEBUG ? TEN_SECONDS : FOUR_HOURS;

    // Guarded by synchronized(this).
    @GuardedBy("this")
    private final Map<String, AggregatedStats> mStats = new HashMap<>();
    @GuardedBy("this")
    private final ArrayDeque<AggregatedStats[]> mStatsArrays = new ArrayDeque<>();
    @GuardedBy("this")
    private ArraySet<String> mStatExpiredkeys = new ArraySet<>();
    private final Context mContext;
    private final Handler mHandler;
    @GuardedBy("this")
    private long mLastEmitTime;

    public NotificationUsageStats(Context context) {
@@ -105,11 +98,7 @@ public class NotificationUsageStats {
     */
    public synchronized float getAppEnqueueRate(String packageName) {
        AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName);
        if (stats != null) {
        return stats.getEnqueueRate(SystemClock.elapsedRealtime());
        } else {
            return 0f;
        }
    }

    /**
@@ -117,11 +106,7 @@ public class NotificationUsageStats {
     */
    public synchronized boolean isAlertRateLimited(String packageName) {
        AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName);
        if (stats != null) {
        return stats.isAlertRateLimited();
        } else {
            return false;
        }
    }

    /**
@@ -135,17 +120,33 @@ public class NotificationUsageStats {
        releaseAggregatedStatsLocked(aggregatedStatsArray);
    }

    /**
     * Called when a notification that was enqueued by an app is effectively enqueued to be
     * posted. This is after rate checking, to update the rate.
     *
     * <p>Note that if we updated the arrival estimate <em>before</em> checking it, then an app
     * enqueueing at slightly above the acceptable rate would never get their notifications
     * accepted; updating afterwards allows the rate to dip below the threshold and thus lets
     * through some of them.
     */
    public synchronized void registerEnqueuedByAppAndAccepted(String packageName) {
        final long now = SystemClock.elapsedRealtime();
        AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName);
        for (AggregatedStats stats : aggregatedStatsArray) {
            stats.updateInterarrivalEstimate(now);
        }
        releaseAggregatedStatsLocked(aggregatedStatsArray);
    }

    /**
     * Called when a notification has been posted.
     */
    public synchronized void registerPostedByApp(NotificationRecord notification) {
        final long now = SystemClock.elapsedRealtime();
        notification.stats.posttimeElapsedMs = now;
        notification.stats.posttimeElapsedMs = SystemClock.elapsedRealtime();

        AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
        for (AggregatedStats stats : aggregatedStatsArray) {
            stats.numPostedByApp++;
            stats.updateInterarrivalEstimate(now);
            stats.countApiUse(notification);
            stats.numUndecoratedRemoteViews += (notification.hasUndecoratedRemoteView() ? 1 : 0);
        }
@@ -161,7 +162,6 @@ public class NotificationUsageStats {
        AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
        for (AggregatedStats stats : aggregatedStatsArray) {
            stats.numUpdatedByApp++;
            stats.updateInterarrivalEstimate(SystemClock.elapsedRealtime());
            stats.countApiUse(notification);
        }
        releaseAggregatedStatsLocked(aggregatedStatsArray);
@@ -257,12 +257,12 @@ public class NotificationUsageStats {
        }
    }

    // Locked by this.
    @GuardedBy("this")
    private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
        return getAggregatedStatsLocked(record.getSbn().getPackageName());
    }

    // Locked by this.
    @GuardedBy("this")
    private AggregatedStats[] getAggregatedStatsLocked(String packageName) {
        if (!ENABLE_AGGREGATED_IN_MEMORY_STATS) {
            return EMPTY_AGGREGATED_STATS;
@@ -277,7 +277,7 @@ public class NotificationUsageStats {
        return array;
    }

    // Locked by this.
    @GuardedBy("this")
    private void releaseAggregatedStatsLocked(AggregatedStats[] array) {
        for(int i = 0; i < array.length; i++) {
            array[i] = null;
@@ -285,7 +285,7 @@ public class NotificationUsageStats {
        mStatsArrays.offer(array);
    }

    // Locked by this.
    @GuardedBy("this")
    private AggregatedStats getOrCreateAggregatedStatsLocked(String key) {
        AggregatedStats result = mStats.get(key);
        if (result == null) {
+5 −10
Original line number Diff line number Diff line
@@ -22,9 +22,10 @@ package com.android.server.notification;
 *
 * {@hide}
 */
public class RateEstimator {
    private static final double RATE_ALPHA = 0.8;
class RateEstimator {
    private static final double RATE_ALPHA = 0.5;
    private static final double MINIMUM_DT = 0.0005;

    private Long mLastEventTime;
    private double mInterarrivalTime;

@@ -34,18 +35,12 @@ public class RateEstimator {
    }

    /** Update the estimate to account for an event that just happened. */
    public float update(long now) {
        float rate;
        if (mLastEventTime == null) {
            // No last event time, rate is zero.
            rate = 0f;
        } else {
    public void update(long now) {
        if (mLastEventTime != null) {
            // Calculate the new inter-arrival time based on last event time.
            mInterarrivalTime = getInterarrivalEstimate(now);
            rate = (float) (1.0 / mInterarrivalTime);
        }
        mLastEventTime = now;
        return rate;
    }

    /** @return the estimated rate if there were a new event right now. */
+99 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
@@ -678,6 +679,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    @After
    public void assertAllTrackersFinishedOrCancelled() {
        waitForIdle(); // Finish async work.
        // Verify that no trackers were left dangling.
        for (PostNotificationTracker tracker : mPostNotificationTrackerFactory.mCreatedTrackers) {
            assertThat(tracker.isOngoing()).isFalse();
@@ -11768,6 +11770,103 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertFalse(n.isUserInitiatedJob());
    }
    @Test
    public void enqueue_updatesEnqueueRate() throws Exception {
        Notification n = generateNotificationRecord(null).getNotification();
        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
        // Don't waitForIdle() here. We want to verify the "intermediate" state.
        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
        verify(mUsageStats, never()).registerPostedByApp(any());
        waitForIdle();
    }
    @Test
    public void enqueue_withPost_updatesEnqueueRateAndPost() throws Exception {
        Notification n = generateNotificationRecord(null).getNotification();
        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
        waitForIdle();
        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
        verify(mUsageStats).registerPostedByApp(any());
    }
    @Test
    public void enqueueNew_whenOverEnqueueRate_accepts() throws Exception {
        Notification n = generateNotificationRecord(null).getNotification();
        when(mUsageStats.getAppEnqueueRate(eq(PKG)))
                .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE + 1f);
        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
        waitForIdle();
        assertThat(mService.mNotificationsByKey).hasSize(1);
        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
        verify(mUsageStats).registerPostedByApp(any());
    }
    @Test
    public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception {
        // Post the first version.
        Notification original = generateNotificationRecord(null).getNotification();
        original.when = 111;
        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, original, mUserId);
        waitForIdle();
        assertThat(mService.mNotificationList).hasSize(1);
        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
        reset(mUsageStats);
        when(mUsageStats.getAppEnqueueRate(eq(PKG)))
                .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE - 1f);
        // Post the update.
        Notification update = generateNotificationRecord(null).getNotification();
        update.when = 222;
        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, update, mUserId);
        waitForIdle();
        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
        verify(mUsageStats, never()).registerPostedByApp(any());
        verify(mUsageStats).registerUpdatedByApp(any(), any());
        assertThat(mService.mNotificationList).hasSize(1);
        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222);
    }
    @Test
    public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception {
        // Post the first version.
        Notification original = generateNotificationRecord(null).getNotification();
        original.when = 111;
        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, original, mUserId);
        waitForIdle();
        assertThat(mService.mNotificationList).hasSize(1);
        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
        reset(mUsageStats);
        when(mUsageStats.getAppEnqueueRate(eq(PKG)))
                .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE + 1f);
        // Post the update.
        Notification update = generateNotificationRecord(null).getNotification();
        update.when = 222;
        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, update, mUserId);
        waitForIdle();
        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
        verify(mUsageStats, never()).registerEnqueuedByAppAndAccepted(any());
        verify(mUsageStats, never()).registerPostedByApp(any());
        verify(mUsageStats, never()).registerUpdatedByApp(any(), any());
        assertThat(mService.mNotificationList).hasSize(1);
        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old
    }
    private void setDpmAppOppsExemptFromDismissal(boolean isOn) {
        DeviceConfig.setProperty(
                DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+52 −42

File changed.

Preview size limit exceeded, changes collapsed.