diff --git a/protos/view_capture.proto b/protos/view_capture.proto index 349ff36d15..f363f36820 100644 --- a/protos/view_capture.proto +++ b/protos/view_capture.proto @@ -23,6 +23,7 @@ option java_outer_classname = "ViewCaptureData"; message ExportedData { repeated FrameData frameData = 1; + repeated string classname = 2; } message FrameData { @@ -31,26 +32,28 @@ message FrameData { } message ViewNode { - optional string classname = 1; - optional string id = 2; - optional int32 left = 3; - optional int32 top = 4; - optional int32 width = 5; - optional int32 height = 6; - optional int32 scrollX = 7; - optional int32 scrollY = 8; + optional int32 classname_index = 1; + optional int32 hashcode = 2; - optional float translationX = 9; - optional float translationY = 10; - optional float scaleX = 11 [default = 1]; - optional float scaleY = 12 [default = 1]; - optional float alpha = 13 [default = 1]; + repeated ViewNode children = 3; - optional bool willNotDraw = 14; - optional bool clipChildren = 15; - optional int32 visibility = 16; + optional string id = 4; + optional int32 left = 5; + optional int32 top = 6; + optional int32 width = 7; + optional int32 height = 8; + optional int32 scrollX = 9; + optional int32 scrollY = 10; - repeated ViewNode children = 17; + optional float translationX = 11; + optional float translationY = 12; + optional float scaleX = 13 [default = 1]; + optional float scaleY = 14 [default = 1]; + optional float alpha = 15 [default = 1]; - optional float elevation = 18; + optional bool willNotDraw = 16; + optional bool clipChildren = 17; + optional int32 visibility = 18; + + optional float elevation = 19; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 761f198dad..4a52d3e6a2 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1491,9 +1491,9 @@ public class Launcher extends StatefulActivity if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) { View root = getDragLayer().getRootView(); if (mViewCapture != null) { - root.getViewTreeObserver().removeOnDrawListener(mViewCapture); + mViewCapture.detach(); } - mViewCapture = new ViewCapture(root); + mViewCapture = new ViewCapture(getWindow()); mViewCapture.attach(); } } @@ -1501,6 +1501,10 @@ public class Launcher extends StatefulActivity @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); + if (mViewCapture != null) { + mViewCapture.detach(); + mViewCapture = null; + } mOverlayManager.onDetachedFromWindow(); closeContextMenu(); } @@ -2981,6 +2985,7 @@ public class Launcher extends StatefulActivity */ @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + SafeCloseable viewDump = mViewCapture == null ? null : mViewCapture.beginDump(writer, fd); super.dump(prefix, fd, writer, args); if (args.length > 0 && TextUtils.equals(args[0], "--all")) { @@ -3015,19 +3020,16 @@ public class Launcher extends StatefulActivity writer.println(prefix + "\tmRotationHelper: " + mRotationHelper); writer.println(prefix + "\tmAppWidgetHost.isListening: " + mAppWidgetHost.isListening()); - if (mViewCapture != null) { - writer.print(prefix + "\tmViewCapture: "); - writer.flush(); - mViewCapture.dump(fd); - writer.println(); - } - // Extra logging for general debugging mDragLayer.dump(prefix, writer); mStateManager.dump(prefix, writer); mPopupDataProvider.dump(prefix, writer); mDeviceProfile.dump(this, prefix, writer); + if (viewDump != null) { + viewDump.close(); + } + try { FileLog.flushAll(writer); } catch (Exception e) { diff --git a/src/com/android/launcher3/util/ViewCapture.java b/src/com/android/launcher3/util/ViewCapture.java index 58c8269f97..e368ac3c2b 100644 --- a/src/com/android/launcher3/util/ViewCapture.java +++ b/src/com/android/launcher3/util/ViewCapture.java @@ -15,18 +15,23 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static java.util.stream.Collectors.toList; + import android.content.res.Resources; import android.os.Handler; import android.os.Message; import android.os.Trace; +import android.text.TextUtils; import android.util.Base64; import android.util.Base64OutputStream; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnDrawListener; +import android.view.Window; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; @@ -38,7 +43,10 @@ import com.android.launcher3.view.ViewCaptureData.ViewNode; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; import java.util.concurrent.Future; +import java.util.zip.GZIPOutputStream; /** * Utility class for capturing view data every frame @@ -53,6 +61,7 @@ public class ViewCapture implements OnDrawListener { // Launcher. This allows the first free frames avoid object allocation during view capture. private static final int INIT_POOL_SIZE = 300; + private final Window mWindow; private final View mRoot; private final Resources mResources; @@ -67,11 +76,12 @@ public class ViewCapture implements OnDrawListener { private ViewRef mPool = new ViewRef(); /** - * @param root the root view for the capture data + * @param window the window for the capture data */ - public ViewCapture(View root) { - mRoot = root; - mResources = root.getResources(); + public ViewCapture(Window window) { + mWindow = window; + mRoot = mWindow.getDecorView(); + mResources = mRoot.getResources(); mHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::captureViewPropertiesBg); } @@ -82,6 +92,14 @@ public class ViewCapture implements OnDrawListener { mHandler.post(this::initPool); } + /** + * Removes a previously attached ViewCapture from the root + */ + public void detach() { + mHandler.post(() -> MAIN_EXECUTOR.execute( + () -> mRoot.getViewTreeObserver().removeOnDrawListener(this))); + } + @Override public void onDraw() { Trace.beginSection("view_capture"); @@ -139,7 +157,7 @@ public class ViewCapture implements OnDrawListener { } mNodesBg[mFrameIndexBg] = result; ViewRef end = last; - Executors.MAIN_EXECUTOR.execute(() -> addToPool(start, end)); + MAIN_EXECUTOR.execute(() -> addToPool(start, end)); return true; } @@ -160,7 +178,7 @@ public class ViewCapture implements OnDrawListener { } ViewRef end = current; - Executors.MAIN_EXECUTOR.execute(() -> { + MAIN_EXECUTOR.execute(() -> { addToPool(start, end); if (mRoot.isAttachedToWindow()) { mRoot.getViewTreeObserver().addOnDrawListener(this); @@ -168,38 +186,58 @@ public class ViewCapture implements OnDrawListener { }); } + private String getName() { + String title = mWindow.getAttributes().getTitle().toString(); + return TextUtils.isEmpty(title) ? mWindow.toString() : title; + } + /** - * Creates a proto of all the data captured so far. + * Starts the dump process which is completed on closing the returned object. */ - public void dump(FileDescriptor out) { + public SafeCloseable beginDump(PrintWriter writer, FileDescriptor out) { Future task = UI_HELPER_EXECUTOR.submit(this::dumpToProto); - try (OutputStream os = new FileOutputStream(out)) { - ExportedData data = task.get(); - Base64OutputStream encodedOS = new Base64OutputStream(os, - Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP); - data.writeTo(encodedOS); - encodedOS.close(); - os.flush(); - } catch (Exception e) { - Log.e(TAG, "Error capturing proto", e); - } + + return () -> { + writer.println(); + writer.println(" ContinuousViewCapture:"); + writer.println(" window " + getName() + ":"); + writer.println(" pkg:" + mRoot.getContext().getPackageName()); + writer.print(" data:"); + writer.flush(); + + try (OutputStream os = new FileOutputStream(out)) { + ExportedData data = task.get(); + OutputStream encodedOS = new GZIPOutputStream(new Base64OutputStream(os, + Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP)); + data.writeTo(encodedOS); + encodedOS.close(); + os.flush(); + } catch (Exception e) { + Log.e(TAG, "Error capturing proto", e); + } + writer.println(); + writer.println("--end--"); + }; } @WorkerThread private ExportedData dumpToProto() { ExportedData.Builder dataBuilder = ExportedData.newBuilder(); Resources res = mResources; + ArrayList classList = new ArrayList<>(); int size = (mNodesBg[MEMORY_SIZE - 1] == null) ? mFrameIndexBg + 1 : MEMORY_SIZE; for (int i = size - 1; i >= 0; i--) { int index = (MEMORY_SIZE + mFrameIndexBg - i) % MEMORY_SIZE; ViewNode.Builder nodeBuilder = ViewNode.newBuilder(); - mNodesBg[index].toProto(res, nodeBuilder); + mNodesBg[index].toProto(res, classList, nodeBuilder); dataBuilder.addFrameData(FrameData.newBuilder() .setNode(nodeBuilder) .setTimestamp(mFrameTimesBg[index])); } - return dataBuilder.build(); + return dataBuilder + .addAllClassname(classList.stream().map(Class::getName).collect(toList())) + .build(); } private ViewRef captureViewTree(View view, ViewRef start) { @@ -278,10 +316,10 @@ public class ViewCapture implements OnDrawListener { /** * Converts the data to the proto representation and returns the next property ref * at the end of the iteration. - * @param res * @return */ - public ViewPropertyRef toProto(Resources res, ViewNode.Builder outBuilder) { + public ViewPropertyRef toProto(Resources res, ArrayList classList, + ViewNode.Builder outBuilder) { String resolvedId; if (id >= 0) { try { @@ -292,7 +330,14 @@ public class ViewCapture implements OnDrawListener { } else { resolvedId = "NO_ID"; } - outBuilder.setClassname(clazz.getName() + "@" + hashCode) + int classnameIndex = classList.indexOf(clazz); + if (classnameIndex < 0) { + classnameIndex = classList.size(); + classList.add(clazz); + } + outBuilder + .setClassnameIndex(classnameIndex) + .setHashcode(hashCode) .setId(resolvedId) .setLeft(left) .setTop(top) @@ -311,7 +356,7 @@ public class ViewCapture implements OnDrawListener { ViewPropertyRef result = next; for (int i = 0; (i < childCount) && (result != null); i++) { ViewNode.Builder childBuilder = ViewNode.newBuilder(); - result = result.toProto(res, childBuilder); + result = result.toProto(res, classList, childBuilder); outBuilder.addChildren(childBuilder); } return result;