1 /* <lambda>null2 * Copyright (C) 2022 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.app.motiontool 18 19 import android.os.Process 20 import android.util.Log 21 import android.view.Choreographer 22 import android.view.View 23 import android.view.WindowManagerGlobal 24 import androidx.annotation.VisibleForTesting 25 import com.android.app.viewcapture.ViewCapture 26 import com.android.app.viewcapture.data.ExportedData 27 28 /** 29 * Singleton to manage motion tracing sessions. 30 * 31 * A motion tracing session captures motion-relevant data on a frame-by-frame basis for a given 32 * window, as long as the trace is running. 33 * 34 * To start a trace, use [beginTrace]. The returned handle must be used to terminate tracing and 35 * receive the data by calling [endTrace]. While the trace is active, data is buffered, however 36 * the buffer size is limited (@see [ViewCapture.mMemorySize]. Use [pollTrace] periodically to 37 * ensure no data is dropped. Both, [pollTrace] and [endTrace] only return data captured since the 38 * last call to either [beginTrace] or [endTrace]. 39 * 40 * NOTE: a running trace will incur some performance penalty. Only keep traces running while a user 41 * requested it. 42 * 43 * @see [DdmHandleMotionTool] 44 */ 45 class MotionToolManager private constructor(private val windowManagerGlobal: WindowManagerGlobal) { 46 private val viewCapture: ViewCapture = SimpleViewCapture() 47 48 companion object { 49 private const val TAG = "MotionToolManager" 50 51 private var INSTANCE: MotionToolManager? = null 52 53 @Synchronized 54 fun getInstance(windowManagerGlobal: WindowManagerGlobal): MotionToolManager { 55 return INSTANCE ?: MotionToolManager(windowManagerGlobal).also { INSTANCE = it } 56 } 57 } 58 59 private var traceIdCounter = 0 60 private val traces = mutableMapOf<Int, TraceMetadata>() 61 62 @Synchronized 63 fun hasWindow(windowId: WindowIdentifier): Boolean { 64 val rootView = getRootView(windowId.rootWindow) 65 return rootView != null 66 } 67 68 /** Starts [ViewCapture] and returns a traceId. */ 69 @Synchronized 70 fun beginTrace(windowId: String): Int { 71 val traceId = ++traceIdCounter 72 Log.d(TAG, "Begin Trace for id: $traceId") 73 val rootView = getRootView(windowId) ?: throw WindowNotFoundException(windowId) 74 val autoCloseable = viewCapture.startCapture(rootView, windowId) 75 traces[traceId] = TraceMetadata(windowId, 0, autoCloseable::close) 76 return traceId 77 } 78 79 /** 80 * Ends [ViewCapture] and returns the captured [ExportedData] since the [beginTrace] call or the 81 * last [pollTrace] call. 82 */ 83 @Synchronized 84 fun endTrace(traceId: Int): ExportedData { 85 Log.d(TAG, "End Trace for id: $traceId") 86 val traceMetadata = traces.getOrElse(traceId) { throw UnknownTraceIdException(traceId) } 87 val exportedData = pollTrace(traceId) 88 traceMetadata.stopTrace() 89 traces.remove(traceId) 90 return exportedData 91 } 92 93 /** 94 * Returns the [ExportedData] captured since the [beginTrace] call or the last [pollTrace] call. 95 * This function can only be used after [beginTrace] is called and before [endTrace] is called. 96 */ 97 @Synchronized 98 fun pollTrace(traceId: Int): ExportedData { 99 val traceMetadata = traces.getOrElse(traceId) { throw UnknownTraceIdException(traceId) } 100 val exportedData = getExportedDataFromViewCapture(traceMetadata) 101 traceMetadata.updateLastPolledTime(exportedData) 102 return exportedData 103 } 104 105 /** 106 * Stops and deletes all active [traces] and resets the [traceIdCounter]. 107 */ 108 @VisibleForTesting 109 @Synchronized 110 fun reset() { 111 for (traceMetadata in traces.values) { 112 traceMetadata.stopTrace() 113 } 114 traces.clear() 115 traceIdCounter = 0 116 } 117 118 private fun getExportedDataFromViewCapture(traceMetadata: TraceMetadata): ExportedData { 119 val rootView = 120 getRootView(traceMetadata.windowId) 121 ?: throw WindowNotFoundException(traceMetadata.windowId) 122 123 val exportedData = viewCapture 124 .getDumpTask(rootView) 125 ?.orElse(null) 126 ?.get() ?: return ExportedData.newBuilder().build() 127 128 val filteredFrameData = exportedData.frameDataList 129 ?.filter { it.timestamp > traceMetadata.lastPolledTime } 130 131 return exportedData.toBuilder() 132 .clearFrameData() 133 .addAllFrameData(filteredFrameData) 134 .build() 135 } 136 137 private fun getRootView(windowId: String): View? { 138 return windowManagerGlobal.getRootView(windowId) 139 } 140 141 class SimpleViewCapture : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, 142 MAIN_EXECUTOR.submit { Choreographer.getInstance() }.get(), 143 createAndStartNewLooperExecutor("MTViewCapture", Process.THREAD_PRIORITY_FOREGROUND)) 144 } 145 146 private data class TraceMetadata( 147 val windowId: String, 148 var lastPolledTime: Long, 149 var stopTrace: () -> Unit 150 ) { updateLastPolledTimenull151 fun updateLastPolledTime(exportedData: ExportedData?) { 152 exportedData?.frameDataList?.maxOfOrNull { it.timestamp }?.let { maxFrameTimestamp -> 153 lastPolledTime = maxFrameTimestamp 154 } 155 } 156 } 157 158 class UnknownTraceIdException(val traceId: Int) : Exception() 159 160 class WindowNotFoundException(val windowId: String) : Exception()