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()