Loading motiontoollib/Android.bp +2 −2 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ android_library { name: "motion_tool_lib", manifest: "AndroidManifest.xml", platform_apis: true, min_sdk_version: "26", min_sdk_version: "30", static_libs: [ "androidx.core_core", Loading @@ -58,7 +58,7 @@ android_test { name: "motion_tool_lib_tests", manifest: "tests/AndroidManifest.xml", platform_apis: true, min_sdk_version: "26", min_sdk_version: "30", static_libs: [ "androidx.core_core", Loading viewcapturelib/Android.bp +3 −2 Original line number Diff line number Diff line Loading @@ -34,11 +34,12 @@ android_library { name: "view_capture", manifest: "AndroidManifest.xml", platform_apis: true, min_sdk_version: "26", min_sdk_version: "30", static_libs: [ "androidx.core_core", "view_capture_proto", "perfetto_trace_javastream_protos_jarjar", ], srcs: [ Loading @@ -51,7 +52,7 @@ android_test { name: "view_capture_tests", manifest: "tests/AndroidManifest.xml", platform_apis: true, min_sdk_version: "26", min_sdk_version: "30", static_libs: [ "androidx.core_core", Loading viewcapturelib/src/com/android/app/viewcapture/PerfettoViewCapture.kt 0 → 100644 +325 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.app.viewcapture import android.content.Context import android.internal.perfetto.protos.InternedDataOuterClass.InternedData import android.internal.perfetto.protos.ProfileCommon.InternedString import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket import android.internal.perfetto.protos.Viewcapture.ViewCapture as ViewCaptureMessage import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl import android.os.Trace import android.tracing.perfetto.DataSourceParams import android.tracing.perfetto.InitArguments import android.tracing.perfetto.Producer import android.util.proto.ProtoOutputStream import androidx.annotation.WorkerThread import java.util.concurrent.Executor import java.util.concurrent.atomic.AtomicInteger /** * ViewCapture that listens to Perfetto events (OnStart, OnStop, OnFlush) and continuously writes * captured frames to the Perfetto service (traced). */ internal class PerfettoViewCapture internal constructor(private val context: Context, executor: Executor) : ViewCapture(RING_BUFFER_SIZE, DEFAULT_INIT_POOL_SIZE, executor) { private val mDataSource = ViewCaptureDataSource( { mActiveSessions.incrementAndGet() }, {}, { mActiveSessions.decrementAndGet() } ) private val mActiveSessions = AtomicInteger(0) private val mViewIdProvider = ViewIdProvider(context.getResources()) private var mSerializationCurrentId: Int = 0 private var mSerializationCurrentView: ViewPropertyRef? = null inner class NewInternedStrings { val packageNames = mutableListOf<String>() val windowNames = mutableListOf<String>() val viewIds = mutableListOf<String>() val classNames = mutableListOf<String>() } init { Producer.init(InitArguments.DEFAULTS) val params = DataSourceParams.Builder() .setBufferExhaustedPolicy( DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT ) .setNoFlush(true) .setWillNotifyOnStop(false) .build() mDataSource.register(params) } @WorkerThread override fun onCapturedViewPropertiesBg( elapsedRealtimeNanos: Long, windowName: String, startFlattenedTree: ViewPropertyRef ) { Trace.beginSection("vc#onCapturedViewPropertiesBg") mDataSource.trace { ctx -> val newInternedStrings = NewInternedStrings() val os = ctx.newTracePacket() os.write(TracePacket.TIMESTAMP, elapsedRealtimeNanos) serializeViews( os, windowName, startFlattenedTree, ctx.incrementalState, newInternedStrings ) serializeIncrementalState(os, ctx.incrementalState, newInternedStrings) } Trace.endSection() } private fun serializeViews( os: ProtoOutputStream, windowName: String, startFlattenedTree: ViewPropertyRef, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ) { mSerializationCurrentView = startFlattenedTree mSerializationCurrentId = 0 val tokenExtensions = os.start(TracePacket.WINSCOPE_EXTENSIONS) val tokenViewCapture = os.start(WinscopeExtensionsImpl.VIEWCAPTURE) os.write( ViewCaptureMessage.PACKAGE_NAME_IID, internPackageName(context.packageName, incrementalState, newInternedStrings) ) os.write( ViewCaptureMessage.WINDOW_NAME_IID, internWindowName(windowName, incrementalState, newInternedStrings) ) serializeViewsRec(os, -1, incrementalState, newInternedStrings) os.end(tokenViewCapture) os.end(tokenExtensions) } private fun serializeViewsRec( os: ProtoOutputStream, parentId: Int, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ) { if (mSerializationCurrentView == null) { return } val id = mSerializationCurrentId val childCount = mSerializationCurrentView!!.childCount serializeView( os, mSerializationCurrentView!!, mSerializationCurrentId, parentId, incrementalState, newInternedStrings ) ++mSerializationCurrentId mSerializationCurrentView = mSerializationCurrentView!!.next for (i in 0..childCount - 1) { serializeViewsRec(os, id, incrementalState, newInternedStrings) } } private fun serializeView( os: ProtoOutputStream, view: ViewPropertyRef, id: Int, parentId: Int, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ) { val token = os.start(ViewCaptureMessage.VIEWS) os.write(ViewCaptureMessage.View.ID, id) os.write(ViewCaptureMessage.View.PARENT_ID, parentId) os.write(ViewCaptureMessage.View.HASHCODE, view.hashCode) os.write( ViewCaptureMessage.View.VIEW_ID_IID, internViewId(mViewIdProvider.getName(view.id), incrementalState, newInternedStrings) ) os.write( ViewCaptureMessage.View.CLASS_NAME_IID, internClassName(view.clazz.name, incrementalState, newInternedStrings) ) os.write(ViewCaptureMessage.View.LEFT, view.left) os.write(ViewCaptureMessage.View.TOP, view.top) os.write(ViewCaptureMessage.View.WIDTH, view.right - view.left) os.write(ViewCaptureMessage.View.HEIGHT, view.bottom - view.top) os.write(ViewCaptureMessage.View.SCROLL_X, view.scrollX) os.write(ViewCaptureMessage.View.SCROLL_Y, view.scrollY) os.write(ViewCaptureMessage.View.TRANSLATION_X, view.translateX) os.write(ViewCaptureMessage.View.TRANSLATION_Y, view.translateY) os.write(ViewCaptureMessage.View.SCALE_X, view.scaleX) os.write(ViewCaptureMessage.View.SCALE_Y, view.scaleY) os.write(ViewCaptureMessage.View.ALPHA, view.alpha) os.write(ViewCaptureMessage.View.WILL_NOT_DRAW, view.willNotDraw) os.write(ViewCaptureMessage.View.CLIP_CHILDREN, view.clipChildren) os.write(ViewCaptureMessage.View.VISIBILITY, view.visibility) os.write(ViewCaptureMessage.View.ELEVATION, view.elevation) os.end(token) } private fun internClassName( string: String, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ): Int { return internString( string, incrementalState.mInternMapClassName, newInternedStrings.classNames ) } private fun internPackageName( string: String, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ): Int { return internString( string, incrementalState.mInternMapPackageName, newInternedStrings.packageNames ) } private fun internViewId( string: String, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ): Int { return internString(string, incrementalState.mInternMapViewId, newInternedStrings.viewIds) } private fun internWindowName( string: String, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ): Int { return internString( string, incrementalState.mInternMapWindowName, newInternedStrings.windowNames ) } private fun internString( string: String, internMap: MutableMap<String, Int>, newInternedStrings: MutableList<String> ): Int { if (internMap.containsKey(string)) { return internMap[string]!! } // +1 to avoid intern ID = 0, because javastream optimizes out zero values // and the perfetto trace processor would not de-intern that string. val internId = internMap.size + 1 internMap.put(string, internId) newInternedStrings.add(string) return internId } private fun serializeIncrementalState( os: ProtoOutputStream, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ) { var flags = TracePacket.SEQ_NEEDS_INCREMENTAL_STATE if (!incrementalState.mHasNotifiedClearedState) { flags = flags or TracePacket.SEQ_INCREMENTAL_STATE_CLEARED incrementalState.mHasNotifiedClearedState = true } os.write(TracePacket.SEQUENCE_FLAGS, flags) val token = os.start(TracePacket.INTERNED_DATA) serializeInternMap( os, InternedData.VIEWCAPTURE_CLASS_NAME, incrementalState.mInternMapClassName, newInternedStrings.classNames ) serializeInternMap( os, InternedData.VIEWCAPTURE_PACKAGE_NAME, incrementalState.mInternMapPackageName, newInternedStrings.packageNames ) serializeInternMap( os, InternedData.VIEWCAPTURE_VIEW_ID, incrementalState.mInternMapViewId, newInternedStrings.viewIds ) serializeInternMap( os, InternedData.VIEWCAPTURE_WINDOW_NAME, incrementalState.mInternMapWindowName, newInternedStrings.windowNames ) os.end(token) } private fun serializeInternMap( os: ProtoOutputStream, fieldId: Long, map: Map<String, Int>, newInternedStrings: List<String> ) { if (newInternedStrings.isEmpty()) { return } var currentInternId = map.size - newInternedStrings.size + 1 for (internedString in newInternedStrings) { val token = os.start(fieldId) os.write(InternedString.IID, currentInternId++) os.write(InternedString.STR, internedString.toByteArray()) os.end(token) } } companion object { // Keep two frames in the base class' ring buffer. // This is the minimum required by the current implementation to work. private val RING_BUFFER_SIZE = 2 } } viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java +13 −3 Original line number Diff line number Diff line Loading @@ -229,6 +229,10 @@ public abstract class ViewCapture { mBgExecutor); } @WorkerThread protected void onCapturedViewPropertiesBg(long elapsedRealtimeNanos, String windowName, ViewPropertyRef startFlattenedViewTree) { } /** * Once this window listener is attached to a window's root view, it traverses the entire Loading Loading @@ -302,7 +306,7 @@ public abstract class ViewCapture { */ @Override public void onDraw() { Trace.beginSection("view_capture"); Trace.beginSection("vc#onDraw"); captureViewTree(mRoot, mViewRef); ViewRef captured = mViewRef.next; if (captured != null) { Loading @@ -320,6 +324,8 @@ public abstract class ViewCapture { */ @WorkerThread private void captureViewPropertiesBg(ViewRef viewRefStart) { Trace.beginSection("vc#captureViewPropertiesBg"); long elapsedRealtimeNanos = viewRefStart.elapsedRealtimeNanos; mFrameIndexBg++; if (mFrameIndexBg >= mMemorySize) { Loading Loading @@ -388,6 +394,10 @@ public abstract class ViewCapture { viewRefEnd = viewRefEnd.next; } mNodesBg[mFrameIndexBg] = resultStart; onCapturedViewPropertiesBg(elapsedRealtimeNanos, name, resultStart); Trace.endSection(); } private @Nullable ViewPropertyRef findInLastFrame(int hashCode) { Loading Loading @@ -508,7 +518,7 @@ public abstract class ViewCapture { } } private static class ViewPropertyRef { protected static class ViewPropertyRef { // We store reference in memory to avoid generating and storing too many strings public Class clazz; public int hashCode; Loading Loading @@ -639,7 +649,7 @@ public abstract class ViewCapture { } } private static final class ViewIdProvider { protected static final class ViewIdProvider { private final SparseArray<String> mNames = new SparseArray<>(); private final Resources mRes; Loading viewcapturelib/src/com/android/app/viewcapture/ViewCaptureDataSource.java 0 → 100644 +78 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.app.viewcapture; import android.tracing.perfetto.CreateIncrementalStateArgs; import android.tracing.perfetto.DataSource; import android.tracing.perfetto.DataSourceInstance; import android.tracing.perfetto.FlushCallbackArguments; import android.tracing.perfetto.StartCallbackArguments; import android.tracing.perfetto.StopCallbackArguments; import android.util.proto.ProtoInputStream; import java.util.HashMap; import java.util.Map; class ViewCaptureDataSource extends DataSource<DataSourceInstance, Void, ViewCaptureDataSource.IncrementalState> { public static String DATA_SOURCE_NAME = "android.viewcapture"; private final Runnable mOnStartStaticCallback; private final Runnable mOnFlushStaticCallback; private final Runnable mOnStopStaticCallback; ViewCaptureDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) { super(DATA_SOURCE_NAME); this.mOnStartStaticCallback = onStart; this.mOnFlushStaticCallback = onFlush; this.mOnStopStaticCallback = onStop; } @Override public IncrementalState createIncrementalState( CreateIncrementalStateArgs<DataSourceInstance> args) { return new IncrementalState(); } public static class IncrementalState { public final Map<String, Integer> mInternMapPackageName = new HashMap<>(); public final Map<String, Integer> mInternMapWindowName = new HashMap<>(); public final Map<String, Integer> mInternMapViewId = new HashMap<>(); public final Map<String, Integer> mInternMapClassName = new HashMap<>(); public boolean mHasNotifiedClearedState = false; } @Override public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) { return new DataSourceInstance(this, instanceIndex) { @Override protected void onStart(StartCallbackArguments args) { mOnStartStaticCallback.run(); } @Override protected void onFlush(FlushCallbackArguments args) { mOnFlushStaticCallback.run(); } @Override protected void onStop(StopCallbackArguments args) { mOnStopStaticCallback.run(); } }; } } Loading
motiontoollib/Android.bp +2 −2 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ android_library { name: "motion_tool_lib", manifest: "AndroidManifest.xml", platform_apis: true, min_sdk_version: "26", min_sdk_version: "30", static_libs: [ "androidx.core_core", Loading @@ -58,7 +58,7 @@ android_test { name: "motion_tool_lib_tests", manifest: "tests/AndroidManifest.xml", platform_apis: true, min_sdk_version: "26", min_sdk_version: "30", static_libs: [ "androidx.core_core", Loading
viewcapturelib/Android.bp +3 −2 Original line number Diff line number Diff line Loading @@ -34,11 +34,12 @@ android_library { name: "view_capture", manifest: "AndroidManifest.xml", platform_apis: true, min_sdk_version: "26", min_sdk_version: "30", static_libs: [ "androidx.core_core", "view_capture_proto", "perfetto_trace_javastream_protos_jarjar", ], srcs: [ Loading @@ -51,7 +52,7 @@ android_test { name: "view_capture_tests", manifest: "tests/AndroidManifest.xml", platform_apis: true, min_sdk_version: "26", min_sdk_version: "30", static_libs: [ "androidx.core_core", Loading
viewcapturelib/src/com/android/app/viewcapture/PerfettoViewCapture.kt 0 → 100644 +325 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.app.viewcapture import android.content.Context import android.internal.perfetto.protos.InternedDataOuterClass.InternedData import android.internal.perfetto.protos.ProfileCommon.InternedString import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket import android.internal.perfetto.protos.Viewcapture.ViewCapture as ViewCaptureMessage import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl import android.os.Trace import android.tracing.perfetto.DataSourceParams import android.tracing.perfetto.InitArguments import android.tracing.perfetto.Producer import android.util.proto.ProtoOutputStream import androidx.annotation.WorkerThread import java.util.concurrent.Executor import java.util.concurrent.atomic.AtomicInteger /** * ViewCapture that listens to Perfetto events (OnStart, OnStop, OnFlush) and continuously writes * captured frames to the Perfetto service (traced). */ internal class PerfettoViewCapture internal constructor(private val context: Context, executor: Executor) : ViewCapture(RING_BUFFER_SIZE, DEFAULT_INIT_POOL_SIZE, executor) { private val mDataSource = ViewCaptureDataSource( { mActiveSessions.incrementAndGet() }, {}, { mActiveSessions.decrementAndGet() } ) private val mActiveSessions = AtomicInteger(0) private val mViewIdProvider = ViewIdProvider(context.getResources()) private var mSerializationCurrentId: Int = 0 private var mSerializationCurrentView: ViewPropertyRef? = null inner class NewInternedStrings { val packageNames = mutableListOf<String>() val windowNames = mutableListOf<String>() val viewIds = mutableListOf<String>() val classNames = mutableListOf<String>() } init { Producer.init(InitArguments.DEFAULTS) val params = DataSourceParams.Builder() .setBufferExhaustedPolicy( DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT ) .setNoFlush(true) .setWillNotifyOnStop(false) .build() mDataSource.register(params) } @WorkerThread override fun onCapturedViewPropertiesBg( elapsedRealtimeNanos: Long, windowName: String, startFlattenedTree: ViewPropertyRef ) { Trace.beginSection("vc#onCapturedViewPropertiesBg") mDataSource.trace { ctx -> val newInternedStrings = NewInternedStrings() val os = ctx.newTracePacket() os.write(TracePacket.TIMESTAMP, elapsedRealtimeNanos) serializeViews( os, windowName, startFlattenedTree, ctx.incrementalState, newInternedStrings ) serializeIncrementalState(os, ctx.incrementalState, newInternedStrings) } Trace.endSection() } private fun serializeViews( os: ProtoOutputStream, windowName: String, startFlattenedTree: ViewPropertyRef, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ) { mSerializationCurrentView = startFlattenedTree mSerializationCurrentId = 0 val tokenExtensions = os.start(TracePacket.WINSCOPE_EXTENSIONS) val tokenViewCapture = os.start(WinscopeExtensionsImpl.VIEWCAPTURE) os.write( ViewCaptureMessage.PACKAGE_NAME_IID, internPackageName(context.packageName, incrementalState, newInternedStrings) ) os.write( ViewCaptureMessage.WINDOW_NAME_IID, internWindowName(windowName, incrementalState, newInternedStrings) ) serializeViewsRec(os, -1, incrementalState, newInternedStrings) os.end(tokenViewCapture) os.end(tokenExtensions) } private fun serializeViewsRec( os: ProtoOutputStream, parentId: Int, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ) { if (mSerializationCurrentView == null) { return } val id = mSerializationCurrentId val childCount = mSerializationCurrentView!!.childCount serializeView( os, mSerializationCurrentView!!, mSerializationCurrentId, parentId, incrementalState, newInternedStrings ) ++mSerializationCurrentId mSerializationCurrentView = mSerializationCurrentView!!.next for (i in 0..childCount - 1) { serializeViewsRec(os, id, incrementalState, newInternedStrings) } } private fun serializeView( os: ProtoOutputStream, view: ViewPropertyRef, id: Int, parentId: Int, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ) { val token = os.start(ViewCaptureMessage.VIEWS) os.write(ViewCaptureMessage.View.ID, id) os.write(ViewCaptureMessage.View.PARENT_ID, parentId) os.write(ViewCaptureMessage.View.HASHCODE, view.hashCode) os.write( ViewCaptureMessage.View.VIEW_ID_IID, internViewId(mViewIdProvider.getName(view.id), incrementalState, newInternedStrings) ) os.write( ViewCaptureMessage.View.CLASS_NAME_IID, internClassName(view.clazz.name, incrementalState, newInternedStrings) ) os.write(ViewCaptureMessage.View.LEFT, view.left) os.write(ViewCaptureMessage.View.TOP, view.top) os.write(ViewCaptureMessage.View.WIDTH, view.right - view.left) os.write(ViewCaptureMessage.View.HEIGHT, view.bottom - view.top) os.write(ViewCaptureMessage.View.SCROLL_X, view.scrollX) os.write(ViewCaptureMessage.View.SCROLL_Y, view.scrollY) os.write(ViewCaptureMessage.View.TRANSLATION_X, view.translateX) os.write(ViewCaptureMessage.View.TRANSLATION_Y, view.translateY) os.write(ViewCaptureMessage.View.SCALE_X, view.scaleX) os.write(ViewCaptureMessage.View.SCALE_Y, view.scaleY) os.write(ViewCaptureMessage.View.ALPHA, view.alpha) os.write(ViewCaptureMessage.View.WILL_NOT_DRAW, view.willNotDraw) os.write(ViewCaptureMessage.View.CLIP_CHILDREN, view.clipChildren) os.write(ViewCaptureMessage.View.VISIBILITY, view.visibility) os.write(ViewCaptureMessage.View.ELEVATION, view.elevation) os.end(token) } private fun internClassName( string: String, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ): Int { return internString( string, incrementalState.mInternMapClassName, newInternedStrings.classNames ) } private fun internPackageName( string: String, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ): Int { return internString( string, incrementalState.mInternMapPackageName, newInternedStrings.packageNames ) } private fun internViewId( string: String, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ): Int { return internString(string, incrementalState.mInternMapViewId, newInternedStrings.viewIds) } private fun internWindowName( string: String, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ): Int { return internString( string, incrementalState.mInternMapWindowName, newInternedStrings.windowNames ) } private fun internString( string: String, internMap: MutableMap<String, Int>, newInternedStrings: MutableList<String> ): Int { if (internMap.containsKey(string)) { return internMap[string]!! } // +1 to avoid intern ID = 0, because javastream optimizes out zero values // and the perfetto trace processor would not de-intern that string. val internId = internMap.size + 1 internMap.put(string, internId) newInternedStrings.add(string) return internId } private fun serializeIncrementalState( os: ProtoOutputStream, incrementalState: ViewCaptureDataSource.IncrementalState, newInternedStrings: NewInternedStrings ) { var flags = TracePacket.SEQ_NEEDS_INCREMENTAL_STATE if (!incrementalState.mHasNotifiedClearedState) { flags = flags or TracePacket.SEQ_INCREMENTAL_STATE_CLEARED incrementalState.mHasNotifiedClearedState = true } os.write(TracePacket.SEQUENCE_FLAGS, flags) val token = os.start(TracePacket.INTERNED_DATA) serializeInternMap( os, InternedData.VIEWCAPTURE_CLASS_NAME, incrementalState.mInternMapClassName, newInternedStrings.classNames ) serializeInternMap( os, InternedData.VIEWCAPTURE_PACKAGE_NAME, incrementalState.mInternMapPackageName, newInternedStrings.packageNames ) serializeInternMap( os, InternedData.VIEWCAPTURE_VIEW_ID, incrementalState.mInternMapViewId, newInternedStrings.viewIds ) serializeInternMap( os, InternedData.VIEWCAPTURE_WINDOW_NAME, incrementalState.mInternMapWindowName, newInternedStrings.windowNames ) os.end(token) } private fun serializeInternMap( os: ProtoOutputStream, fieldId: Long, map: Map<String, Int>, newInternedStrings: List<String> ) { if (newInternedStrings.isEmpty()) { return } var currentInternId = map.size - newInternedStrings.size + 1 for (internedString in newInternedStrings) { val token = os.start(fieldId) os.write(InternedString.IID, currentInternId++) os.write(InternedString.STR, internedString.toByteArray()) os.end(token) } } companion object { // Keep two frames in the base class' ring buffer. // This is the minimum required by the current implementation to work. private val RING_BUFFER_SIZE = 2 } }
viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java +13 −3 Original line number Diff line number Diff line Loading @@ -229,6 +229,10 @@ public abstract class ViewCapture { mBgExecutor); } @WorkerThread protected void onCapturedViewPropertiesBg(long elapsedRealtimeNanos, String windowName, ViewPropertyRef startFlattenedViewTree) { } /** * Once this window listener is attached to a window's root view, it traverses the entire Loading Loading @@ -302,7 +306,7 @@ public abstract class ViewCapture { */ @Override public void onDraw() { Trace.beginSection("view_capture"); Trace.beginSection("vc#onDraw"); captureViewTree(mRoot, mViewRef); ViewRef captured = mViewRef.next; if (captured != null) { Loading @@ -320,6 +324,8 @@ public abstract class ViewCapture { */ @WorkerThread private void captureViewPropertiesBg(ViewRef viewRefStart) { Trace.beginSection("vc#captureViewPropertiesBg"); long elapsedRealtimeNanos = viewRefStart.elapsedRealtimeNanos; mFrameIndexBg++; if (mFrameIndexBg >= mMemorySize) { Loading Loading @@ -388,6 +394,10 @@ public abstract class ViewCapture { viewRefEnd = viewRefEnd.next; } mNodesBg[mFrameIndexBg] = resultStart; onCapturedViewPropertiesBg(elapsedRealtimeNanos, name, resultStart); Trace.endSection(); } private @Nullable ViewPropertyRef findInLastFrame(int hashCode) { Loading Loading @@ -508,7 +518,7 @@ public abstract class ViewCapture { } } private static class ViewPropertyRef { protected static class ViewPropertyRef { // We store reference in memory to avoid generating and storing too many strings public Class clazz; public int hashCode; Loading Loading @@ -639,7 +649,7 @@ public abstract class ViewCapture { } } private static final class ViewIdProvider { protected static final class ViewIdProvider { private final SparseArray<String> mNames = new SparseArray<>(); private final Resources mRes; Loading
viewcapturelib/src/com/android/app/viewcapture/ViewCaptureDataSource.java 0 → 100644 +78 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.app.viewcapture; import android.tracing.perfetto.CreateIncrementalStateArgs; import android.tracing.perfetto.DataSource; import android.tracing.perfetto.DataSourceInstance; import android.tracing.perfetto.FlushCallbackArguments; import android.tracing.perfetto.StartCallbackArguments; import android.tracing.perfetto.StopCallbackArguments; import android.util.proto.ProtoInputStream; import java.util.HashMap; import java.util.Map; class ViewCaptureDataSource extends DataSource<DataSourceInstance, Void, ViewCaptureDataSource.IncrementalState> { public static String DATA_SOURCE_NAME = "android.viewcapture"; private final Runnable mOnStartStaticCallback; private final Runnable mOnFlushStaticCallback; private final Runnable mOnStopStaticCallback; ViewCaptureDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) { super(DATA_SOURCE_NAME); this.mOnStartStaticCallback = onStart; this.mOnFlushStaticCallback = onFlush; this.mOnStopStaticCallback = onStop; } @Override public IncrementalState createIncrementalState( CreateIncrementalStateArgs<DataSourceInstance> args) { return new IncrementalState(); } public static class IncrementalState { public final Map<String, Integer> mInternMapPackageName = new HashMap<>(); public final Map<String, Integer> mInternMapWindowName = new HashMap<>(); public final Map<String, Integer> mInternMapViewId = new HashMap<>(); public final Map<String, Integer> mInternMapClassName = new HashMap<>(); public boolean mHasNotifiedClearedState = false; } @Override public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) { return new DataSourceInstance(this, instanceIndex) { @Override protected void onStart(StartCallbackArguments args) { mOnStartStaticCallback.run(); } @Override protected void onFlush(FlushCallbackArguments args) { mOnFlushStaticCallback.run(); } @Override protected void onStop(StopCallbackArguments args) { mOnStopStaticCallback.run(); } }; } }