• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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