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