1 /*
2  * Copyright (C) 2023 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.log.echo
18 
19 import android.util.IndentingPrintWriter
20 import com.android.systemui.log.core.LogLevel
21 import com.android.systemui.log.echo.Outcome.Failure
22 import com.android.systemui.log.echo.Outcome.Success
23 import com.android.systemui.statusbar.commandline.ParseableCommand
24 import com.android.systemui.statusbar.commandline.Type
25 import java.io.PrintWriter
26 
27 /**
28  * Implementation of command-line interface for modifying echo tracking.
29  *
30  * Invoked via $adb shell cmd statusbar echo <usage>. See [usage] below for usage summary.
31  */
32 internal class LogcatEchoTrackerCommand(private val echoTracker: LogcatEchoTrackerDebug) :
33     ParseableCommand(ECHO_TRACKER_COMMAND_NAME) {
34 
35     val buffer by
36         param(
37             longName = "buffer",
38             shortName = "b",
39             description =
40                 "Modifies the echo level of a buffer. Use the form <name>:<level>, e.g." +
41                     " 'Foo:V'. Valid levels are V,D,I,W,E, and -. The - level clears any" +
42                     " pre-existing override.",
43             valueParser = Type.String,
44         )
45 
46     val tag by
47         param(
48             longName = "tag",
49             shortName = "t",
50             description =
51                 "Modifies the echo level of a tag. Use the form <name>:<level>, e.g." +
52                     " 'Foo:V'. Valid levels are V,D,I,W,E, and -. The - level clears any" +
53                     " pre-existing override.",
54             valueParser = Type.String
55         )
56 
57     val clearAll by
58         flag(
59             longName = "clear-all",
60             description = "Removes all local echo level overrides",
61         )
62 
63     val list by
64         flag(
65             longName = "list",
66             description = "Lists all local echo level overrides",
67         )
68 
usagenull69     override fun usage(pw: IndentingPrintWriter) {
70         pw.println("Usage:")
71         pw.println()
72         pw.println("echo -b MyBufferName:V    // Set echo level of a buffer to verbose")
73         pw.println("echo -t MyTagName:V       // Set echo level of a tag to verbose")
74         pw.println()
75         pw.println("echo -b MyBufferName:-    // Clear any echo overrides for a buffer")
76         pw.println("echo -t MyTagName:-       // Clear any echo overrides for a tag")
77         pw.println()
78         pw.println("echo --list               // List all current echo overrides")
79         pw.println("echo --clear-all          // Clear all echo overrides")
80         pw.println()
81     }
82 
executenull83     override fun execute(pw: PrintWriter) {
84         val buffer = buffer
85         val tag = tag
86 
87         when {
88             buffer != null -> {
89                 parseTagStructure(buffer, EchoOverrideType.BUFFER).ifFailureThenPrintElse(pw) {
90                     echoTracker.setEchoLevel(it.type, it.name, it.level)
91                 }
92             }
93             tag != null -> {
94                 parseTagStructure(tag, EchoOverrideType.TAG).ifFailureThenPrintElse(pw) {
95                     echoTracker.setEchoLevel(it.type, it.name, it.level)
96                 }
97             }
98             clearAll -> {
99                 echoTracker.clearAllOverrides()
100             }
101             list -> {
102                 for (override in echoTracker.listEchoOverrides()) {
103                     pw.print(override.type.toString().padEnd(8))
104                     pw.print(override.level.toString().padEnd(10))
105                     pw.print(override.name)
106                     pw.println()
107                 }
108             }
109             else -> {
110                 pw.println("You must specify one of --buffer, --tag, --list, or --clear-all")
111             }
112         }
113     }
114 
parseTagStructurenull115     private fun parseTagStructure(
116         str: String,
117         type: EchoOverrideType,
118     ): Outcome<ParsedOverride> {
119         val result =
120             OVERRIDE_PATTERN.matchEntire(str)
121                 ?: return Failure("Cannot parse override format, must be `<name>:<level>`")
122 
123         val name = result.groupValues[1]
124         val levelStr = result.groupValues[2]
125 
126         if (levelStr == "-") {
127             return Success(ParsedOverride(type, name, null))
128         } else {
129             val parsedLevel =
130                 parseLevel(levelStr)
131                     ?: return Failure("Unrecognized level $levelStr. Must be one of 'v,d,i,w,e,-'")
132             return Success(ParsedOverride(type, name, parsedLevel))
133         }
134     }
135 
parseLevelnull136     private fun parseLevel(str: String): LogLevel? {
137         return when (str.lowercase()) {
138             "verbose" -> LogLevel.VERBOSE
139             "v" -> LogLevel.VERBOSE
140             "debug" -> LogLevel.DEBUG
141             "d" -> LogLevel.DEBUG
142             "info" -> LogLevel.INFO
143             "i" -> LogLevel.INFO
144             "warning" -> LogLevel.WARNING
145             "warn" -> LogLevel.WARNING
146             "w" -> LogLevel.WARNING
147             "error" -> LogLevel.ERROR
148             "e" -> LogLevel.ERROR
149             "assert" -> LogLevel.WTF
150             "wtf" -> LogLevel.WTF
151             else -> null
152         }
153     }
154 
155     companion object {
156         const val ECHO_TRACKER_COMMAND_NAME = "echo"
157     }
158 }
159 
160 private val OVERRIDE_PATTERN = Regex("([^:]+):(.*)")
161 
162 private class ParsedOverride(val type: EchoOverrideType, val name: String, val level: LogLevel?)
163 
164 private sealed interface Outcome<out T> {
165     class Success<out T>(val value: T) : Outcome<T>
166     class Failure(val message: String) : Outcome<Nothing>
167 }
168 
ifFailureThenPrintElsenull169 private inline fun <T> Outcome<T>.ifFailureThenPrintElse(
170     pw: PrintWriter,
171     handler: (value: T) -> Unit,
172 ) {
173     when (this) {
174         is Success<T> -> handler(value)
175         is Failure -> pw.println(message)
176     }
177 }
178 
179 /*
180 TODO (b/310006154): Investigate using varargs instead of parameterized flags
181 
182 Current structure uses param flags, e.g.
183 
184 adb shell cmd statusbar echo -b MyBufferName:V
185 adb shell cmd statusbar echo -b MyBufferName:-
186 adb shell cmd statusbar echo -t MyTagName:V
187 adb shell cmd statusbar echo -t MyTagName:-
188 adb shell cmd statusbar echo --clear-all
189 adb shell cmd statusbar echo --list
190 
191 A better structure might use non-flag varargs like (but will require updates to the CLI lib):
192 
193 adb shell cmd statusbar echo buffer MyBufferName:V
194 adb shell cmd statusbar echo buffer MyBufferName:-
195 adb shell cmd statusbar echo tag MyTagName:V
196 adb shell cmd statusbar echo tag MyTagName:-
197 adb shell cmd statusbar echo clear-all
198 adb shell cmd statusbar echo list
199 
200 */
201