Loading core/java/com/android/internal/statusbar/IStatusBar.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -242,4 +242,10 @@ oneway interface IStatusBar * @param connect {@code true} if needs connection, otherwise set the connection to null. */ void requestWindowMagnificationConnection(boolean connect); /** * Allow for pass-through arguments from `adb shell cmd statusbar <args>`, and write to the * file descriptor passed in. */ void passThroughShellCommand(in String[] args, in ParcelFileDescriptor pfd); } packages/SystemUI/src/com/android/systemui/Prefs.java +3 −1 Original line number Diff line number Diff line Loading @@ -75,8 +75,10 @@ public final class Prefs { Key.HAS_SEEN_BUBBLES_EDUCATION, Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, Key.HAS_SEEN_REVERSE_BOTTOM_SHEET, Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT, Key.HAS_SEEN_PRIORITY_ONBOARDING }) // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them public @interface Key { @Deprecated String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime"; Loading packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +35 −3 Original line number Diff line number Diff line Loading @@ -58,12 +58,14 @@ import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.view.AppearanceRegion; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.tracing.ProtoTracer; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; /** * This class takes the functions from IStatusBar that come in on Loading Loading @@ -159,6 +161,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< */ private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; private ProtoTracer mProtoTracer; private final @Nullable CommandRegistry mRegistry; /** * These methods are called back on the main thread. Loading Loading @@ -368,11 +371,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } public CommandQueue(Context context) { this(context, null); this(context, null, null); } public CommandQueue(Context context, ProtoTracer protoTracer) { public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) { mProtoTracer = protoTracer; mRegistry = registry; context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler); // We always have default display. setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE); Loading Loading @@ -1013,6 +1017,34 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } @Override public void passThroughShellCommand(String[] args, ParcelFileDescriptor pfd) { final FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor()); final PrintWriter pw = new PrintWriter(fos); // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible // to just throw this work onto the handler just like the other messages Thread thr = new Thread("Sysui.passThroughShellCommand") { public void run() { try { if (mRegistry == null) { return; } // Registry blocks this thread until finished mRegistry.onShellCommand(pw, args); } finally { pw.flush(); try { // Close the file descriptor so the TransferPipe finishes its thread pfd.close(); } catch (Exception e) { } } } }; thr.start(); } private final class H extends Handler { private H(Looper l) { super(l); Loading packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt 0 → 100644 +204 −0 Original line number Diff line number Diff line /* * 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) "set-pref" -> setPref(pw, args.drop(1)) 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)) } } /** * Sets a preference from [Prefs] */ private fun setPref(pw: PrintWriter, args: List<String>) { if (args.isEmpty()) { pw.println("invalid arguments: $args") return } val pref = args[0] when (pref) { Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING -> { val value = Integer.parseInt(args[1]) Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, value != 0) } else -> { pw.println("Cannot set pref ($pref)") } } } } packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +6 −2 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.DynamicChildBindController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; Loading Loading @@ -192,8 +193,11 @@ public interface StatusBarDependenciesModule { */ @Provides @SysUISingleton static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) { return new CommandQueue(context, protoTracer); static CommandQueue provideCommandQueue( Context context, ProtoTracer protoTracer, CommandRegistry registry) { return new CommandQueue(context, protoTracer, registry); } /** Loading Loading
core/java/com/android/internal/statusbar/IStatusBar.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -242,4 +242,10 @@ oneway interface IStatusBar * @param connect {@code true} if needs connection, otherwise set the connection to null. */ void requestWindowMagnificationConnection(boolean connect); /** * Allow for pass-through arguments from `adb shell cmd statusbar <args>`, and write to the * file descriptor passed in. */ void passThroughShellCommand(in String[] args, in ParcelFileDescriptor pfd); }
packages/SystemUI/src/com/android/systemui/Prefs.java +3 −1 Original line number Diff line number Diff line Loading @@ -75,8 +75,10 @@ public final class Prefs { Key.HAS_SEEN_BUBBLES_EDUCATION, Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, Key.HAS_SEEN_REVERSE_BOTTOM_SHEET, Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT, Key.HAS_SEEN_PRIORITY_ONBOARDING }) // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them public @interface Key { @Deprecated String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime"; Loading
packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +35 −3 Original line number Diff line number Diff line Loading @@ -58,12 +58,14 @@ import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.view.AppearanceRegion; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.tracing.ProtoTracer; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; /** * This class takes the functions from IStatusBar that come in on Loading Loading @@ -159,6 +161,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< */ private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; private ProtoTracer mProtoTracer; private final @Nullable CommandRegistry mRegistry; /** * These methods are called back on the main thread. Loading Loading @@ -368,11 +371,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } public CommandQueue(Context context) { this(context, null); this(context, null, null); } public CommandQueue(Context context, ProtoTracer protoTracer) { public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) { mProtoTracer = protoTracer; mRegistry = registry; context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler); // We always have default display. setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE); Loading Loading @@ -1013,6 +1017,34 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } @Override public void passThroughShellCommand(String[] args, ParcelFileDescriptor pfd) { final FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor()); final PrintWriter pw = new PrintWriter(fos); // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible // to just throw this work onto the handler just like the other messages Thread thr = new Thread("Sysui.passThroughShellCommand") { public void run() { try { if (mRegistry == null) { return; } // Registry blocks this thread until finished mRegistry.onShellCommand(pw, args); } finally { pw.flush(); try { // Close the file descriptor so the TransferPipe finishes its thread pfd.close(); } catch (Exception e) { } } } }; thr.start(); } private final class H extends Handler { private H(Looper l) { super(l); Loading
packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt 0 → 100644 +204 −0 Original line number Diff line number Diff line /* * 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) "set-pref" -> setPref(pw, args.drop(1)) 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)) } } /** * Sets a preference from [Prefs] */ private fun setPref(pw: PrintWriter, args: List<String>) { if (args.isEmpty()) { pw.println("invalid arguments: $args") return } val pref = args[0] when (pref) { Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING -> { val value = Integer.parseInt(args[1]) Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, value != 0) } else -> { pw.println("Cannot set pref ($pref)") } } } }
packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +6 −2 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.DynamicChildBindController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; Loading Loading @@ -192,8 +193,11 @@ public interface StatusBarDependenciesModule { */ @Provides @SysUISingleton static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) { return new CommandQueue(context, protoTracer); static CommandQueue provideCommandQueue( Context context, ProtoTracer protoTracer, CommandRegistry registry) { return new CommandQueue(context, protoTracer, registry); } /** Loading