1 /*
<lambda>null2  * Copyright (C) 2024 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 package com.android.app.viewcapture
17 
18 import android.content.Context
19 import android.internal.perfetto.protos.InternedDataOuterClass.InternedData
20 import android.internal.perfetto.protos.ProfileCommon.InternedString
21 import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket
22 import android.internal.perfetto.protos.Viewcapture.ViewCapture as ViewCaptureMessage
23 import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl
24 import android.os.Trace
25 import android.tracing.perfetto.DataSourceParams
26 import android.tracing.perfetto.InitArguments
27 import android.tracing.perfetto.Producer
28 import android.util.proto.ProtoOutputStream
29 import androidx.annotation.WorkerThread
30 import java.util.concurrent.Executor
31 import java.util.concurrent.atomic.AtomicInteger
32 
33 /**
34  * ViewCapture that listens to Perfetto events (OnStart, OnStop, OnFlush) and continuously writes
35  * captured frames to the Perfetto service (traced).
36  */
37 internal class PerfettoViewCapture
38 internal constructor(private val context: Context, executor: Executor) :
39     ViewCapture(RING_BUFFER_SIZE, DEFAULT_INIT_POOL_SIZE, executor) {
40 
41     private val mDataSource = ViewCaptureDataSource({ onStart() }, {}, { onStop() })
42 
43     private val mActiveSessions = AtomicInteger(0)
44 
45     private val mViewIdProvider = ViewIdProvider(context.getResources())
46 
47     private var mSerializationCurrentId: Int = 0
48     private var mSerializationCurrentView: ViewPropertyRef? = null
49 
50     inner class NewInternedStrings {
51         val packageNames = mutableListOf<String>()
52         val windowNames = mutableListOf<String>()
53         val viewIds = mutableListOf<String>()
54         val classNames = mutableListOf<String>()
55     }
56 
57     init {
58         enableOrDisableWindowListeners(false)
59 
60         Producer.init(InitArguments.DEFAULTS)
61 
62         val dataSourceParams =
63             DataSourceParams.Builder()
64                 .setBufferExhaustedPolicy(
65                     DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT
66                 )
67                 .setNoFlush(true)
68                 .setWillNotifyOnStop(false)
69                 .build()
70         mDataSource.register(dataSourceParams)
71     }
72 
73     fun onStart() {
74         if (mActiveSessions.incrementAndGet() == 1) {
75             enableOrDisableWindowListeners(true)
76         }
77     }
78 
79     fun onStop() {
80         if (mActiveSessions.decrementAndGet() == 0) {
81             enableOrDisableWindowListeners(false)
82         }
83     }
84 
85     @WorkerThread
86     override fun onCapturedViewPropertiesBg(
87         elapsedRealtimeNanos: Long,
88         windowName: String,
89         startFlattenedTree: ViewPropertyRef
90     ) {
91         Trace.beginSection("vc#onCapturedViewPropertiesBg")
92 
93         mDataSource.trace { ctx ->
94             val newInternedStrings = NewInternedStrings()
95             val os = ctx.newTracePacket()
96             os.write(TracePacket.TIMESTAMP, elapsedRealtimeNanos)
97             serializeViews(
98                 os,
99                 windowName,
100                 startFlattenedTree,
101                 ctx.incrementalState,
102                 newInternedStrings
103             )
104             serializeIncrementalState(os, ctx.incrementalState, newInternedStrings)
105         }
106 
107         Trace.endSection()
108     }
109 
110     private fun serializeViews(
111         os: ProtoOutputStream,
112         windowName: String,
113         startFlattenedTree: ViewPropertyRef,
114         incrementalState: ViewCaptureDataSource.IncrementalState,
115         newInternedStrings: NewInternedStrings
116     ) {
117         mSerializationCurrentView = startFlattenedTree
118         mSerializationCurrentId = 0
119 
120         val tokenExtensions = os.start(TracePacket.WINSCOPE_EXTENSIONS)
121         val tokenViewCapture = os.start(WinscopeExtensionsImpl.VIEWCAPTURE)
122         os.write(
123             ViewCaptureMessage.PACKAGE_NAME_IID,
124             internPackageName(context.packageName, incrementalState, newInternedStrings)
125         )
126         os.write(
127             ViewCaptureMessage.WINDOW_NAME_IID,
128             internWindowName(windowName, incrementalState, newInternedStrings)
129         )
130         serializeViewsRec(os, -1, incrementalState, newInternedStrings)
131         os.end(tokenViewCapture)
132         os.end(tokenExtensions)
133     }
134 
135     private fun serializeViewsRec(
136         os: ProtoOutputStream,
137         parentId: Int,
138         incrementalState: ViewCaptureDataSource.IncrementalState,
139         newInternedStrings: NewInternedStrings
140     ) {
141         if (mSerializationCurrentView == null) {
142             return
143         }
144 
145         val id = mSerializationCurrentId
146         val childCount = mSerializationCurrentView!!.childCount
147 
148         serializeView(
149             os,
150             mSerializationCurrentView!!,
151             mSerializationCurrentId,
152             parentId,
153             incrementalState,
154             newInternedStrings
155         )
156 
157         ++mSerializationCurrentId
158         mSerializationCurrentView = mSerializationCurrentView!!.next
159 
160         for (i in 0..childCount - 1) {
161             serializeViewsRec(os, id, incrementalState, newInternedStrings)
162         }
163     }
164 
165     private fun serializeView(
166         os: ProtoOutputStream,
167         view: ViewPropertyRef,
168         id: Int,
169         parentId: Int,
170         incrementalState: ViewCaptureDataSource.IncrementalState,
171         newInternedStrings: NewInternedStrings
172     ) {
173         val token = os.start(ViewCaptureMessage.VIEWS)
174 
175         os.write(ViewCaptureMessage.View.ID, id)
176         os.write(ViewCaptureMessage.View.PARENT_ID, parentId)
177         os.write(ViewCaptureMessage.View.HASHCODE, view.hashCode)
178         os.write(
179             ViewCaptureMessage.View.VIEW_ID_IID,
180             internViewId(mViewIdProvider.getName(view.id), incrementalState, newInternedStrings)
181         )
182         os.write(
183             ViewCaptureMessage.View.CLASS_NAME_IID,
184             internClassName(view.clazz.name, incrementalState, newInternedStrings)
185         )
186 
187         os.write(ViewCaptureMessage.View.LEFT, view.left)
188         os.write(ViewCaptureMessage.View.TOP, view.top)
189         os.write(ViewCaptureMessage.View.WIDTH, view.right - view.left)
190         os.write(ViewCaptureMessage.View.HEIGHT, view.bottom - view.top)
191         os.write(ViewCaptureMessage.View.SCROLL_X, view.scrollX)
192         os.write(ViewCaptureMessage.View.SCROLL_Y, view.scrollY)
193 
194         os.write(ViewCaptureMessage.View.TRANSLATION_X, view.translateX)
195         os.write(ViewCaptureMessage.View.TRANSLATION_Y, view.translateY)
196         os.write(ViewCaptureMessage.View.SCALE_X, view.scaleX)
197         os.write(ViewCaptureMessage.View.SCALE_Y, view.scaleY)
198         os.write(ViewCaptureMessage.View.ALPHA, view.alpha)
199 
200         os.write(ViewCaptureMessage.View.WILL_NOT_DRAW, view.willNotDraw)
201         os.write(ViewCaptureMessage.View.CLIP_CHILDREN, view.clipChildren)
202         os.write(ViewCaptureMessage.View.VISIBILITY, view.visibility)
203 
204         os.write(ViewCaptureMessage.View.ELEVATION, view.elevation)
205 
206         os.end(token)
207     }
208 
209     private fun internClassName(
210         string: String,
211         incrementalState: ViewCaptureDataSource.IncrementalState,
212         newInternedStrings: NewInternedStrings
213     ): Int {
214         return internString(
215             string,
216             incrementalState.mInternMapClassName,
217             newInternedStrings.classNames
218         )
219     }
220 
221     private fun internPackageName(
222         string: String,
223         incrementalState: ViewCaptureDataSource.IncrementalState,
224         newInternedStrings: NewInternedStrings
225     ): Int {
226         return internString(
227             string,
228             incrementalState.mInternMapPackageName,
229             newInternedStrings.packageNames
230         )
231     }
232 
233     private fun internViewId(
234         string: String,
235         incrementalState: ViewCaptureDataSource.IncrementalState,
236         newInternedStrings: NewInternedStrings
237     ): Int {
238         return internString(string, incrementalState.mInternMapViewId, newInternedStrings.viewIds)
239     }
240 
241     private fun internWindowName(
242         string: String,
243         incrementalState: ViewCaptureDataSource.IncrementalState,
244         newInternedStrings: NewInternedStrings
245     ): Int {
246         return internString(
247             string,
248             incrementalState.mInternMapWindowName,
249             newInternedStrings.windowNames
250         )
251     }
252 
253     private fun internString(
254         string: String,
255         internMap: MutableMap<String, Int>,
256         newInternedStrings: MutableList<String>
257     ): Int {
258         if (internMap.containsKey(string)) {
259             return internMap[string]!!
260         }
261 
262         // +1 to avoid intern ID = 0, because javastream optimizes out zero values
263         // and the perfetto trace processor would not de-intern that string.
264         val internId = internMap.size + 1
265 
266         internMap.put(string, internId)
267         newInternedStrings.add(string)
268         return internId
269     }
270 
271     private fun serializeIncrementalState(
272         os: ProtoOutputStream,
273         incrementalState: ViewCaptureDataSource.IncrementalState,
274         newInternedStrings: NewInternedStrings
275     ) {
276         var flags = TracePacket.SEQ_NEEDS_INCREMENTAL_STATE
277         if (!incrementalState.mHasNotifiedClearedState) {
278             flags = flags or TracePacket.SEQ_INCREMENTAL_STATE_CLEARED
279             incrementalState.mHasNotifiedClearedState = true
280         }
281         os.write(TracePacket.SEQUENCE_FLAGS, flags)
282 
283         val token = os.start(TracePacket.INTERNED_DATA)
284         serializeInternMap(
285             os,
286             InternedData.VIEWCAPTURE_CLASS_NAME,
287             incrementalState.mInternMapClassName,
288             newInternedStrings.classNames
289         )
290         serializeInternMap(
291             os,
292             InternedData.VIEWCAPTURE_PACKAGE_NAME,
293             incrementalState.mInternMapPackageName,
294             newInternedStrings.packageNames
295         )
296         serializeInternMap(
297             os,
298             InternedData.VIEWCAPTURE_VIEW_ID,
299             incrementalState.mInternMapViewId,
300             newInternedStrings.viewIds
301         )
302         serializeInternMap(
303             os,
304             InternedData.VIEWCAPTURE_WINDOW_NAME,
305             incrementalState.mInternMapWindowName,
306             newInternedStrings.windowNames
307         )
308         os.end(token)
309     }
310 
311     private fun serializeInternMap(
312         os: ProtoOutputStream,
313         fieldId: Long,
314         map: Map<String, Int>,
315         newInternedStrings: List<String>
316     ) {
317         if (newInternedStrings.isEmpty()) {
318             return
319         }
320 
321         var currentInternId = map.size - newInternedStrings.size + 1
322         for (internedString in newInternedStrings) {
323             val token = os.start(fieldId)
324             os.write(InternedString.IID, currentInternId++)
325             os.write(InternedString.STR, internedString.toByteArray())
326             os.end(token)
327         }
328     }
329 
330     companion object {
331         // Keep two frames in the base class' ring buffer.
332         // This is the minimum required by the current implementation to work.
333         private val RING_BUFFER_SIZE = 2
334     }
335 }
336