blob: 1ad6ef5a4440ca58a842b2f8cdfd1cb964332bae [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:JvmName("Utils")
package android.tools.device.traces
import android.app.UiAutomation
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.tools.common.Logger
import android.tools.common.MILLISECOND_AS_NANOSECONDS
import android.tools.common.io.TraceType
import android.tools.common.traces.DeviceStateDump
import android.tools.common.traces.NullableDeviceStateDump
import android.tools.common.traces.surfaceflinger.LayerTraceEntry
import android.tools.common.traces.wm.WindowManagerState
import android.tools.device.traces.parsers.DeviceDumpParser
import androidx.test.platform.app.InstrumentationRegistry
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
fun formatRealTimestamp(timestampNs: Long): String {
val timestampMs = timestampNs / MILLISECOND_AS_NANOSECONDS
val remainderNs = timestampNs % MILLISECOND_AS_NANOSECONDS
val date = Date(timestampMs)
val timeFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ENGLISH)
timeFormatter.timeZone = TimeZone.getTimeZone("UTC")
return "${timeFormatter.format(date)}${remainderNs.toString().padStart(6, '0')}"
}
fun executeShellCommand(cmd: String): ByteArray {
val uiAutomation: UiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
val fileDescriptor = uiAutomation.executeShellCommand(cmd)
ParcelFileDescriptor.AutoCloseInputStream(fileDescriptor).use { inputStream ->
return inputStream.readBytes()
}
}
private fun doBinderDump(name: String): ByteArray {
// create an fd for the binder transaction
val pipe = ParcelFileDescriptor.createPipe()
val source = pipe[0]
val sink = pipe[1]
// ServiceManager isn't accessible from tests, so use reflection
// this should return an IBinder
val service =
Class.forName("android.os.ServiceManager")
.getMethod("getServiceOrThrow", String::class.java)
.invoke(null, name) as IBinder?
// this is equal to ServiceManager::PROTO_ARG
val args = arrayOf("--proto")
service?.dump(sink.fileDescriptor, args)
sink.close()
// convert the FD into a ByteArray
ParcelFileDescriptor.AutoCloseInputStream(source).use { inputStream ->
return inputStream.readBytes()
}
}
private fun getCurrentWindowManagerState() = doBinderDump("window")
private fun getCurrentLayersState() = doBinderDump("SurfaceFlinger")
/**
* Gets the current device state dump containing the [WindowManagerState] (optional) and the
* [LayerTraceEntry] (optional) in raw (byte) data.
*
* @param dumpTypes Flags determining which types of traces should be included in the dump
*/
fun getCurrentState(
vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP)
): Pair<ByteArray, ByteArray> {
if (dumpTypes.isEmpty()) {
throw IllegalArgumentException("No dump specified")
}
val traceTypes = dumpTypes.filter { it.isTrace }
if (traceTypes.isNotEmpty()) {
throw IllegalArgumentException("Only dump types are supported. Invalid types: $traceTypes")
}
Logger.d(LOG_TAG, "Requesting new device state dump")
val wmTraceData =
if (dumpTypes.contains(TraceType.WM_DUMP)) {
getCurrentWindowManagerState()
} else {
ByteArray(0)
}
val layersTraceData =
if (dumpTypes.contains(TraceType.SF_DUMP)) {
getCurrentLayersState()
} else {
ByteArray(0)
}
return Pair(wmTraceData, layersTraceData)
}
/**
* Gets the current device state dump containing the [WindowManagerState] (optional) and the
* [LayerTraceEntry] (optional) parsed
*
* @param dumpTypes Flags determining which types of traces should be included in the dump
* @param clearCacheAfterParsing If the caching used while parsing the proto should be
*
* ```
* cleared or remain in memory
* ```
*/
@JvmOverloads
fun getCurrentStateDumpNullable(
vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
clearCacheAfterParsing: Boolean = true
): NullableDeviceStateDump {
val currentStateDump = getCurrentState(*dumpTypes)
return DeviceDumpParser.fromNullableDump(
currentStateDump.first,
currentStateDump.second,
clearCacheAfterParsing = clearCacheAfterParsing
)
}
@JvmOverloads
fun getCurrentStateDump(
vararg dumpTypes: TraceType = arrayOf(TraceType.SF_DUMP, TraceType.WM_DUMP),
clearCacheAfterParsing: Boolean = true
): DeviceStateDump {
val currentStateDump = getCurrentState(*dumpTypes)
return DeviceDumpParser.fromDump(
currentStateDump.first,
currentStateDump.second,
clearCacheAfterParsing = clearCacheAfterParsing
)
}