Merge "Add a debug mode that allows disabling notifications for all packages that aren't allowed."

This commit is contained in:
TreeHugger Robot
2021-12-09 23:06:00 +00:00
committed by Android (Google) Code Review
6 changed files with 197 additions and 25 deletions

View File

@@ -34,6 +34,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
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 @@ import javax.inject.Inject;
@SysUISingleton
public class NotificationFilter {
private final DebugModeFilterProvider mDebugNotificationFilter;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardEnvironment mKeyguardEnvironment;
private final ForegroundServiceController mForegroundServiceController;
@@ -52,11 +54,13 @@ public class NotificationFilter {
@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 class NotificationFilter {
*/
public boolean shouldFilterOut(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
if (mDebugNotificationFilter.shouldFilterOut(entry)) {
return true;
}
if (!(mKeyguardEnvironment.isDeviceProvisioned()
|| showNotificationEvenIfUnprovisioned(sbn))) {
return true;

View File

@@ -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)
}
}

View File

@@ -47,6 +47,7 @@ class NotifCoordinatorsImpl @Inject constructor(
gutsCoordinator: GutsCoordinator,
communalCoordinator: CommunalCoordinator,
conversationCoordinator: ConversationCoordinator,
debugModeCoordinator: DebugModeCoordinator,
groupCountCoordinator: GroupCountCoordinator,
mediaCoordinator: MediaCoordinator,
preparationCoordinator: PreparationCoordinator,
@@ -86,6 +87,7 @@ class NotifCoordinatorsImpl @Inject constructor(
mCoordinators.add(deviceProvisionedCoordinator)
mCoordinators.add(bubbleCoordinator)
mCoordinators.add(communalCoordinator)
mCoordinators.add(debugModeCoordinator)
mCoordinators.add(conversationCoordinator)
mCoordinators.add(groupCountCoordinator)
mCoordinators.add(mediaCoordinator)

View File

@@ -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"
}
}

View File

@@ -36,6 +36,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica
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 @@ class NotificationsControllerImpl @Inject constructor(
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 @@ class NotificationsControllerImpl @Inject constructor(
headsUpController.attach(entryManager, headsUpManager)
groupManagerLegacy.get().setHeadsUpManager(headsUpManager)
groupAlertTransferHelper.setHeadsUpManager(headsUpManager)
debugModeFilterProvider.registerInvalidationListener {
entryManager.updateNotifications("debug mode filter changed")
}
entryManager.initialize(notificationListener, legacyRanker)
}

View File

@@ -37,6 +37,7 @@ import android.testing.AndroidTestingRunner;
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.NotificationEntryManager.Keyg
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;
@@ -77,6 +79,8 @@ public class NotificationFilterTest extends SysuiTestCase {
private final StatusBarNotification mMockStatusBarNotification =
mock(StatusBarNotification.class);
@Mock
DebugModeFilterProvider mDebugModeFilterProvider;
@Mock
StatusBarStateController mStatusBarStateController;
@Mock
@@ -132,7 +136,13 @@ public class NotificationFilterTest extends SysuiTestCase {
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 class NotificationFilterTest extends SysuiTestCase {
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 class NotificationFilterTest extends SysuiTestCase {
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 class NotificationFilterTest extends SysuiTestCase {
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 class NotificationFilterTest extends SysuiTestCase {
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