• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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()