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

Commit 8c019185 authored by Matías Hernández's avatar Matías Hernández
Browse files

Throttle excessive calls to NM.notify()

This approach doesn't use IpcDataCache (which might be infeasible for posted notifications, since they change frequently) so, lacking precise information, it's heuristic-based. However, the thresholds are sufficiently large so that normal usage of the API shouldn't be affected.

Bug: 381875150
Test: atest NotificationManagerTest + existing CTS
Flag: android.app.nm_binder_perf_throttle_notify
Change-Id: Icdc969c0c646ef2222194bacb5a8486fe38e0498
parent 15c04f36
Loading
Loading
Loading
Loading
+140 −84

File changed.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Diff line number Diff line
@@ -289,6 +289,7 @@ import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
import com.android.internal.util.Preconditions;

import java.time.InstantSource;
import java.util.Map;
import java.util.Objects;

@@ -625,7 +626,7 @@ public final class SystemServiceRegistry {
                                    com.android.internal.R.style.Theme_Holo_Dialog,
                                    com.android.internal.R.style.Theme_DeviceDefault_Dialog,
                                    com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)),
                    ctx.mMainThread.getHandler());
                    InstantSource.system());
            }});

        registerService(Context.PEOPLE_SERVICE, PeopleManager.class,
+7 −0
Original line number Diff line number Diff line
@@ -290,6 +290,13 @@ flag {
  bug: "362981561"
}

flag {
  name: "nm_binder_perf_throttle_notify"
  namespace: "systemui"
  description: "Rate-limit calls to enqueueNotificationWithTag client-side"
  bug: "362981561"
}

flag {
  name: "no_sbnholder"
  namespace: "systemui"
+4 −4
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -11,10 +11,10 @@
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 * limitations under the License.
 */

package com.android.server.notification;
package android.service.notification;


/**
@@ -22,7 +22,7 @@ package com.android.server.notification;
 *
 * {@hide}
 */
class RateEstimator {
public class RateEstimator {
    private static final double RATE_ALPHA = 0.7;
    private static final double MINIMUM_DT = 0.0005;

+151 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.time.Instant;
import java.time.InstantSource;

@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class NotificationManagerTest {
    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    private Context mContext;
    private NotificationManagerWithMockService mNotificationManager;
    private final FakeClock mClock = new FakeClock();

    @Before
    public void setUp() {
        mContext = ApplicationProvider.getApplicationContext();
        mNotificationManager = new NotificationManagerWithMockService(mContext, mClock);
    }

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY)
    public void notify_rapidUpdate_isThrottled() throws Exception {
        Notification n = exampleNotification();

        for (int i = 0; i < 100; i++) {
            mNotificationManager.notify(1, n);
            mClock.advanceByMillis(5);
        }

        verify(mNotificationManager.mBackendService, atMost(30)).enqueueNotificationWithTag(any(),
                any(), any(), anyInt(), any(), anyInt());
    }

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY)
    public void notify_reasonableUpdate_isNotThrottled() throws Exception {
        Notification n = exampleNotification();

        for (int i = 0; i < 100; i++) {
            mNotificationManager.notify(1, n);
            mClock.advanceByMillis(300);
        }

        verify(mNotificationManager.mBackendService, times(100)).enqueueNotificationWithTag(any(),
                any(), any(), anyInt(), any(), anyInt());
    }

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY)
    public void notify_rapidAdd_isNotThrottled() throws Exception {
        Notification n = exampleNotification();

        for (int i = 0; i < 100; i++) {
            mNotificationManager.notify(i, n);
            mClock.advanceByMillis(5);
        }

        verify(mNotificationManager.mBackendService, times(100)).enqueueNotificationWithTag(any(),
                any(), any(), anyInt(), any(), anyInt());
    }

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY)
    public void notify_rapidAddAndCancel_isNotThrottled() throws Exception {
        Notification n = exampleNotification();

        for (int i = 0; i < 100; i++) {
            mNotificationManager.notify(1, n);
            mNotificationManager.cancel(1);
            mClock.advanceByMillis(5);
        }

        verify(mNotificationManager.mBackendService, times(100)).enqueueNotificationWithTag(any(),
                any(), any(), anyInt(), any(), anyInt());
    }

    private Notification exampleNotification() {
        return new Notification.Builder(mContext, "channel")
                .setSmallIcon(android.R.drawable.star_big_on)
                .build();
    }

    private static class NotificationManagerWithMockService extends NotificationManager {

        private final INotificationManager mBackendService;

        NotificationManagerWithMockService(Context context, InstantSource clock) {
            super(context, clock);
            mBackendService = mock(INotificationManager.class);
        }

        @Override
        public INotificationManager service() {
            return mBackendService;
        }
    }

    private static class FakeClock implements InstantSource {

        private long mNowMillis = 441644400000L;

        @Override
        public Instant instant() {
            return Instant.ofEpochMilli(mNowMillis);
        }

        private void advanceByMillis(long millis) {
            mNowMillis += millis;
        }
    }
}
Loading