1 /* <lambda>null2 * 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.isNotEmpty 30 import java.io.PrintWriter 31 import javax.inject.Inject 32 33 /** 34 * A debug mode provider which is used by both the legacy and new notification pipelines to 35 * block unwanted notifications from appearing to the user, primarily for integration testing. 36 * 37 * The only configuration is a list of allowed packages. When this list is empty, the feature is 38 * disabled. When SystemUI starts up, this feature is disabled. 39 * 40 * To enabled filtering, provide the space-separated list of packages using the command: 41 * 42 * `$ adb shell cmd statusbar notif-filter allowed-pkgs <package> ...` 43 * 44 * To disable filtering, send the command without any packages, or explicitly reset: 45 * 46 * `$ adb shell cmd statusbar notif-filter reset` 47 * 48 * NOTE: this feature only works on debug builds, and when the broadcaster is root. 49 */ 50 @SysUISingleton 51 class DebugModeFilterProvider @Inject constructor( 52 private val commandRegistry: CommandRegistry, 53 dumpManager: DumpManager 54 ) : Dumpable { 55 private var allowedPackages: List<String> = emptyList() 56 private val listeners = ListenerSet<Runnable>() 57 58 init { 59 dumpManager.registerDumpable(this) 60 } 61 62 /** 63 * Register a runnable to be invoked when the allowed packages changes, which would mean the 64 * result of [shouldFilterOut] may have changed for some entries. 65 */ 66 fun registerInvalidationListener(listener: Runnable) { 67 Assert.isMainThread() 68 if (!Build.isDebuggable()) { 69 return 70 } 71 val needsInitialization = listeners.isEmpty() 72 listeners.addIfAbsent(listener) 73 if (needsInitialization) { 74 commandRegistry.registerCommand("notif-filter") { NotifFilterCommand() } 75 Log.d(TAG, "Registered notif-filter command") 76 } 77 } 78 79 /** 80 * Determine if the given entry should be hidden from the user in debug mode. 81 * Will always return false in release. 82 */ 83 fun shouldFilterOut(entry: NotificationEntry): Boolean { 84 if (allowedPackages.isEmpty()) { 85 return false 86 } 87 return entry.sbn.packageName !in allowedPackages 88 } 89 90 override fun dump(pw: PrintWriter, args: Array<out String>) { 91 pw.println("initialized: ${listeners.isNotEmpty()}") 92 pw.println("allowedPackages: ${allowedPackages.size}") 93 allowedPackages.forEachIndexed { i, pkg -> 94 pw.println(" [$i]: $pkg") 95 } 96 } 97 98 companion object { 99 private const val TAG = "DebugModeFilterProvider" 100 } 101 102 inner class NotifFilterCommand : Command { 103 override fun execute(pw: PrintWriter, args: List<String>) { 104 when (args.firstOrNull()) { 105 "reset" -> { 106 if (args.size > 1) { 107 return invalidCommand(pw, "Unexpected arguments for 'reset' command") 108 } 109 allowedPackages = emptyList() 110 } 111 "allowed-pkgs" -> { 112 allowedPackages = args.drop(1) 113 } 114 null -> return invalidCommand(pw, "Missing command") 115 else -> return invalidCommand(pw, "Unknown command: ${args.firstOrNull()}") 116 } 117 Log.d(TAG, "Updated allowedPackages: $allowedPackages") 118 if (allowedPackages.isEmpty()) { 119 pw.print("Resetting allowedPackages ... ") 120 } else { 121 pw.print("Updating allowedPackages: $allowedPackages ... ") 122 } 123 listeners.forEach(Runnable::run) 124 pw.println("DONE") 125 } 126 127 private fun invalidCommand(pw: PrintWriter, reason: String) { 128 pw.println("Error: $reason") 129 pw.println() 130 help(pw) 131 } 132 133 override fun help(pw: PrintWriter) { 134 pw.println("Usage: adb shell cmd statusbar notif-filter <command>") 135 pw.println("Available commands:") 136 pw.println(" reset") 137 pw.println(" Restore the default system behavior.") 138 pw.println(" allowed-pkgs <package> ...") 139 pw.println(" Hide all notification except from packages listed here.") 140 pw.println(" Providing no packages is treated as a reset.") 141 } 142 } 143 } 144