1 /*
2  * Copyright (C) 2020 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.dump
18 
19 import android.util.ArrayMap
20 import com.android.systemui.Dumpable
21 import com.android.systemui.log.LogBuffer
22 import java.io.FileDescriptor
23 import java.io.PrintWriter
24 import javax.inject.Inject
25 import javax.inject.Singleton
26 
27 /**
28  * Maintains a registry of things that should be dumped when a bug report is taken
29  *
30  * When a bug report is taken, SystemUI dumps various diagnostic information that we hope will be
31  * useful for the eventual readers of the bug report. Code that wishes to participate in this dump
32  * should register itself here.
33  *
34  * See [DumpHandler] for more information on how and when this information is dumped.
35  */
36 @Singleton
37 open class DumpManager @Inject constructor() {
38     private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
39     private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
40 
41     /**
42      * Register a dumpable to be called during a bug report. The dumpable will be called during the
43      * CRITICAL section of the bug report, so don't dump an excessive amount of stuff here.
44      *
45      * @param name The name to register the dumpable under. This is typically the qualified class
46      * name of the thing being dumped (getClass().getName()), but can be anything as long as it
47      * doesn't clash with an existing registration.
48      */
49     @Synchronized
50     fun registerDumpable(name: String, module: Dumpable) {
51         if (!canAssignToNameLocked(name, module)) {
52             throw IllegalArgumentException("'$name' is already registered")
53         }
54 
55         dumpables[name] = RegisteredDumpable(name, module)
56     }
57 
58     /**
59      * Same as the above override, but automatically uses the simple class name as the dumpable
60      * name.
61      */
62     @Synchronized
63     fun registerDumpable(module: Dumpable) {
64         registerDumpable(module::class.java.simpleName, module)
65     }
66 
67     /**
68      * Unregisters a previously-registered dumpable.
69      */
70     @Synchronized
71     fun unregisterDumpable(name: String) {
72         dumpables.remove(name)
73     }
74 
75     /**
76      * Register a [LogBuffer] to be dumped during a bug report.
77      */
78     @Synchronized
79     fun registerBuffer(name: String, buffer: LogBuffer) {
80         if (!canAssignToNameLocked(name, buffer)) {
81             throw IllegalArgumentException("'$name' is already registered")
82         }
83         buffers[name] = RegisteredDumpable(name, buffer)
84     }
85 
86     /**
87      * Dumps the first dumpable or buffer whose registered name ends with [target]
88      */
89     @Synchronized
90     fun dumpTarget(
91         target: String,
92         fd: FileDescriptor,
93         pw: PrintWriter,
94         args: Array<String>,
95         tailLength: Int
96     ) {
97         for (dumpable in dumpables.values) {
98             if (dumpable.name.endsWith(target)) {
99                 dumpDumpable(dumpable, fd, pw, args)
100                 return
101             }
102         }
103 
104         for (buffer in buffers.values) {
105             if (buffer.name.endsWith(target)) {
106                 dumpBuffer(buffer, pw, tailLength)
107                 return
108             }
109         }
110     }
111 
112     /**
113      * Dumps all registered dumpables to [pw]
114      */
115     @Synchronized
116     fun dumpDumpables(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
117         for (module in dumpables.values) {
118             dumpDumpable(module, fd, pw, args)
119         }
120     }
121 
122     /**
123      * Dumps the names of all registered dumpables (one per line)
124      */
125     @Synchronized
126     fun listDumpables(pw: PrintWriter) {
127         for (module in dumpables.values) {
128             pw.println(module.name)
129         }
130     }
131 
132     /**
133      * Dumps all registered [LogBuffer]s to [pw]
134      */
135     @Synchronized
136     fun dumpBuffers(pw: PrintWriter, tailLength: Int) {
137         for (buffer in buffers.values) {
138             dumpBuffer(buffer, pw, tailLength)
139         }
140     }
141 
142     /**
143      * Dumps the names of all registered buffers (one per line)
144      */
145     @Synchronized
146     fun listBuffers(pw: PrintWriter) {
147         for (buffer in buffers.values) {
148             pw.println(buffer.name)
149         }
150     }
151 
152     @Synchronized
153     fun freezeBuffers() {
154         for (buffer in buffers.values) {
155             buffer.dumpable.freeze()
156         }
157     }
158 
159     @Synchronized
160     fun unfreezeBuffers() {
161         for (buffer in buffers.values) {
162             buffer.dumpable.unfreeze()
163         }
164     }
165 
166     private fun dumpDumpable(
167         dumpable: RegisteredDumpable<Dumpable>,
168         fd: FileDescriptor,
169         pw: PrintWriter,
170         args: Array<String>
171     ) {
172         pw.println()
173         pw.println("${dumpable.name}:")
174         pw.println("----------------------------------------------------------------------------")
175         dumpable.dumpable.dump(fd, pw, args)
176     }
177 
178     private fun dumpBuffer(
179         buffer: RegisteredDumpable<LogBuffer>,
180         pw: PrintWriter,
181         tailLength: Int
182     ) {
183         pw.println()
184         pw.println()
185         pw.println("BUFFER ${buffer.name}:")
186         pw.println("============================================================================")
187         buffer.dumpable.dump(pw, tailLength)
188     }
189 
190     private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
191         val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
192         return existingDumpable == null || newDumpable == existingDumpable
193     }
194 }
195 
196 private data class RegisteredDumpable<T>(
197     val name: String,
198     val dumpable: T
199 )
200 
201 private const val TAG = "DumpManager"