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