Add a debug mode that allows disabling notifications for all packages that aren't allowed.
Bug: 209080636
Test: manual
Change-Id: Ifc9e348c5d4c8075a62ac95bd88596dbb33b22e1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 1940cb2..54f1380 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider;
import javax.inject.Inject;
@@ -44,6 +45,7 @@
@SysUISingleton
public class NotificationFilter {
+ private final DebugModeFilterProvider mDebugNotificationFilter;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardEnvironment mKeyguardEnvironment;
private final ForegroundServiceController mForegroundServiceController;
@@ -52,11 +54,13 @@
@Inject
public NotificationFilter(
+ DebugModeFilterProvider debugNotificationFilter,
StatusBarStateController statusBarStateController,
KeyguardEnvironment keyguardEnvironment,
ForegroundServiceController foregroundServiceController,
NotificationLockscreenUserManager userManager,
MediaFeatureFlag mediaFeatureFlag) {
+ mDebugNotificationFilter = debugNotificationFilter;
mStatusBarStateController = statusBarStateController;
mKeyguardEnvironment = keyguardEnvironment;
mForegroundServiceController = foregroundServiceController;
@@ -69,6 +73,10 @@
*/
public boolean shouldFilterOut(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
+ if (mDebugNotificationFilter.shouldFilterOut(entry)) {
+ return true;
+ }
+
if (!(mKeyguardEnvironment.isDeviceProvisioned()
|| showNotificationEvenIfUnprovisioned(sbn))) {
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt
new file mode 100644
index 0000000..df54ccd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider
+import javax.inject.Inject
+
+/** A small coordinator which filters out notifications from non-allowed apps. */
+@CoordinatorScope
+class DebugModeCoordinator @Inject constructor(
+ private val debugModeFilterProvider: DebugModeFilterProvider
+) : Coordinator {
+
+ override fun attach(pipeline: NotifPipeline) {
+ pipeline.addPreGroupFilter(preGroupFilter)
+ debugModeFilterProvider.registerInvalidationListener(preGroupFilter::invalidateList)
+ }
+
+ private val preGroupFilter = object : NotifFilter("DebugModeCoordinator") {
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long) =
+ debugModeFilterProvider.shouldFilterOut(entry)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 02649ba..d21d5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -47,6 +47,7 @@
gutsCoordinator: GutsCoordinator,
communalCoordinator: CommunalCoordinator,
conversationCoordinator: ConversationCoordinator,
+ debugModeCoordinator: DebugModeCoordinator,
groupCountCoordinator: GroupCountCoordinator,
mediaCoordinator: MediaCoordinator,
preparationCoordinator: PreparationCoordinator,
@@ -86,6 +87,7 @@
mCoordinators.add(deviceProvisionedCoordinator)
mCoordinators.add(bubbleCoordinator)
mCoordinators.add(communalCoordinator)
+ mCoordinators.add(debugModeCoordinator)
mCoordinators.add(conversationCoordinator)
mCoordinators.add(groupCountCoordinator)
mCoordinators.add(mediaCoordinator)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
new file mode 100644
index 0000000..d16d76a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Build
+import android.util.Log
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.Assert
+import com.android.systemui.util.ListenerSet
+import com.android.systemui.util.isNotEmpty
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * A debug mode provider which is used by both the legacy and new notification pipelines to
+ * block unwanted notifications from appearing to the user, primarily for integration testing.
+ *
+ * The only configuration is a list of allowed packages. When this list is empty, the feature is
+ * disabled. When SystemUI starts up, this feature is disabled.
+ *
+ * To enabled filtering, provide the list of packages in a comma-separated list using the command:
+ *
+ * `$ adb shell am broadcast -a com.android.systemui.action.SET_NOTIF_DEBUG_MODE
+ * --esal allowed_packages <comma-separated-packages>`
+ *
+ * To disable filtering, send the action without a list:
+ *
+ * `$ adb shell am broadcast -a com.android.systemui.action.SET_NOTIF_DEBUG_MODE`
+ *
+ * NOTE: this feature only works on debug builds, and when the broadcaster is root.
+ */
+@SysUISingleton
+class DebugModeFilterProvider @Inject constructor(
+ private val context: Context,
+ dumpManager: DumpManager
+) : Dumpable {
+ private var allowedPackages: List<String> = emptyList()
+ private val listeners = ListenerSet<Runnable>()
+
+ init {
+ dumpManager.registerDumpable(this)
+ }
+
+ /**
+ * Register a runnable to be invoked when the allowed packages changes, which would mean the
+ * result of [shouldFilterOut] may have changed for some entries.
+ */
+ fun registerInvalidationListener(listener: Runnable) {
+ Assert.isMainThread()
+ if (!Build.isDebuggable()) {
+ return
+ }
+ val needsInitialization = listeners.isEmpty()
+ listeners.addIfAbsent(listener)
+ if (needsInitialization) {
+ val filter = IntentFilter().apply { addAction(ACTION_SET_NOTIF_DEBUG_MODE) }
+ val permission = NOTIF_DEBUG_MODE_PERMISSION
+ context.registerReceiver(mReceiver, filter, permission, null)
+ Log.d(TAG, "Registered: $mReceiver")
+ }
+ }
+
+ /**
+ * Determine if the given entry should be hidden from the user in debug mode.
+ * Will always return false in release.
+ */
+ fun shouldFilterOut(entry: NotificationEntry): Boolean {
+ if (allowedPackages.isEmpty()) {
+ return false
+ }
+ return entry.sbn.packageName !in allowedPackages
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("initialized: ${listeners.isNotEmpty()}")
+ pw.println("allowedPackages: ${allowedPackages.size}")
+ allowedPackages.forEachIndexed { i, pkg ->
+ pw.println(" [$i]: $pkg")
+ }
+ }
+
+ private val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent?) {
+ val action = intent?.action
+ if (ACTION_SET_NOTIF_DEBUG_MODE == action) {
+ allowedPackages = intent.extras?.getStringArrayList(EXTRA_ALLOWED_PACKAGES)
+ ?: emptyList()
+ Log.d(TAG, "Updated allowedPackages: $allowedPackages")
+ listeners.forEach(Runnable::run)
+ } else {
+ Log.d(TAG, "Malformed intent: $intent")
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "DebugModeFilterProvider"
+ private const val ACTION_SET_NOTIF_DEBUG_MODE =
+ "com.android.systemui.action.SET_NOTIF_DEBUG_MODE"
+ private const val NOTIF_DEBUG_MODE_PERMISSION =
+ "com.android.systemui.permission.NOTIF_DEBUG_MODE"
+ private const val EXTRA_ALLOWED_PACKAGES = "allowed_packages"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 84f0955..38f3c39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -36,6 +36,7 @@
import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.interruption.HeadsUpController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
@@ -65,6 +66,7 @@
private val notifPipelineFlags: NotifPipelineFlags,
private val notificationListener: NotificationListener,
private val entryManager: NotificationEntryManager,
+ private val debugModeFilterProvider: DebugModeFilterProvider,
private val legacyRanker: NotificationRankingManager,
private val commonNotifCollection: Lazy<CommonNotifCollection>,
private val notifPipeline: Lazy<NotifPipeline>,
@@ -134,6 +136,9 @@
headsUpController.attach(entryManager, headsUpManager)
groupManagerLegacy.get().setHeadsUpManager(headsUpManager)
groupAlertTransferHelper.setHeadsUpManager(headsUpManager)
+ debugModeFilterProvider.registerInvalidationListener {
+ entryManager.updateNotifications("debug mode filter changed")
+ }
entryManager.initialize(notificationListener, legacyRanker)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index b02a336..ed8b532 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -37,6 +37,7 @@
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.annotation.NonNull;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -50,6 +51,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -78,6 +80,8 @@
mock(StatusBarNotification.class);
@Mock
+ DebugModeFilterProvider mDebugModeFilterProvider;
+ @Mock
StatusBarStateController mStatusBarStateController;
@Mock
KeyguardEnvironment mEnvironment;
@@ -132,7 +136,13 @@
mDependency,
TestableLooper.get(this));
mRow = testHelper.createRow();
- mNotificationFilter = new NotificationFilter(
+ mNotificationFilter = newNotificationFilter();
+ }
+
+ @NonNull
+ private NotificationFilter newNotificationFilter() {
+ return new NotificationFilter(
+ mDebugModeFilterProvider,
mStatusBarStateController,
mEnvironment,
mFsc,
@@ -205,12 +215,7 @@
public void shouldFilterOtherNotificationWhenDisabled() {
// GIVEN that the media feature is disabled
when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
- NotificationFilter filter = new NotificationFilter(
- mStatusBarStateController,
- mEnvironment,
- mFsc,
- mUserManager,
- mMediaFeatureFlag);
+ NotificationFilter filter = newNotificationFilter();
// WHEN the media filter is asked about an entry
NotificationEntry otherEntry = new NotificationEntryBuilder().build();
final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
@@ -222,12 +227,7 @@
public void shouldFilterOtherNotificationWhenEnabled() {
// GIVEN that the media feature is enabled
when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
- NotificationFilter filter = new NotificationFilter(
- mStatusBarStateController,
- mEnvironment,
- mFsc,
- mUserManager,
- mMediaFeatureFlag);
+ NotificationFilter filter = newNotificationFilter();
// WHEN the media filter is asked about an entry
NotificationEntry otherEntry = new NotificationEntryBuilder().build();
final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
@@ -239,12 +239,7 @@
public void shouldFilterMediaNotificationWhenDisabled() {
// GIVEN that the media feature is disabled
when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
- NotificationFilter filter = new NotificationFilter(
- mStatusBarStateController,
- mEnvironment,
- mFsc,
- mUserManager,
- mMediaFeatureFlag);
+ NotificationFilter filter = newNotificationFilter();
// WHEN the media filter is asked about a media entry
final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
// THEN it shouldn't be filtered
@@ -255,12 +250,7 @@
public void shouldFilterMediaNotificationWhenEnabled() {
// GIVEN that the media feature is enabled
when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
- NotificationFilter filter = new NotificationFilter(
- mStatusBarStateController,
- mEnvironment,
- mFsc,
- mUserManager,
- mMediaFeatureFlag);
+ NotificationFilter filter = newNotificationFilter();
// WHEN the media filter is asked about a media entry
final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
// THEN it should be filtered