Loading core/java/android/app/INotificationManager.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -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); Loading core/java/android/app/NotificationManager.java +100 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 = Loading @@ -1420,6 +1443,7 @@ public class NotificationManager { } return new ArrayList<>(); } } /** * Deletes the given notification channel group, and all notification channels that Loading Loading @@ -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<>() { Loading Loading @@ -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( Loading @@ -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 Loading @@ -1509,8 +1587,9 @@ public class NotificationManager { * @hide */ @VisibleForTesting public void setChannelCacheToTestMode() { public void setChannelCachesToTestMode() { mNotificationChannelListCache.testPropertyName(); mNotificationChannelGroupsCache.testPropertyName(); } /** Loading core/tests/coretests/src/android/app/NotificationManagerTest.java +84 −3 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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 Loading Loading @@ -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(); } Loading Loading @@ -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() { Loading services/core/java/com/android/server/notification/NotificationManagerService.java +10 −0 Original line number Diff line number Diff line Loading @@ -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); Loading services/core/java/com/android/server/notification/PreferencesHelper.java +28 −2 Original line number Diff line number Diff line Loading @@ -276,6 +276,7 @@ public class PreferencesHelper implements RankingConfig { // notification channels. if (android.app.Flags.nmBinderPerfCacheChannels()) { invalidateNotificationChannelCache(); invalidateNotificationChannelGroupCache(); } } Loading Loading @@ -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) { Loading Loading @@ -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, Loading @@ -1062,6 +1065,9 @@ public class PreferencesHelper implements RankingConfig { if (needsDndChange) { updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi); } if (android.app.Flags.nmBinderPerfCacheChannels() && changed) { invalidateNotificationChannelGroupCache(); } } @Override Loading Loading @@ -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)) { Loading @@ -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); } Loading @@ -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) { Loading Loading @@ -2896,6 +2914,7 @@ public class PreferencesHelper implements RankingConfig { } if (android.app.Flags.nmBinderPerfCacheChannels() && removed) { invalidateNotificationChannelCache(); invalidateNotificationChannelGroupCache(); } } } Loading Loading @@ -3008,6 +3027,7 @@ public class PreferencesHelper implements RankingConfig { updateConfig(); if (android.app.Flags.nmBinderPerfCacheChannels()) { invalidateNotificationChannelCache(); invalidateNotificationChannelGroupCache(); } } return updated; Loading @@ -3028,6 +3048,7 @@ public class PreferencesHelper implements RankingConfig { p.showBadge = DEFAULT_SHOW_BADGE; if (android.app.Flags.nmBinderPerfCacheChannels()) { invalidateNotificationChannelCache(); invalidateNotificationChannelGroupCache(); } } } Loading Loading @@ -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 Loading
core/java/android/app/INotificationManager.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
core/java/android/app/NotificationManager.java +100 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 = Loading @@ -1420,6 +1443,7 @@ public class NotificationManager { } return new ArrayList<>(); } } /** * Deletes the given notification channel group, and all notification channels that Loading Loading @@ -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<>() { Loading Loading @@ -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( Loading @@ -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 Loading @@ -1509,8 +1587,9 @@ public class NotificationManager { * @hide */ @VisibleForTesting public void setChannelCacheToTestMode() { public void setChannelCachesToTestMode() { mNotificationChannelListCache.testPropertyName(); mNotificationChannelGroupsCache.testPropertyName(); } /** Loading
core/tests/coretests/src/android/app/NotificationManagerTest.java +84 −3 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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 Loading Loading @@ -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(); } Loading Loading @@ -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() { Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +10 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
services/core/java/com/android/server/notification/PreferencesHelper.java +28 −2 Original line number Diff line number Diff line Loading @@ -276,6 +276,7 @@ public class PreferencesHelper implements RankingConfig { // notification channels. if (android.app.Flags.nmBinderPerfCacheChannels()) { invalidateNotificationChannelCache(); invalidateNotificationChannelGroupCache(); } } Loading Loading @@ -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) { Loading Loading @@ -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, Loading @@ -1062,6 +1065,9 @@ public class PreferencesHelper implements RankingConfig { if (needsDndChange) { updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi); } if (android.app.Flags.nmBinderPerfCacheChannels() && changed) { invalidateNotificationChannelGroupCache(); } } @Override Loading Loading @@ -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)) { Loading @@ -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); } Loading @@ -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) { Loading Loading @@ -2896,6 +2914,7 @@ public class PreferencesHelper implements RankingConfig { } if (android.app.Flags.nmBinderPerfCacheChannels() && removed) { invalidateNotificationChannelCache(); invalidateNotificationChannelGroupCache(); } } } Loading Loading @@ -3008,6 +3027,7 @@ public class PreferencesHelper implements RankingConfig { updateConfig(); if (android.app.Flags.nmBinderPerfCacheChannels()) { invalidateNotificationChannelCache(); invalidateNotificationChannelGroupCache(); } } return updated; Loading @@ -3028,6 +3048,7 @@ public class PreferencesHelper implements RankingConfig { p.showBadge = DEFAULT_SHOW_BADGE; if (android.app.Flags.nmBinderPerfCacheChannels()) { invalidateNotificationChannelCache(); invalidateNotificationChannelGroupCache(); } } } Loading Loading @@ -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