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