1 /* 2 * Copyright (C) 2020 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.commandline 18 19 import android.content.Context 20 21 import com.android.systemui.Prefs 22 import com.android.systemui.dagger.SysUISingleton 23 import com.android.systemui.dagger.qualifiers.Main 24 25 import java.io.PrintWriter 26 import java.lang.IllegalStateException 27 import java.util.concurrent.Executor 28 import java.util.concurrent.FutureTask 29 30 import javax.inject.Inject 31 32 /** 33 * Registry / dispatcher for incoming shell commands. See [StatusBarManagerService] and 34 * [StatusBarShellCommand] for how things are set up. Commands come in here by way of the service 35 * like so: 36 * 37 * `adb shell cmd statusbar <command>` 38 * 39 * Where `cmd statusbar` send the shell command through to StatusBarManagerService, and 40 * <command> is either processed in system server, or sent through to IStatusBar (CommandQueue) 41 */ 42 @SysUISingleton 43 class CommandRegistry @Inject constructor( 44 val context: Context, 45 @Main val mainExecutor: Executor 46 ) { 47 // To keep the command line parser hermetic, create a new one for every shell command 48 private val commandMap = mutableMapOf<String, CommandWrapper>() 49 private var initialized = false 50 51 /** 52 * Register a [Command] for a given name. The name here is the top-level namespace for 53 * the registered command. A command could look like this for instance: 54 * 55 * `adb shell cmd statusbar notifications list` 56 * 57 * Where `notifications` is the command that signifies which receiver to send the remaining args 58 * to. 59 * 60 * @param command String name of the command to register. Currently does not support aliases 61 * @param receiverFactory Creates an instance of the receiver on every command 62 * @param executor Pass an executor to offload your `receive` to another thread 63 */ 64 @Synchronized registerCommandnull65 fun registerCommand( 66 name: String, 67 commandFactory: () -> Command, 68 executor: Executor 69 ) { 70 if (commandMap[name] != null) { 71 throw IllegalStateException("A command is already registered for ($name)") 72 } 73 commandMap[name] = CommandWrapper(commandFactory, executor) 74 } 75 76 /** 77 * Register a [Command] for a given name, to be executed on the main thread. 78 */ 79 @Synchronized registerCommandnull80 fun registerCommand(name: String, commandFactory: () -> Command) { 81 registerCommand(name, commandFactory, mainExecutor) 82 } 83 84 /** Unregister a receiver */ 85 @Synchronized unregisterCommandnull86 fun unregisterCommand(command: String) { 87 commandMap.remove(command) 88 } 89 initializeCommandsnull90 private fun initializeCommands() { 91 initialized = true 92 // TODO: Might want a dedicated place for commands without a home. Currently 93 // this is here because Prefs.java is just an interface 94 registerCommand("prefs") { PrefsCommand(context) } 95 } 96 97 /** 98 * Receive a shell command and dispatch to the appropriate [Command]. Blocks until finished. 99 */ onShellCommandnull100 fun onShellCommand(pw: PrintWriter, args: Array<String>) { 101 if (!initialized) initializeCommands() 102 103 if (args.isEmpty()) { 104 help(pw) 105 return 106 } 107 108 val commandName = args[0] 109 val wrapper = commandMap[commandName] 110 111 if (wrapper == null) { 112 help(pw) 113 return 114 } 115 116 // Create a new instance of the command 117 val command = wrapper.commandFactory() 118 119 // Wrap the receive command in a task so that we can wait for its completion 120 val task = FutureTask<Unit> { 121 command.execute(pw, args.drop(1)) 122 } 123 124 wrapper.executor.execute { 125 task.run() 126 } 127 128 // Wait for the future to complete 129 task.get() 130 } 131 helpnull132 private fun help(pw: PrintWriter) { 133 pw.println("Usage: adb shell cmd statusbar <command>") 134 pw.println(" known commands:") 135 for (k in commandMap.keys) { 136 pw.println(" $k") 137 } 138 } 139 } 140 141 private const val TAG = "CommandRegistry" 142 143 interface Command { executenull144 fun execute(pw: PrintWriter, args: List<String>) 145 fun help(pw: PrintWriter) 146 } 147 148 // Wrap commands in an executor package 149 private data class CommandWrapper(val commandFactory: () -> Command, val executor: Executor) 150 151 // Commands can go here for now, but they should move outside 152 153 private class PrefsCommand(val context: Context) : Command { 154 override fun help(pw: PrintWriter) { 155 pw.println("usage: prefs <command> [args]") 156 pw.println("Available commands:") 157 pw.println(" list-prefs") 158 pw.println(" set-pref <pref name> <value>") 159 } 160 161 override fun execute(pw: PrintWriter, args: List<String>) { 162 if (args.isEmpty()) { 163 help(pw) 164 return 165 } 166 167 val topLevel = args[0] 168 169 when (topLevel) { 170 "list-prefs" -> listPrefs(pw) 171 else -> help(pw) 172 } 173 } 174 175 private fun listPrefs(pw: PrintWriter) { 176 pw.println("Available keys:") 177 for (field in Prefs.Key::class.java.declaredFields) { 178 pw.print(" ") 179 pw.println(field.get(Prefs.Key::class.java)) 180 } 181 } 182 } 183