blob: a98a58814b1a304d1caeb71f926aba89d68ec4d7 [file] [edit]
/*
* Copyright (C) 2022 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.
*/
package com.android.app.motiontool
import android.os.Process
import android.util.Log
import android.view.Choreographer
import android.view.View
import android.view.WindowManagerGlobal
import androidx.annotation.VisibleForTesting
import com.android.app.viewcapture.SimpleViewCapture
import com.android.app.viewcapture.ViewCapture
import com.android.app.viewcapture.data.MotionWindowData
/**
* Singleton to manage motion tracing sessions.
*
* A motion tracing session captures motion-relevant data on a frame-by-frame basis for a given
* window, as long as the trace is running.
*
* To start a trace, use [beginTrace]. The returned handle must be used to terminate tracing and
* receive the data by calling [endTrace]. While the trace is active, data is buffered, however
* the buffer size is limited (@see [ViewCapture.mMemorySize]. Use [pollTrace] periodically to
* ensure no data is dropped. Both, [pollTrace] and [endTrace] only return data captured since the
* last call to either [beginTrace] or [endTrace].
*
* NOTE: a running trace will incur some performance penalty. Only keep traces running while a user
* requested it.
*
* @see [DdmHandleMotionTool]
*/
class MotionToolManager private constructor(private val windowManagerGlobal: WindowManagerGlobal) {
private val viewCapture: ViewCapture = SimpleViewCapture("MTViewCapture")
companion object {
private const val TAG = "MotionToolManager"
private var INSTANCE: MotionToolManager? = null
@Synchronized
fun getInstance(windowManagerGlobal: WindowManagerGlobal): MotionToolManager {
return INSTANCE ?: MotionToolManager(windowManagerGlobal).also { INSTANCE = it }
}
}
private var traceIdCounter = 0
private val traces = mutableMapOf<Int, TraceMetadata>()
@Synchronized
fun hasWindow(windowId: WindowIdentifier): Boolean {
val rootView = getRootView(windowId.rootWindow)
return rootView != null
}
/** Starts [ViewCapture] and returns a traceId. */
@Synchronized
fun beginTrace(windowId: String): Int {
val traceId = ++traceIdCounter
Log.d(TAG, "Begin Trace for id: $traceId")
val rootView = getRootView(windowId) ?: throw WindowNotFoundException(windowId)
val autoCloseable = viewCapture.startCapture(rootView, windowId)
traces[traceId] = TraceMetadata(windowId, 0, autoCloseable::close)
return traceId
}
/**
* Ends [ViewCapture] and returns the captured [MotionWindowData] since the [beginTrace] call or
* the last [pollTrace] call.
*/
@Synchronized
fun endTrace(traceId: Int): MotionWindowData {
Log.d(TAG, "End Trace for id: $traceId")
val traceMetadata = traces.getOrElse(traceId) { throw UnknownTraceIdException(traceId) }
val data = pollTrace(traceId)
traceMetadata.stopTrace()
traces.remove(traceId)
return data
}
/**
* Returns the [MotionWindowData] captured since the [beginTrace] call or last [pollTrace] call.
* This function can only be used after [beginTrace] is called and before [endTrace] is called.
*/
@Synchronized
fun pollTrace(traceId: Int): MotionWindowData {
val traceMetadata = traces.getOrElse(traceId) { throw UnknownTraceIdException(traceId) }
val data = getDataFromViewCapture(traceMetadata)
traceMetadata.updateLastPolledTime(data)
return data
}
/**
* Stops and deletes all active [traces] and resets the [traceIdCounter].
*/
@VisibleForTesting
@Synchronized
fun reset() {
for (traceMetadata in traces.values) {
traceMetadata.stopTrace()
}
traces.clear()
traceIdCounter = 0
}
private fun getDataFromViewCapture(traceMetadata: TraceMetadata): MotionWindowData {
val rootView =
getRootView(traceMetadata.windowId)
?: throw WindowNotFoundException(traceMetadata.windowId)
val data: MotionWindowData = viewCapture
.getDumpTask(rootView).get()
?.orElse(null) ?: return MotionWindowData.newBuilder().build()
val filteredFrameData = data.frameDataList.filter {
it.timestamp > traceMetadata.lastPolledTime
}
return data.toBuilder()
.clearFrameData()
.addAllFrameData(filteredFrameData)
.build()
}
private fun getRootView(windowId: String): View? {
return windowManagerGlobal.getRootView(windowId)
}
}
private data class TraceMetadata(
val windowId: String,
var lastPolledTime: Long,
var stopTrace: () -> Unit
) {
fun updateLastPolledTime(data: MotionWindowData?) {
data?.frameDataList?.maxOfOrNull { it.timestamp }?.let {
lastPolledTime = it
}
}
}
class UnknownTraceIdException(val traceId: Int) : Exception()
class WindowNotFoundException(val windowId: String) : Exception()