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

Commit 9e25efbe authored by Yuri Lin's avatar Yuri Lin Committed by Android (Google) Code Review
Browse files

Merge "Cache notification channel groups." into main

parents 5555f7c8 2ba3ac9e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -121,6 +121,7 @@ interface INotificationManager
    void deleteNotificationChannelGroup(String pkg, String channelGroupId);
    NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId);
    ParceledListSlice getNotificationChannelGroups(String pkg);
    ParceledListSlice getNotificationChannelGroupsWithoutChannels(String pkg);
    boolean onlyHasDefaultChannel(String pkg, int uid);
    boolean areChannelsBypassingDnd();
    ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int uid);
+100 −21
Original line number Diff line number Diff line
@@ -69,12 +69,14 @@ import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.notification.NotificationChannelGroupsHelper;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1396,18 +1398,39 @@ public class NotificationManager {
     * The channel group must belong to your package, or null will be returned.
     */
    public NotificationChannelGroup getNotificationChannelGroup(String channelGroupId) {
        if (Flags.nmBinderPerfCacheChannels()) {
            String pkgName = mContext.getPackageName();
            // getNotificationChannelGroup may only be called by the same package.
            List<NotificationChannel> channelList = mNotificationChannelListCache.query(
                    new NotificationChannelQuery(pkgName, pkgName, mContext.getUserId()));
            Map<String, NotificationChannelGroup> groupHeaders =
                    mNotificationChannelGroupsCache.query(pkgName);
            return NotificationChannelGroupsHelper.getGroupWithChannels(channelGroupId, channelList,
                    groupHeaders, /* includeDeleted= */ false);
        } else {
            INotificationManager service = service();
            try {
            return service.getNotificationChannelGroup(mContext.getPackageName(), channelGroupId);
                return service.getNotificationChannelGroup(mContext.getPackageName(),
                        channelGroupId);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Returns all notification channel groups belonging to the calling app.
     */
    public List<NotificationChannelGroup> getNotificationChannelGroups() {
        if (Flags.nmBinderPerfCacheChannels()) {
            String pkgName = mContext.getPackageName();
            List<NotificationChannel> channelList = mNotificationChannelListCache.query(
                    new NotificationChannelQuery(pkgName, pkgName, mContext.getUserId()));
            Map<String, NotificationChannelGroup> groupHeaders =
                    mNotificationChannelGroupsCache.query(pkgName);
            return NotificationChannelGroupsHelper.getGroupsWithChannels(channelList, groupHeaders,
                    NotificationChannelGroupsHelper.Params.forAllGroups());
        } else {
            INotificationManager service = service();
            try {
                final ParceledListSlice<NotificationChannelGroup> parceledList =
@@ -1420,6 +1443,7 @@ public class NotificationManager {
            }
            return new ArrayList<>();
        }
    }

    /**
     * Deletes the given notification channel group, and all notification channels that
@@ -1448,9 +1472,11 @@ public class NotificationManager {
        }
    }

    private static final String NOTIFICATION_CHANNEL_CACHE_API = "getNotificationChannel";
    private static final String NOTIFICATION_CHANNEL_LIST_CACHE_NAME = "getNotificationChannels";
    private static final int NOTIFICATION_CHANNEL_CACHE_SIZE = 10;
    private static final String NOTIFICATION_CHANNELS_CACHE_API = "getNotificationChannels";
    private static final int NOTIFICATION_CHANNELS_CACHE_SIZE = 10;
    private static final String NOTIFICATION_CHANNEL_GROUPS_CACHE_API =
            "getNotificationChannelGroups";
    private static final int NOTIFICATION_CHANNEL_GROUPS_CACHE_SIZE = 10;

    private final IpcDataCache.QueryHandler<NotificationChannelQuery, List<NotificationChannel>>
            mNotificationChannelListQueryHandler = new IpcDataCache.QueryHandler<>() {
@@ -1480,8 +1506,8 @@ public class NotificationManager {

    private final IpcDataCache<NotificationChannelQuery, List<NotificationChannel>>
            mNotificationChannelListCache =
            new IpcDataCache<>(NOTIFICATION_CHANNEL_CACHE_SIZE, IpcDataCache.MODULE_SYSTEM,
                    NOTIFICATION_CHANNEL_CACHE_API, NOTIFICATION_CHANNEL_LIST_CACHE_NAME,
            new IpcDataCache<>(NOTIFICATION_CHANNELS_CACHE_SIZE, IpcDataCache.MODULE_SYSTEM,
                    NOTIFICATION_CHANNELS_CACHE_API, NOTIFICATION_CHANNELS_CACHE_API,
                    mNotificationChannelListQueryHandler);

    private record NotificationChannelQuery(
@@ -1489,19 +1515,71 @@ public class NotificationManager {
            String targetPkg,
            int userId) {}

    private final IpcDataCache.QueryHandler<String, Map<String, NotificationChannelGroup>>
            mNotificationChannelGroupsQueryHandler = new IpcDataCache.QueryHandler<>() {
                @Override
                public Map<String, NotificationChannelGroup> apply(String pkg) {
                    INotificationManager service = service();
                    Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
                    try {
                        final ParceledListSlice<NotificationChannelGroup> parceledList =
                                service.getNotificationChannelGroupsWithoutChannels(pkg);
                        if (parceledList != null) {
                            for (NotificationChannelGroup group : parceledList.getList()) {
                                groups.put(group.getId(), group);
                            }
                        }
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                    return groups;
                }

                @Override
                public boolean shouldBypassCache(@NonNull String query) {
                    // Other locations should also not be querying the cache in the first place if
                    // the flag is not enabled, but this is an extra precaution.
                    if (!Flags.nmBinderPerfCacheChannels()) {
                        Log.wtf(TAG,
                                "shouldBypassCache called when nm_binder_perf_cache_channels off");
                        return true;
                    }
                    return false;
                }
            };

    private final IpcDataCache<String, Map<String, NotificationChannelGroup>>
            mNotificationChannelGroupsCache = new IpcDataCache<>(
            NOTIFICATION_CHANNEL_GROUPS_CACHE_SIZE, IpcDataCache.MODULE_SYSTEM,
            NOTIFICATION_CHANNEL_GROUPS_CACHE_API, NOTIFICATION_CHANNEL_GROUPS_CACHE_API,
            mNotificationChannelGroupsQueryHandler);

    /**
     * @hide
     */
    public static void invalidateNotificationChannelCache() {
        if (Flags.nmBinderPerfCacheChannels()) {
            IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
                    NOTIFICATION_CHANNEL_CACHE_API);
                    NOTIFICATION_CHANNELS_CACHE_API);
        } else {
            // if we are here, we have failed to flag something
            Log.wtf(TAG, "invalidateNotificationChannelCache called without flag");
        }
    }

    /**
     * @hide
     */
    public static void invalidateNotificationChannelGroupCache() {
        if (Flags.nmBinderPerfCacheChannels()) {
            IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
                    NOTIFICATION_CHANNEL_GROUPS_CACHE_API);
        } else {
            // if we are here, we have failed to flag something
            Log.wtf(TAG, "invalidateNotificationChannelGroupCache called without flag");
        }
    }

    /**
     * For testing only: running tests with a cache requires marking the cache's property for
     * testing, as test APIs otherwise cannot invalidate the cache. This must be called after
@@ -1509,8 +1587,9 @@ public class NotificationManager {
     * @hide
     */
    @VisibleForTesting
    public void setChannelCacheToTestMode() {
    public void setChannelCachesToTestMode() {
        mNotificationChannelListCache.testPropertyName();
        mNotificationChannelGroupsCache.testPropertyName();
    }

    /**
+84 −3
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ import org.junit.runner.RunWith;

import java.time.Instant;
import java.time.InstantSource;
import java.util.ArrayList;
import java.util.List;

@RunWith(AndroidJUnit4.class)
@@ -72,7 +73,7 @@ public class NotificationManagerTest {

        // Caches must be in test mode in order to be used in tests.
        PropertyInvalidatedCache.setTestMode(true);
        mNotificationManager.setChannelCacheToTestMode();
        mNotificationManager.setChannelCachesToTestMode();
    }

    @After
@@ -347,8 +348,8 @@ public class NotificationManagerTest {
        when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
                anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));

        // ask for channels 100 times without invalidating the cache
        for (int i = 0; i < 100; i++) {
        // ask for channels 5 times without invalidating the cache
        for (int i = 0; i < 5; i++) {
            List<NotificationChannel> unused = mNotificationManager.getNotificationChannels();
        }

@@ -439,6 +440,86 @@ public class NotificationManagerTest {
                .getNotificationChannels(any(), any(), anyInt());
    }

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
    public void getNotificationChannelGroup_cachedUntilInvalidated() throws Exception {
        // Data setup: group has some channels in it
        NotificationChannelGroup g1 = new NotificationChannelGroup("g1", "group one");

        NotificationChannel nc1 = new NotificationChannel("nc1", "channel one",
                NotificationManager.IMPORTANCE_DEFAULT);
        nc1.setGroup("g1");
        NotificationChannel nc2 = new NotificationChannel("nc2", "channel two",
                NotificationManager.IMPORTANCE_DEFAULT);
        nc2.setGroup("g1");

        NotificationManager.invalidateNotificationChannelCache();
        NotificationManager.invalidateNotificationChannelGroupCache();
        when(mNotificationManager.mBackendService.getNotificationChannelGroupsWithoutChannels(
                any())).thenReturn(new ParceledListSlice<>(List.of(g1)));

        // getting notification channel groups also involves looking for channels
        when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), anyInt()))
                .thenReturn(new ParceledListSlice<>(List.of(nc1, nc2)));

        // ask for group 5 times without invalidating the cache
        for (int i = 0; i < 5; i++) {
            NotificationChannelGroup unused = mNotificationManager.getNotificationChannelGroup(
                    "g1");
        }

        // invalidate group cache but not channels cache; then ask for groups again
        NotificationManager.invalidateNotificationChannelGroupCache();
        NotificationChannelGroup receivedG1 = mNotificationManager.getNotificationChannelGroup(
                "g1");

        verify(mNotificationManager.mBackendService, times(1))
                .getNotificationChannels(any(), any(), anyInt());
        verify(mNotificationManager.mBackendService,
                times(2)).getNotificationChannelGroupsWithoutChannels(any());

        // Also confirm that we got sensible information in the return value
        assertThat(receivedG1).isNotNull();
        assertThat(receivedG1.getChannels()).hasSize(2);
    }

    @Test
    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
    public void getNotificationChannelGroups_cachedUntilInvalidated() throws Exception {
        NotificationChannelGroup g1 = new NotificationChannelGroup("g1", "group one");
        NotificationChannelGroup g2 = new NotificationChannelGroup("g2", "group two");
        NotificationChannel nc1 = new NotificationChannel("nc1", "channel one",
                NotificationManager.IMPORTANCE_DEFAULT);
        nc1.setGroup("g1");

        NotificationManager.invalidateNotificationChannelCache();
        NotificationManager.invalidateNotificationChannelGroupCache();
        when(mNotificationManager.mBackendService.getNotificationChannelGroupsWithoutChannels(
                any())).thenReturn(new ParceledListSlice<>(List.of(g1, g2)));
        when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), anyInt()))
                .thenReturn(new ParceledListSlice<>(List.of(nc1)));

        // ask for groups 5 times without invalidating the cache
        for (int i = 0; i < 5; i++) {
            List<NotificationChannelGroup> unused =
                    mNotificationManager.getNotificationChannelGroups();
        }

        // invalidate group cache; ask again
        NotificationManager.invalidateNotificationChannelGroupCache();
        List<NotificationChannelGroup> result = mNotificationManager.getNotificationChannelGroups();

        verify(mNotificationManager.mBackendService,
                times(2)).getNotificationChannelGroupsWithoutChannels(any());

        NotificationChannelGroup expectedG1 = g1.clone();
        expectedG1.setChannels(List.of(nc1));
        NotificationChannelGroup expectedG2 = g2.clone();
        expectedG2.setChannels(new ArrayList<>());

        assertThat(result).containsExactly(expectedG1, expectedG2);
    }

    @Test
    @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
    public void areAutomaticZenRulesUserManaged_handheld_isTrue() {
+10 −0
Original line number Diff line number Diff line
@@ -4993,6 +4993,16 @@ public class NotificationManagerService extends SystemService {
                    NotificationChannelGroupsHelper.Params.forAllGroups());
        }
        @Override
        public ParceledListSlice<NotificationChannelGroup>
                getNotificationChannelGroupsWithoutChannels(String pkg) {
            checkCallerIsSystemOrSameApp(pkg);
            List<NotificationChannelGroup> groups = new ArrayList<>();
            groups.addAll(
                    mPreferencesHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid()));
            return new ParceledListSlice<>(groups);
        }
        @Override
        public void deleteNotificationChannelGroup(String pkg, String groupId) {
            checkCallerIsSystemOrSameApp(pkg);
+28 −2
Original line number Diff line number Diff line
@@ -276,6 +276,7 @@ public class PreferencesHelper implements RankingConfig {
        // notification channels.
        if (android.app.Flags.nmBinderPerfCacheChannels()) {
            invalidateNotificationChannelCache();
            invalidateNotificationChannelGroupCache();
        }
    }

@@ -1022,6 +1023,7 @@ public class PreferencesHelper implements RankingConfig {
            throw new IllegalArgumentException("group.getName() can't be empty");
        }
        boolean needsDndChange = false;
        boolean changed = false;
        synchronized (mLock) {
            PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
            if (r == null) {
@@ -1052,6 +1054,7 @@ public class PreferencesHelper implements RankingConfig {
            }
            if (!group.equals(oldGroup)) {
                // will log for new entries as well as name/description changes
                changed = true;
                MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
                mNotificationChannelLogger.logNotificationChannelGroup(group, uid, pkg,
                        oldGroup == null,
@@ -1062,6 +1065,9 @@ public class PreferencesHelper implements RankingConfig {
        if (needsDndChange) {
            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
        }
        if (android.app.Flags.nmBinderPerfCacheChannels() && changed) {
            invalidateNotificationChannelGroupCache();
        }
    }

    @Override
@@ -1714,6 +1720,7 @@ public class PreferencesHelper implements RankingConfig {
            String groupId, int callingUid, boolean fromSystemOrSystemUi) {
        List<NotificationChannel> deletedChannels = new ArrayList<>();
        boolean groupBypassedDnd = false;
        boolean deleted = false;
        synchronized (mLock) {
            PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
            if (r == null || TextUtils.isEmpty(groupId)) {
@@ -1722,6 +1729,7 @@ public class PreferencesHelper implements RankingConfig {

            NotificationChannelGroup channelGroup = r.groups.remove(groupId);
            if (channelGroup != null) {
                deleted = true;
                mNotificationChannelLogger.logNotificationChannelGroupDeleted(channelGroup, uid,
                        pkg);
            }
@@ -1739,12 +1747,22 @@ public class PreferencesHelper implements RankingConfig {
        if (groupBypassedDnd) {
            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
        }
        if (android.app.Flags.nmBinderPerfCacheChannels() && deletedChannels.size() > 0) {
        if (android.app.Flags.nmBinderPerfCacheChannels()) {
            if (deletedChannels.size() > 0) {
                invalidateNotificationChannelCache();
            }
            if (deleted) {
                invalidateNotificationChannelGroupCache();
            }
        }
        return deletedChannels;
    }

    /**
     * Returns all notification channel groups for the provided package and uid, without channel
     * information included. Note that this method returns the object instances from the internal
     * structure; do not modify the returned groups before copying or parceling.
     */
    @Override
    public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
            int uid) {
@@ -2896,6 +2914,7 @@ public class PreferencesHelper implements RankingConfig {
            }
            if (android.app.Flags.nmBinderPerfCacheChannels() && removed) {
                invalidateNotificationChannelCache();
                invalidateNotificationChannelGroupCache();
            }
        }
    }
@@ -3008,6 +3027,7 @@ public class PreferencesHelper implements RankingConfig {
            updateConfig();
            if (android.app.Flags.nmBinderPerfCacheChannels()) {
                invalidateNotificationChannelCache();
                invalidateNotificationChannelGroupCache();
            }
        }
        return updated;
@@ -3028,6 +3048,7 @@ public class PreferencesHelper implements RankingConfig {
                p.showBadge = DEFAULT_SHOW_BADGE;
                if (android.app.Flags.nmBinderPerfCacheChannels()) {
                    invalidateNotificationChannelCache();
                    invalidateNotificationChannelGroupCache();
                }
            }
        }
@@ -3253,6 +3274,11 @@ public class PreferencesHelper implements RankingConfig {
        NotificationManager.invalidateNotificationChannelCache();
    }

    @VisibleForTesting
    protected void invalidateNotificationChannelGroupCache() {
        NotificationManager.invalidateNotificationChannelGroupCache();
    }

    private static String packagePreferencesKey(String pkg, int uid) {
        return pkg + "|" + uid;
    }
Loading