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 com.android.systemui.util.asIndenting
30 import com.android.systemui.util.printCollection
31 import com.android.systemui.util.println
32 import java.io.PrintWriter
33 import javax.inject.Inject
34 
35 /**
36  * A debug mode provider which is used by both the legacy and new notification pipelines to
37  * block unwanted notifications from appearing to the user, primarily for integration testing.
38  *
39  * The only configuration is a list of allowed packages.  When this list is empty, the feature is
40  * disabled.  When SystemUI starts up, this feature is disabled.
41  *
42  * To enabled filtering, provide the space-separated list of packages using the command:
43  *
44  * `$ adb shell cmd statusbar notif-filter allowed-pkgs <package> ...`
45  *
46  * To disable filtering, send the command without any packages, or explicitly reset:
47  *
48  * `$ adb shell cmd statusbar notif-filter reset`
49  *
50  * NOTE: this feature only works on debug builds, and when the broadcaster is root.
51  */
52 @SysUISingleton
53 class DebugModeFilterProvider @Inject constructor(
54     private val commandRegistry: CommandRegistry,
55     dumpManager: DumpManager
56 ) : Dumpable {
57     private var allowedPackages: List<String> = emptyList()
58     private val listeners = ListenerSet<Runnable>()
59 
60     init {
61         dumpManager.registerDumpable(this)
62     }
63 
64     /**
65      * Register a runnable to be invoked when the allowed packages changes, which would mean the
66      * result of [shouldFilterOut] may have changed for some entries.
67      */
registerInvalidationListenernull68     fun registerInvalidationListener(listener: Runnable) {
69         Assert.isMainThread()
70         if (!Build.isDebuggable()) {
71             return
72         }
73         val needsInitialization = listeners.isEmpty()
74         listeners.addIfAbsent(listener)
75         if (needsInitialization) {
76             commandRegistry.registerCommand("notif-filter") { NotifFilterCommand() }
77             Log.d(TAG, "Registered notif-filter command")
78         }
79     }
80 
81     /**
82      * Determine if the given entry should be hidden from the user in debug mode.
83      * Will always return false in release.
84      */
shouldFilterOutnull85     fun shouldFilterOut(entry: NotificationEntry): Boolean {
86         if (allowedPackages.isEmpty()) {
87             return false
88         }
89         return entry.sbn.packageName !in allowedPackages
90     }
91 
<lambda>null92     override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
93         println("initialized", listeners.isNotEmpty())
94         printCollection("allowedPackages", allowedPackages)
95     }
96 
97     companion object {
98         private const val TAG = "DebugModeFilterProvider"
99     }
100 
101     inner class NotifFilterCommand : Command {
executenull102         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 
invalidCommandnull126         private fun invalidCommand(pw: PrintWriter, reason: String) {
127             pw.println("Error: $reason")
128             pw.println()
129             help(pw)
130         }
131 
helpnull132         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