summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/NotificationManager.java170
-rw-r--r--core/tests/coretests/src/android/app/NotificationManagerTest.java214
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java1
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java93
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java253
5 files changed, 690 insertions, 41 deletions
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index ec10913360af..0ddda12b0e2f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -17,6 +17,7 @@
package android.app;
import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.service.notification.Flags.notificationClassification;
@@ -50,6 +51,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -71,6 +73,8 @@ import android.util.LruCache;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.InstantSource;
@@ -1202,12 +1206,20 @@ public class NotificationManager {
* package (see {@link Context#createPackageContext(String, int)}).</p>
*/
public NotificationChannel getNotificationChannel(String channelId) {
- INotificationManager service = service();
- try {
- return service.getNotificationChannel(mContext.getOpPackageName(),
- mContext.getUserId(), mContext.getPackageName(), channelId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (Flags.nmBinderPerfCacheChannels()) {
+ return getChannelFromList(channelId,
+ mNotificationChannelListCache.query(new NotificationChannelQuery(
+ mContext.getOpPackageName(),
+ mContext.getPackageName(),
+ mContext.getUserId())));
+ } else {
+ INotificationManager service = service();
+ try {
+ return service.getNotificationChannel(mContext.getOpPackageName(),
+ mContext.getUserId(), mContext.getPackageName(), channelId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@@ -1222,13 +1234,21 @@ public class NotificationManager {
*/
public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
@NonNull String conversationId) {
- INotificationManager service = service();
- try {
- return service.getConversationNotificationChannel(mContext.getOpPackageName(),
- mContext.getUserId(), mContext.getPackageName(), channelId, true,
- conversationId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (Flags.nmBinderPerfCacheChannels()) {
+ return getConversationChannelFromList(channelId, conversationId,
+ mNotificationChannelListCache.query(new NotificationChannelQuery(
+ mContext.getOpPackageName(),
+ mContext.getPackageName(),
+ mContext.getUserId())));
+ } else {
+ INotificationManager service = service();
+ try {
+ return service.getConversationNotificationChannel(mContext.getOpPackageName(),
+ mContext.getUserId(), mContext.getPackageName(), channelId, true,
+ conversationId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@@ -1241,15 +1261,62 @@ public class NotificationManager {
* {@link Context#createPackageContext(String, int)}).</p>
*/
public List<NotificationChannel> getNotificationChannels() {
- INotificationManager service = service();
- try {
- return service.getNotificationChannels(mContext.getOpPackageName(),
- mContext.getPackageName(), mContext.getUserId()).getList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (Flags.nmBinderPerfCacheChannels()) {
+ return mNotificationChannelListCache.query(new NotificationChannelQuery(
+ mContext.getOpPackageName(),
+ mContext.getPackageName(),
+ mContext.getUserId()));
+ } else {
+ INotificationManager service = service();
+ try {
+ return service.getNotificationChannels(mContext.getOpPackageName(),
+ mContext.getPackageName(), mContext.getUserId()).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
+ // channel list assumed to be associated with the appropriate package & user id already.
+ private static NotificationChannel getChannelFromList(String channelId,
+ List<NotificationChannel> channels) {
+ if (channels == null) {
+ return null;
+ }
+ if (channelId == null) {
+ channelId = DEFAULT_CHANNEL_ID;
+ }
+ for (NotificationChannel channel : channels) {
+ if (channelId.equals(channel.getId())) {
+ return channel;
+ }
+ }
+ return null;
+ }
+
+ private static NotificationChannel getConversationChannelFromList(String channelId,
+ String conversationId, List<NotificationChannel> channels) {
+ if (channels == null) {
+ return null;
+ }
+ if (channelId == null) {
+ channelId = DEFAULT_CHANNEL_ID;
+ }
+ if (conversationId == null) {
+ return getChannelFromList(channelId, channels);
+ }
+ NotificationChannel parent = null;
+ for (NotificationChannel channel : channels) {
+ if (conversationId.equals(channel.getConversationId())
+ && channelId.equals(channel.getParentChannelId())) {
+ return channel;
+ } else if (channelId.equals(channel.getId())) {
+ parent = channel;
+ }
+ }
+ return parent;
+ }
+
/**
* Deletes the given notification channel.
*
@@ -1328,6 +1395,71 @@ 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 final IpcDataCache.QueryHandler<NotificationChannelQuery, List<NotificationChannel>>
+ mNotificationChannelListQueryHandler = new IpcDataCache.QueryHandler<>() {
+ @Override
+ public List<NotificationChannel> apply(NotificationChannelQuery query) {
+ INotificationManager service = service();
+ try {
+ return service.getNotificationChannels(query.callingPkg,
+ query.targetPkg, query.userId).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean shouldBypassCache(@NonNull NotificationChannelQuery 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<NotificationChannelQuery, List<NotificationChannel>>
+ mNotificationChannelListCache =
+ new IpcDataCache<>(NOTIFICATION_CHANNEL_CACHE_SIZE, IpcDataCache.MODULE_SYSTEM,
+ NOTIFICATION_CHANNEL_CACHE_API, NOTIFICATION_CHANNEL_LIST_CACHE_NAME,
+ mNotificationChannelListQueryHandler);
+
+ private record NotificationChannelQuery(
+ String callingPkg,
+ String targetPkg,
+ int userId) {}
+
+ /**
+ * @hide
+ */
+ public static void invalidateNotificationChannelCache() {
+ if (Flags.nmBinderPerfCacheChannels()) {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ NOTIFICATION_CHANNEL_CACHE_API);
+ } else {
+ // if we are here, we have failed to flag something
+ Log.wtf(TAG, "invalidateNotificationChannelCache 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
+ * calling PropertyInvalidatedCache.setTestMode(true).
+ * @hide
+ */
+ @VisibleForTesting
+ public void setChannelCacheToTestMode() {
+ mNotificationChannelListCache.testPropertyName();
+ }
+
/**
* @hide
*/
diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 6538ce85457c..84bcc39b8cc6 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -16,6 +16,8 @@
package android.app;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -25,16 +27,21 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.testing.TestableContext;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -42,6 +49,7 @@ import org.junit.runner.RunWith;
import java.time.Instant;
import java.time.InstantSource;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -50,14 +58,25 @@ public class NotificationManagerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- private Context mContext;
private NotificationManagerWithMockService mNotificationManager;
private final FakeClock mClock = new FakeClock();
+ @Rule
+ public final PackageTestableContext mContext = new PackageTestableContext(
+ ApplicationProvider.getApplicationContext());
+
@Before
public void setUp() {
- mContext = ApplicationProvider.getApplicationContext();
mNotificationManager = new NotificationManagerWithMockService(mContext, mClock);
+
+ // Caches must be in test mode in order to be used in tests.
+ PropertyInvalidatedCache.setTestMode(true);
+ mNotificationManager.setChannelCacheToTestMode();
+ }
+
+ @After
+ public void tearDown() {
+ PropertyInvalidatedCache.setTestMode(false);
}
@Test
@@ -243,12 +262,161 @@ public class NotificationManagerTest {
anyInt(), any(), anyInt());
}
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannel_cachedUntilInvalidated() throws Exception {
+ // Invalidate the cache first because the cache won't do anything until then
+ NotificationManager.invalidateNotificationChannelCache();
+
+ // It doesn't matter what the returned contents are, as long as we return a channel.
+ // This setup must set up getNotificationChannels(), as that's the method called.
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+ anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));
+
+ // ask for the same channel 100 times without invalidating the cache
+ for (int i = 0; i < 100; i++) {
+ NotificationChannel unused = mNotificationManager.getNotificationChannel("id");
+ }
+
+ // invalidate the cache; then ask again
+ NotificationManager.invalidateNotificationChannelCache();
+ NotificationChannel unused = mNotificationManager.getNotificationChannel("id");
+
+ verify(mNotificationManager.mBackendService, times(2))
+ .getNotificationChannels(any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannel_sameApp_oneCall() throws Exception {
+ NotificationManager.invalidateNotificationChannelCache();
+
+ NotificationChannel c1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel c2 = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_NONE);
+
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+ anyInt())).thenReturn(new ParceledListSlice<>(List.of(c1, c2)));
+
+ assertThat(mNotificationManager.getNotificationChannel("id1")).isEqualTo(c1);
+ assertThat(mNotificationManager.getNotificationChannel("id2")).isEqualTo(c2);
+ assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
+
+ verify(mNotificationManager.mBackendService, times(1))
+ .getNotificationChannels(any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannels_cachedUntilInvalidated() throws Exception {
+ NotificationManager.invalidateNotificationChannelCache();
+ 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++) {
+ List<NotificationChannel> unused = mNotificationManager.getNotificationChannels();
+ }
+
+ // invalidate the cache; then ask again
+ NotificationManager.invalidateNotificationChannelCache();
+ List<NotificationChannel> res = mNotificationManager.getNotificationChannels();
+
+ verify(mNotificationManager.mBackendService, times(2))
+ .getNotificationChannels(any(), any(), anyInt());
+ assertThat(res).containsExactlyElementsIn(List.of(exampleChannel()));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannel_channelAndConversationLookup() throws Exception {
+ NotificationManager.invalidateNotificationChannelCache();
+
+ // Full list of channels: c1; conv1 = child of c1; c2 is unrelated
+ NotificationChannel c1 = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel conv1 = new NotificationChannel("", "name_conversation",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ conv1.setConversationId("id", "id_conversation");
+ NotificationChannel c2 = new NotificationChannel("other", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), anyInt()))
+ .thenReturn(new ParceledListSlice<>(List.of(c1, conv1, c2)));
+
+ // Lookup for channel c1 and c2: returned as expected
+ assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(c1);
+ assertThat(mNotificationManager.getNotificationChannel("other")).isEqualTo(c2);
+
+ // Lookup for conv1 should return conv1
+ assertThat(mNotificationManager.getNotificationChannel("id", "id_conversation")).isEqualTo(
+ conv1);
+
+ // Lookup for a different conversation channel that doesn't exist, whose parent channel id
+ // is "id", should return c1
+ assertThat(mNotificationManager.getNotificationChannel("id", "nonexistent")).isEqualTo(c1);
+
+ // Lookup of a nonexistent channel is null
+ assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
+
+ // All of that should have been one call to getNotificationChannels()
+ verify(mNotificationManager.mBackendService, times(1))
+ .getNotificationChannels(any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void getNotificationChannel_differentPackages() throws Exception {
+ NotificationManager.invalidateNotificationChannelCache();
+ final String pkg1 = "one";
+ final String pkg2 = "two";
+ final int userId = 0;
+ final int userId1 = 1;
+
+ // multiple channels with the same ID, but belonging to different packages/users
+ NotificationChannel channel1 = new NotificationChannel("id", "name1",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationChannel channel2 = channel1.copy();
+ channel2.setName("name2");
+ NotificationChannel channel3 = channel1.copy();
+ channel3.setName("name3");
+
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
+ eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel1)));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg2),
+ eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel2)));
+ when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
+ eq(userId1))).thenReturn(new ParceledListSlice<>(List.of(channel3)));
+
+ // set our context to pretend to be from package 1 and userId 0
+ mContext.setParameters(pkg1, pkg1, userId);
+ assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(channel1);
+
+ // now package 2
+ mContext.setParameters(pkg2, pkg2, userId);
+ assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(channel2);
+
+ // now pkg1 for a different user
+ mContext.setParameters(pkg1, pkg1, userId1);
+ assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(channel3);
+
+ // Those should have been three different calls
+ verify(mNotificationManager.mBackendService, times(3))
+ .getNotificationChannels(any(), any(), anyInt());
+ }
+
private Notification exampleNotification() {
return new Notification.Builder(mContext, "channel")
.setSmallIcon(android.R.drawable.star_big_on)
.build();
}
+ private NotificationChannel exampleChannel() {
+ return new NotificationChannel("id", "channel_name",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ }
+
private static class NotificationManagerWithMockService extends NotificationManager {
private final INotificationManager mBackendService;
@@ -264,6 +432,48 @@ public class NotificationManagerTest {
}
}
+ // Helper TestableContext class where we can control just the return values of getPackageName,
+ // getOpPackageName, and getUserId (used in getNotificationChannels).
+ private static class PackageTestableContext extends TestableContext {
+ private String mPackage;
+ private String mOpPackage;
+ private Integer mUserId;
+
+ PackageTestableContext(Context base) {
+ super(base);
+ }
+
+ void setParameters(String packageName, String opPackageName, int userId) {
+ mPackage = packageName;
+ mOpPackage = opPackageName;
+ mUserId = userId;
+ }
+
+ @Override
+ public String getPackageName() {
+ if (mPackage != null) return mPackage;
+ return super.getPackageName();
+ }
+
+ @Override
+ public String getOpPackageName() {
+ if (mOpPackage != null) return mOpPackage;
+ return super.getOpPackageName();
+ }
+
+ @Override
+ public int getUserId() {
+ if (mUserId != null) return mUserId;
+ return super.getUserId();
+ }
+
+ @Override
+ public UserHandle getUser() {
+ if (mUserId != null) return UserHandle.of(mUserId);
+ return super.getUser();
+ }
+ }
+
private static class FakeClock implements InstantSource {
private long mNowMillis = 441644400000L;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7375a68c547b..20b83b268990 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3136,6 +3136,7 @@ public class NotificationManagerService extends SystemService {
mAssistants.onBootPhaseAppsCanStart();
mConditionProviders.onBootPhaseAppsCanStart();
mHistoryManager.onBootPhaseAppsCanStart();
+ mPreferencesHelper.onBootPhaseAppsCanStart();
migrateDefaultNAS();
maybeShowInitialReviewPermissionsNotification();
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 15377d6b269a..9d25d18df87c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -82,7 +82,6 @@ import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
-import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -272,6 +271,15 @@ public class PreferencesHelper implements RankingConfig {
updateMediaNotificationFilteringEnabled();
}
+ void onBootPhaseAppsCanStart() {
+ // IpcDataCaches must be invalidated once data becomes available, as queries will only
+ // begin to be cached after the first invalidation signal. At this point, we know about all
+ // notification channels.
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
+ }
+
public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
throws XmlPullParserException, IOException {
int type = parser.getEventType();
@@ -531,12 +539,14 @@ public class PreferencesHelper implements RankingConfig {
private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
@UserIdInt int userId, int uid, int importance, int priority, int visibility,
boolean showBadge, int bubblePreference, long creationTime) {
+ boolean created = false;
final String key = packagePreferencesKey(pkg, uid);
PackagePreferences
r = (uid == UNKNOWN_UID)
? mRestoredWithoutUids.get(unrestoredPackageKey(pkg, userId))
: mPackagePreferences.get(key);
if (r == null) {
+ created = true;
r = new PackagePreferences();
r.pkg = pkg;
r.uid = uid;
@@ -572,6 +582,9 @@ public class PreferencesHelper implements RankingConfig {
mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, userId));
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && created) {
+ invalidateNotificationChannelCache();
+ }
return r;
}
@@ -664,6 +677,9 @@ public class PreferencesHelper implements RankingConfig {
}
NotificationChannel channel = new NotificationChannel(channelId, label, IMPORTANCE_LOW);
p.channels.put(channelId, channel);
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
return channel;
}
@@ -1208,6 +1224,10 @@ public class PreferencesHelper implements RankingConfig {
updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && needsPolicyFileChange) {
+ invalidateNotificationChannelCache();
+ }
+
return needsPolicyFileChange;
}
@@ -1229,6 +1249,9 @@ public class PreferencesHelper implements RankingConfig {
}
channel.unlockFields(USER_LOCKED_IMPORTANCE);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
@@ -1301,6 +1324,9 @@ public class PreferencesHelper implements RankingConfig {
updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
}
if (changed) {
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
updateConfig();
}
}
@@ -1537,6 +1563,10 @@ public class PreferencesHelper implements RankingConfig {
if (channelBypassedDnd) {
updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
}
+
+ if (android.app.Flags.nmBinderPerfCacheChannels() && deletedChannel) {
+ invalidateNotificationChannelCache();
+ }
return deletedChannel;
}
@@ -1566,6 +1596,9 @@ public class PreferencesHelper implements RankingConfig {
}
r.channels.remove(channelId);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
@Override
@@ -1576,13 +1609,18 @@ public class PreferencesHelper implements RankingConfig {
if (r == null) {
return;
}
+ boolean deleted = false;
int N = r.channels.size() - 1;
for (int i = N; i >= 0; i--) {
String key = r.channels.keyAt(i);
if (!DEFAULT_CHANNEL_ID.equals(key)) {
r.channels.remove(key);
+ deleted = true;
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && deleted) {
+ invalidateNotificationChannelCache();
+ }
}
}
@@ -1613,6 +1651,9 @@ public class PreferencesHelper implements RankingConfig {
}
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
public void updateDefaultApps(int userId, ArraySet<String> toRemove,
@@ -1642,6 +1683,9 @@ public class PreferencesHelper implements RankingConfig {
}
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
@@ -1757,6 +1801,9 @@ public class PreferencesHelper implements RankingConfig {
if (groupBypassedDnd) {
updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && deletedChannels.size() > 0) {
+ invalidateNotificationChannelCache();
+ }
return deletedChannels;
}
@@ -1902,8 +1949,13 @@ public class PreferencesHelper implements RankingConfig {
}
}
}
- if (!deletedChannelIds.isEmpty() && mCurrentUserHasChannelsBypassingDnd) {
- updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ if (!deletedChannelIds.isEmpty()) {
+ if (mCurrentUserHasChannelsBypassingDnd) {
+ updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+ }
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
return deletedChannelIds;
}
@@ -2196,6 +2248,11 @@ public class PreferencesHelper implements RankingConfig {
PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid);
prefs.delegate = new Delegate(delegatePkg, delegateUid, true);
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ // If package delegates change, then which packages can get what channel information
+ // also changes, so we need to clear the cache.
+ invalidateNotificationChannelCache();
+ }
}
/**
@@ -2208,6 +2265,9 @@ public class PreferencesHelper implements RankingConfig {
prefs.delegate.mEnabled = false;
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
/**
@@ -2811,18 +2871,24 @@ public class PreferencesHelper implements RankingConfig {
public void onUserRemoved(int userId) {
synchronized (mLock) {
+ boolean removed = false;
int N = mPackagePreferences.size();
for (int i = N - 1; i >= 0; i--) {
PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
mPackagePreferences.removeAt(i);
+ removed = true;
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && removed) {
+ invalidateNotificationChannelCache();
+ }
}
}
protected void onLocaleChanged(Context context, int userId) {
synchronized (mLock) {
+ boolean updated = false;
int N = mPackagePreferences.size();
for (int i = 0; i < N; i++) {
PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
@@ -2833,10 +2899,14 @@ public class PreferencesHelper implements RankingConfig {
DEFAULT_CHANNEL_ID).setName(
context.getResources().getString(
R.string.default_notification_channel_label));
+ updated = true;
}
// TODO (b/346396459): Localize all reserved channels
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels() && updated) {
+ invalidateNotificationChannelCache();
+ }
}
}
@@ -2884,7 +2954,7 @@ public class PreferencesHelper implements RankingConfig {
channel.getAudioAttributes().getUsage());
if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
restoredUri)) {
- Log.w(TAG,
+ Slog.w(TAG,
"Could not restore sound: " + uri + " for channel: "
+ channel);
}
@@ -2922,6 +2992,9 @@ public class PreferencesHelper implements RankingConfig {
if (updated) {
updateConfig();
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
return updated;
}
@@ -2939,6 +3012,9 @@ public class PreferencesHelper implements RankingConfig {
p.priority = DEFAULT_PRIORITY;
p.visibility = DEFAULT_VISIBILITY;
p.showBadge = DEFAULT_SHOW_BADGE;
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
}
}
@@ -3123,6 +3199,9 @@ public class PreferencesHelper implements RankingConfig {
}
}
}
+ if (android.app.Flags.nmBinderPerfCacheChannels()) {
+ invalidateNotificationChannelCache();
+ }
}
public void migrateNotificationPermissions(List<UserInfo> users) {
@@ -3154,6 +3233,12 @@ public class PreferencesHelper implements RankingConfig {
mRankingHandler.requestSort();
}
+ @VisibleForTesting
+ // Utility method for overriding in tests to confirm that the cache gets cleared.
+ protected void invalidateNotificationChannelCache() {
+ NotificationManager.invalidateNotificationChannelCache();
+ }
+
private static String packagePreferencesKey(String pkg, int uid) {
return pkg + "|" + uid;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8e79514c875e..8cc233b7594b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -164,6 +164,7 @@ import com.android.os.AtomsProto.PackageNotificationChannelPreferences;
import com.android.os.AtomsProto.PackageNotificationPreferences;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.PermissionHelper.PackagePermission;
+import com.android.server.uri.UriGrantsManagerInternal;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -179,6 +180,9 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -199,9 +203,6 @@ import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
@@ -239,9 +240,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
private NotificationManager.Policy mTestNotificationPolicy;
- private PreferencesHelper mHelper;
- // fresh object for testing xml reading
- private PreferencesHelper mXmlHelper;
+ private TestPreferencesHelper mHelper;
+ // fresh object for testing xml reading; also TestPreferenceHelper in order to avoid interacting
+ // with real IpcDataCaches
+ private TestPreferencesHelper mXmlHelper;
private AudioAttributes mAudioAttributes;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
@@ -378,10 +380,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
when(mClock.millis()).thenReturn(System.currentTimeMillis());
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
resetZenModeHelper();
@@ -793,7 +795,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_oldXml_migrates() throws Exception {
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
@@ -929,7 +931,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
@@ -988,7 +990,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, /* showReviewPermissionsNotification= */ false, mClock);
@@ -1047,7 +1049,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
- mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
@@ -1641,7 +1643,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
serializer.flush();
// simulate load after reboot
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
@@ -1696,7 +1698,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
Duration.ofDays(2).toMillis() + System.currentTimeMillis());
// simulate load after reboot
- mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
loadByteArrayXml(xml.getBytes(), false, USER_ALL);
@@ -1774,10 +1776,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
when(contentResolver.getResourceId(ANDROID_RES_SOUND_URI)).thenReturn(resId).thenThrow(
new FileNotFoundException("")).thenReturn(resId);
- mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
+ mHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
- mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
+ mXmlHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
mUgmInternal, false, mClock);
@@ -6573,4 +6575,223 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.setCanBePromoted(PKG_P, UID_P, false, false);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
}
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateChannelCache_invalidateOnCreationAndChange() {
+ mHelper.resetCacheInvalidation();
+ NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+
+ // new channel should invalidate the cache.
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // when the channel data is updated, should invalidate the cache again after that.
+ mHelper.resetCacheInvalidation();
+ NotificationChannel newChannel = channel.copy();
+ newChannel.setName("new name");
+ newChannel.setImportance(IMPORTANCE_HIGH);
+ mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // also for conversations
+ mHelper.resetCacheInvalidation();
+ String parentId = "id";
+ String convId = "conversation";
+ NotificationChannel conv = new NotificationChannel(
+ String.format(CONVERSATION_CHANNEL_ID_FORMAT, parentId, convId), "conversation",
+ IMPORTANCE_DEFAULT);
+ conv.setConversationId(parentId, convId);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, conv, true, false, UID_N_MR1,
+ false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ mHelper.resetCacheInvalidation();
+ NotificationChannel newConv = conv.copy();
+ newConv.setName("changed");
+ mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, newConv, true, UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateChannelCache_invalidateOnDelete() {
+ NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+
+ // ignore any invalidations up until now
+ mHelper.resetCacheInvalidation();
+
+ mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // recreate channel and now permanently delete
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+ mHelper.resetCacheInvalidation();
+ mHelper.permanentlyDeleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "id");
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateChannelCache_noInvalidationWhenNoChange() {
+ NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+
+ // ignore any invalidations up until now
+ mHelper.resetCacheInvalidation();
+
+ // newChannel, same as the old channel
+ NotificationChannel newChannel = channel.copy();
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, false, UID_N_MR1,
+ false);
+ mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, UID_N_MR1, false);
+
+ // because there were no effective changes, we should not see any cache invalidations
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+
+ // deletions of a nonexistent channel also don't change anything
+ mHelper.resetCacheInvalidation();
+ mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "nonexistent", UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateCache_multipleUsersAndPackages() {
+ // Setup: create channels for:
+ // pkg O, user
+ // pkg O, work (same channel ID, different user)
+ // pkg N_MR1, user
+ // pkg N_MR1, user, conversation child of above
+ String p2u1ConvId = String.format(CONVERSATION_CHANNEL_ID_FORMAT, "p2", "conv");
+ NotificationChannel p1u1 = new NotificationChannel("p1", "p1u1", IMPORTANCE_DEFAULT);
+ NotificationChannel p1u2 = new NotificationChannel("p1", "p1u2", IMPORTANCE_DEFAULT);
+ NotificationChannel p2u1 = new NotificationChannel("p2", "p2u1", IMPORTANCE_DEFAULT);
+ NotificationChannel p2u1Conv = new NotificationChannel(p2u1ConvId, "p2u1 conv",
+ IMPORTANCE_DEFAULT);
+ p2u1Conv.setConversationId("p2", "conv");
+
+ mHelper.createNotificationChannel(PKG_O, UID_O, p1u1, true,
+ false, UID_O, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O + UserHandle.PER_USER_RANGE, p1u2, true,
+ false, UID_O + UserHandle.PER_USER_RANGE, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, p2u1, true,
+ false, UID_N_MR1, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, p2u1Conv, true,
+ false, UID_N_MR1, false);
+ mHelper.resetCacheInvalidation();
+
+ // Update to an existent channel, with a change: should invalidate
+ NotificationChannel p1u1New = p1u1.copy();
+ p1u1New.setName("p1u1 new");
+ mHelper.updateNotificationChannel(PKG_O, UID_O, p1u1New, true, UID_O, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // Do it again, but no change for this user
+ mHelper.resetCacheInvalidation();
+ mHelper.updateNotificationChannel(PKG_O, UID_O, p1u1New.copy(), true, UID_O, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+
+ // Delete conversations, but for a package without those conversations
+ mHelper.resetCacheInvalidation();
+ mHelper.deleteConversations(PKG_O, UID_O, Set.of(p2u1Conv.getConversationId()), UID_O,
+ false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+
+ // Now delete conversations for the right package
+ mHelper.resetCacheInvalidation();
+ mHelper.deleteConversations(PKG_N_MR1, UID_N_MR1, Set.of(p2u1Conv.getConversationId()),
+ UID_N_MR1, false);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateCache_userRemoved() throws Exception {
+ NotificationChannel c1 = new NotificationChannel("id1", "name1", IMPORTANCE_DEFAULT);
+ int uid1 = UserHandle.getUid(1, 1);
+ setUpPackageWithUid("pkg1", uid1);
+ mHelper.createNotificationChannel("pkg1", uid1, c1, true, false, uid1, false);
+ mHelper.resetCacheInvalidation();
+
+ // delete user 1; should invalidate cache
+ mHelper.onUserRemoved(1);
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateCache_packagesChanged() {
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
+ UID_N_MR1, false);
+
+ // package deleted: expect cache invalidation
+ mHelper.resetCacheInvalidation();
+ mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1},
+ new int[]{UID_N_MR1});
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+ // re-created: expect cache invalidation again
+ mHelper.resetCacheInvalidation();
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
+ UID_N_MR1, false);
+ mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1},
+ new int[]{UID_N_MR1});
+ assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+ public void testInvalidateCache_flagOff_neverTouchesCache() {
+ // Do a bunch of channel-changing operations.
+ NotificationChannel channel =
+ new NotificationChannel("id", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false,
+ UID_N_MR1, false);
+
+ NotificationChannel copy = channel.copy();
+ copy.setName("name2");
+ mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, copy, true, UID_N_MR1, false);
+ mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", UID_N_MR1, false);
+
+ assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+ }
+
+ // Test version of PreferencesHelper whose only functional difference is that it does not
+ // interact with the real IpcDataCache, and instead tracks whether or not the cache has been
+ // invalidated since creation or the last reset.
+ private static class TestPreferencesHelper extends PreferencesHelper {
+ private boolean mCacheInvalidated = false;
+
+ TestPreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
+ ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
+ NotificationChannelLogger notificationChannelLogger,
+ AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
+ UriGrantsManagerInternal ugmInternal,
+ boolean showReviewPermissionsNotification, Clock clock) {
+ super(context, pm, rankingHandler, zenHelper, permHelper, permManager,
+ notificationChannelLogger, appOpsManager, userProfiles, ugmInternal,
+ showReviewPermissionsNotification, clock);
+ }
+
+ @Override
+ protected void invalidateNotificationChannelCache() {
+ mCacheInvalidated = true;
+ }
+
+ boolean hasCacheBeenInvalidated() {
+ return mCacheInvalidated;
+ }
+
+ void resetCacheInvalidation() {
+ mCacheInvalidated = false;
+ }
+ }
}