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