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.Log
20 import com.android.systemui.log.core.LogLevel
21 import java.util.StringJoiner
22 
23 /**
24  * Encodes/decodes the list of tags/buffers that [LogcatEchoTrackerDebug] echoes to logcat to/from a
25  * string format (that can be stored in a permanent place like a setting).
26  */
27 class LogcatEchoSettingFormat {
parseOverridesnull28     fun parseOverrides(str: String): List<LogcatEchoOverride> {
29         // The format begins with a schema version specifier formatted as "<number>;", followed by
30         // the encoded data.
31 
32         // First, read the schema version:
33         val split = str.split(";", limit = 2)
34         if (split.size != 2) {
35             Log.e(TAG, "Unrecognized echo override format: \"$str\"")
36             return emptyList()
37         }
38         val formatVersion =
39             try {
40                 split[0].toInt()
41             } catch (e: NumberFormatException) {
42                 Log.e(TAG, "Unrecognized echo override formation version: ${split[0]}")
43                 return emptyList()
44             }
45 
46         // Then, dispatch to the appropriate parser based on format
47         return when (formatVersion) {
48             0 -> parseOverridesV0(split[1])
49             else -> {
50                 Log.e(TAG, "Unrecognized echo override formation version: $formatVersion")
51                 emptyList()
52             }
53         }
54     }
55 
stringifyOverridesnull56     fun stringifyOverrides(
57         overrides: List<LogcatEchoOverride>,
58     ): String {
59         return stringifyOverridesV0(overrides)
60     }
61 
parseOverridesV0null62     private fun parseOverridesV0(
63         str: String,
64     ): List<LogcatEchoOverride> {
65         // Format: <type>;<name>;<level>(;...)
66         // Where
67         // <type> = "b" | "t"
68         // <name> = string
69         // <level> = "v" | "d" | "i" | "w" | "e" | "!"
70 
71         val list = mutableListOf<LogcatEchoOverride>()
72 
73         // Split on any ";" that is not preceded by a "\"
74         val pieces = str.split(Regex("""(?<!\\);"""))
75 
76         var i = 0
77         while (i < pieces.size) {
78             if (pieces.size - i < 3) {
79                 break
80             }
81             val type =
82                 when (pieces[i]) {
83                     "b" -> EchoOverrideType.BUFFER
84                     "t" -> EchoOverrideType.TAG
85                     else -> break
86                 }
87             val name = pieces[i + 1].replace("\\;", ";")
88             val level =
89                 when (pieces[i + 2]) {
90                     "v" -> LogLevel.VERBOSE
91                     "d" -> LogLevel.DEBUG
92                     "i" -> LogLevel.INFO
93                     "w" -> LogLevel.WARNING
94                     "e" -> LogLevel.ERROR
95                     "!" -> LogLevel.WTF
96                     else -> break
97                 }
98             i += 3
99 
100             list.add(LogcatEchoOverride(type, name, level))
101         }
102 
103         return list
104     }
105 
stringifyOverridesV0null106     private fun stringifyOverridesV0(
107         overrides: List<LogcatEchoOverride>,
108     ): String {
109         val sj = StringJoiner(";")
110 
111         sj.add("0")
112 
113         for (override in overrides) {
114             sj.add(
115                 when (override.type) {
116                     EchoOverrideType.BUFFER -> "b"
117                     EchoOverrideType.TAG -> "t"
118                 }
119             )
120             sj.add(override.name.replace(";", "\\;"))
121             sj.add(
122                 when (override.level) {
123                     LogLevel.VERBOSE -> "v"
124                     LogLevel.DEBUG -> "d"
125                     LogLevel.INFO -> "i"
126                     LogLevel.WARNING -> "w"
127                     LogLevel.ERROR -> "e"
128                     LogLevel.WTF -> "!"
129                 }
130             )
131         }
132 
133         return sj.toString()
134     }
135 }
136 
137 data class LogcatEchoOverride(val type: EchoOverrideType, val name: String, val level: LogLevel)
138 
139 private const val TAG = "EchoFormat"
140