1 /*
<lambda>null2 * Copyright (C) 2024 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 @file:JvmName("Utils")
18
19 package android.tools.traces
20
21 import android.app.UiAutomation
22 import android.os.IBinder
23 import android.os.ParcelFileDescriptor
24 import android.tools.MILLISECOND_AS_NANOSECONDS
25 import android.tools.io.TraceType
26 import android.tools.traces.monitors.PerfettoTraceMonitor
27 import android.tools.traces.parsers.DeviceDumpParser
28 import android.tools.traces.surfaceflinger.LayerTraceEntry
29 import android.tools.traces.wm.WindowManagerState
30 import android.util.Log
31 import androidx.test.platform.app.InstrumentationRegistry
32 import java.text.SimpleDateFormat
33 import java.util.Date
34 import java.util.Locale
35 import java.util.TimeZone
36
37 fun formatRealTimestamp(timestampNs: Long): String {
38 val timestampMs = timestampNs / MILLISECOND_AS_NANOSECONDS
39 val remainderNs = timestampNs % MILLISECOND_AS_NANOSECONDS
40 val date = Date(timestampMs)
41
42 val timeFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ENGLISH)
43 timeFormatter.timeZone = TimeZone.getTimeZone("UTC")
44
45 return "${timeFormatter.format(date)}${remainderNs.toString().padStart(6, '0')}"
46 }
47
executeShellCommandnull48 fun executeShellCommand(cmd: String): ByteArray {
49 Log.d(LOG_TAG, "Executing shell command $cmd")
50 val uiAutomation: UiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
51 val fileDescriptor = uiAutomation.executeShellCommand(cmd)
52 ParcelFileDescriptor.AutoCloseInputStream(fileDescriptor).use { inputStream ->
53 return inputStream.readBytes()
54 }
55 }
56
doBinderDumpnull57 private fun doBinderDump(name: String): ByteArray {
58 // create an fd for the binder transaction
59 val pipe = ParcelFileDescriptor.createPipe()
60 val source = pipe[0]
61 val sink = pipe[1]
62
63 // ServiceManager isn't accessible from tests, so use reflection
64 // this should return an IBinder
65 val service =
66 Class.forName("android.os.ServiceManager")
67 .getMethod("getServiceOrThrow", String::class.java)
68 .invoke(null, name) as IBinder?
69
70 // this is equal to ServiceManager::PROTO_ARG
71 val args = arrayOf("--proto")
72 service?.dump(sink.fileDescriptor, args)
73 sink.close()
74
75 // convert the FD into a ByteArray
76 ParcelFileDescriptor.AutoCloseInputStream(source).use { inputStream ->
77 return inputStream.readBytes()
78 }
79 }
80
getCurrentWindowManagerStatenull81 private fun getCurrentWindowManagerState() = doBinderDump("window")
82
83 /**
84 * Gets the current device state dump containing the [WindowManagerState] (optional) and the
85 * [LayerTraceEntry] (optional) in raw (byte) data.
86 *
87 * @param dumpTypes Flags determining which types of traces should be included in the dump
88 */
89 fun getCurrentState(
90 vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP)
91 ): Pair<ByteArray, ByteArray> {
92 if (dumpTypes.isEmpty()) {
93 throw IllegalArgumentException("No dump specified")
94 }
95
96 val traceTypes = dumpTypes.filter { it.isTrace }
97 if (traceTypes.isNotEmpty()) {
98 throw IllegalArgumentException("Only dump types are supported. Invalid types: $traceTypes")
99 }
100
101 Log.d(LOG_TAG, "Requesting new device state dump")
102 val wmTraceData =
103 if (dumpTypes.contains(TraceType.WM_DUMP)) {
104 getCurrentWindowManagerState()
105 } else {
106 ByteArray(0)
107 }
108 val layersTraceData =
109 if (dumpTypes.contains(TraceType.SF_DUMP)) {
110 PerfettoTraceMonitor.newBuilder().enableLayersDump().build().withTracing {}
111 } else {
112 ByteArray(0)
113 }
114
115 return Pair(wmTraceData, layersTraceData)
116 }
117
118 /**
119 * Gets the current device state dump containing the [WindowManagerState] (optional) and the
120 * [LayerTraceEntry] (optional) parsed
121 *
122 * @param dumpTypes Flags determining which types of traces should be included in the dump
123 * @param clearCacheAfterParsing If the caching used while parsing the proto should be
124 *
125 * ```
126 * cleared or remain in memory
127 * ```
128 */
129 @JvmOverloads
getCurrentStateDumpNullablenull130 fun getCurrentStateDumpNullable(
131 vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
132 clearCacheAfterParsing: Boolean = true
133 ): NullableDeviceStateDump {
134 val currentStateDump = getCurrentState(*dumpTypes)
135 return DeviceDumpParser.fromNullableDump(
136 currentStateDump.first,
137 currentStateDump.second,
138 clearCacheAfterParsing = clearCacheAfterParsing
139 )
140 }
141
142 @JvmOverloads
getCurrentStateDumpnull143 fun getCurrentStateDump(
144 vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
145 clearCacheAfterParsing: Boolean = true
146 ): DeviceStateDump {
147 val currentStateDump = getCurrentState(*dumpTypes)
148 return DeviceDumpParser.fromDump(
149 currentStateDump.first,
150 currentStateDump.second,
151 clearCacheAfterParsing = clearCacheAfterParsing
152 )
153 }
154