1 /*
2  * Copyright (C) 2019 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.wm.shell.sysui;
18 
19 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
20 
21 import com.android.internal.protolog.common.ProtoLog;
22 
23 import java.io.PrintWriter;
24 import java.util.Arrays;
25 import java.util.TreeMap;
26 import java.util.function.BiConsumer;
27 
28 /**
29  * An entry point into the shell for dumping shell internal state and running adb commands.
30  *
31  * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
32  */
33 public final class ShellCommandHandler {
34     private static final String TAG = ShellCommandHandler.class.getSimpleName();
35 
36     // We're using a TreeMap to keep them sorted by command name
37     private final TreeMap<String, BiConsumer<PrintWriter, String>> mDumpables = new TreeMap<>();
38     private final TreeMap<String, ShellCommandActionHandler> mCommands = new TreeMap<>();
39 
40     public interface ShellCommandActionHandler {
41         /**
42          * Handles the given command.
43          *
44          * @param args the arguments starting with the action name, then the action arguments
45          * @param pw the write to print output to
46          */
onShellCommand(String[] args, PrintWriter pw)47         boolean onShellCommand(String[] args, PrintWriter pw);
48 
49         /**
50          * Prints the help for this class of commands.  Implementations do not need to print the
51          * command class.
52          */
printShellCommandHelp(PrintWriter pw, String prefix)53         void printShellCommandHelp(PrintWriter pw, String prefix);
54     }
55 
56 
57     /**
58      * Adds a callback to run when the Shell is being dumped.
59      *
60      * @param callback the callback to be made when Shell is dumped, takes the print writer and
61      *                 a prefix
62      * @param instance used for debugging only
63      */
addDumpCallback(BiConsumer<PrintWriter, String> callback, T instance)64     public <T> void addDumpCallback(BiConsumer<PrintWriter, String> callback, T instance) {
65         mDumpables.put(instance.getClass().getSimpleName(), callback);
66         ProtoLog.v(WM_SHELL_INIT, "Adding dump callback for %s",
67                 instance.getClass().getSimpleName());
68     }
69 
70     /**
71      * Adds an action callback to be invoked when the user runs that particular command from adb.
72      *
73      * @param commandClass the top level class of command to invoke
74      * @param actions the interface to callback when an action of this class is invoked
75      * @param instance used for debugging only
76      */
addCommandCallback(String commandClass, ShellCommandActionHandler actions, T instance)77     public <T> void addCommandCallback(String commandClass, ShellCommandActionHandler actions,
78             T instance) {
79         mCommands.put(commandClass, actions);
80         ProtoLog.v(WM_SHELL_INIT, "Adding command class %s for %s", commandClass,
81                 instance.getClass().getSimpleName());
82     }
83 
84     /** Dumps WM Shell internal state. */
dump(PrintWriter pw)85     public void dump(PrintWriter pw) {
86         for (String key : mDumpables.keySet()) {
87             final BiConsumer<PrintWriter, String> r = mDumpables.get(key);
88             r.accept(pw, "");
89             pw.println();
90         }
91     }
92 
93 
94     /** Returns {@code true} if command was found and executed. */
handleCommand(final String[] args, PrintWriter pw)95     public boolean handleCommand(final String[] args, PrintWriter pw) {
96         if (args.length < 2) {
97             // Argument at position 0 is "WMShell".
98             return false;
99         }
100 
101         final String cmdClass = args[1];
102         if (cmdClass.toLowerCase().equals("help")) {
103             return runHelp(pw);
104         }
105         if (!mCommands.containsKey(cmdClass)) {
106             return false;
107         }
108 
109         // Only pass the actions onwards as arguments to the callback
110         final ShellCommandActionHandler actions = mCommands.get(args[1]);
111         final String[] cmdClassArgs = Arrays.copyOfRange(args, 2, args.length);
112         actions.onShellCommand(cmdClassArgs, pw);
113         return true;
114     }
115 
runHelp(PrintWriter pw)116     private boolean runHelp(PrintWriter pw) {
117         pw.println("Window Manager Shell commands:");
118         for (String commandClass : mCommands.keySet()) {
119             pw.println("  " + commandClass);
120             mCommands.get(commandClass).printShellCommandHelp(pw, "    ");
121         }
122         pw.println("  help");
123         pw.println("      Print this help text.");
124         pw.println("  <no arguments provided>");
125         pw.println("    Dump all Window Manager Shell internal state");
126         return true;
127     }
128 }
129