1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.notification.collection.provider 18 19 import android.os.Build 20 import android.util.Log 21 import com.android.systemui.Dumpable 22 import com.android.systemui.dagger.SysUISingleton 23 import com.android.systemui.dump.DumpManager 24 import com.android.systemui.statusbar.commandline.Command 25 import com.android.systemui.statusbar.commandline.CommandRegistry 26 import com.android.systemui.statusbar.notification.collection.NotificationEntry 27 import com.android.systemui.util.Assert 28 import com.android.systemui.util.ListenerSet 29 import java.io.PrintWriter 30 import javax.inject.Inject 31 32 /** 33 * A debug mode provider which is used by both the legacy and new notification pipelines to 34 * block unwanted notifications from appearing to the user, primarily for integration testing. 35 * 36 * The only configuration is a list of allowed packages. When this list is empty, the feature is 37 * disabled. When SystemUI starts up, this feature is disabled. 38 * 39 * To enabled filtering, provide the space-separated list of packages using the command: 40 * 41 * `$ adb shell cmd statusbar notif-filter allowed-pkgs <package> ...` 42 * 43 * To disable filtering, send the command without any packages, or explicitly reset: 44 * 45 * `$ adb shell cmd statusbar notif-filter reset` 46 * 47 * NOTE: this feature only works on debug builds, and when the broadcaster is root. 48 */ 49 @SysUISingleton 50 class DebugModeFilterProvider @Inject constructor( 51 private val commandRegistry: CommandRegistry, 52 dumpManager: DumpManager 53 ) : Dumpable { 54 private var allowedPackages: List<String> = emptyList() 55 private val listeners = ListenerSet<Runnable>() 56 57 init { 58 dumpManager.registerDumpable(this) 59 } 60 61 /** 62 * Register a runnable to be invoked when the allowed packages changes, which would mean the 63 * result of [shouldFilterOut] may have changed for some entries. 64 */ 65 fun registerInvalidationListener(listener: Runnable) { 66 Assert.isMainThread() 67 if (!Build.isDebuggable()) { 68 return 69 } 70 val needsInitialization = listeners.isEmpty() 71 listeners.addIfAbsent(listener) 72 if (needsInitialization) { 73 commandRegistry.registerCommand("notif-filter") { NotifFilterCommand() } 74 Log.d(TAG, "Registered notif-filter command") 75 } 76 } 77 78 /** 79 * Determine if the given entry should be hidden from the user in debug mode. 80 * Will always return false in release. 81 */ 82 fun shouldFilterOut(entry: NotificationEntry): Boolean { 83 if (allowedPackages.isEmpty()) { 84 return false 85 } 86 return entry.sbn.packageName !in allowedPackages 87 } 88 89 override fun dump(pw: PrintWriter, args: Array<out String>) { 90 pw.println("initialized: ${listeners.isNotEmpty()}") 91 pw.println("allowedPackages: ${allowedPackages.size}") 92 allowedPackages.forEachIndexed { i, pkg -> 93 pw.println(" [$i]: $pkg") 94 } 95 } 96 97 companion object { 98 private const val TAG = "DebugModeFilterProvider" 99 } 100 101 inner class NotifFilterCommand : Command { 102 override fun execute(pw: PrintWriter, args: List<String>) { 103 when (args.firstOrNull()) { 104 "reset" -> { 105 if (args.size > 1) { 106 return invalidCommand(pw, "Unexpected arguments for 'reset' command") 107 } 108 allowedPackages = emptyList() 109 } 110 "allowed-pkgs" -> { 111 allowedPackages = args.drop(1) 112 } 113 null -> return invalidCommand(pw, "Missing command") 114 else -> return invalidCommand(pw, "Unknown command: ${args.firstOrNull()}") 115 } 116 Log.d(TAG, "Updated allowedPackages: $allowedPackages") 117 if (allowedPackages.isEmpty()) { 118 pw.print("Resetting allowedPackages ... ") 119 } else { 120 pw.print("Updating allowedPackages: $allowedPackages ... ") 121 } 122 listeners.forEach(Runnable::run) 123 pw.println("DONE") 124 } 125 126 private fun invalidCommand(pw: PrintWriter, reason: String) { 127 pw.println("Error: $reason") 128 pw.println() 129 help(pw) 130 } 131 132 override fun help(pw: PrintWriter) { 133 pw.println("Usage: adb shell cmd statusbar notif-filter <command>") 134 pw.println("Available commands:") 135 pw.println(" reset") 136 pw.println(" Restore the default system behavior.") 137 pw.println(" allowed-pkgs <package> ...") 138 pw.println(" Hide all notification except from packages listed here.") 139 pw.println(" Providing no packages is treated as a reset.") 140 } 141 } 142 } 143