Loading src/com/android/launcher3/Launcher.java +1 −1 Original line number Diff line number Diff line Loading @@ -1494,7 +1494,7 @@ public class Launcher extends StatefulActivity<LauncherState> root.getViewTreeObserver().removeOnDrawListener(mViewCapture); } mViewCapture = new ViewCapture(root); root.getViewTreeObserver().addOnDrawListener(mViewCapture); mViewCapture.attach(); } } Loading src/com/android/launcher3/util/ViewCapture.java +184 −80 Original line number Diff line number Diff line Loading @@ -15,10 +15,11 @@ */ package com.android.launcher3.util; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.content.res.Resources; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.os.Message; import android.os.Trace; import android.util.Base64; import android.util.Base64OutputStream; Loading @@ -28,6 +29,7 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver.OnDrawListener; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import com.android.launcher3.view.ViewCaptureData.ExportedData; import com.android.launcher3.view.ViewCaptureData.FrameData; Loading @@ -36,7 +38,7 @@ import com.android.launcher3.view.ViewCaptureData.ViewNode; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.concurrent.FutureTask; import java.util.concurrent.Future; /** * Utility class for capturing view data every frame Loading @@ -45,49 +47,132 @@ public class ViewCapture implements OnDrawListener { private static final String TAG = "ViewCapture"; // Number of frames to keep in memory private static final int MEMORY_SIZE = 2000; // Initial size of the reference pool. This is at least be 5 * total number of views in // Launcher. This allows the first free frames avoid object allocation during view capture. private static final int INIT_POOL_SIZE = 300; private final View mRoot; private final long[] mFrameTimes = new long[MEMORY_SIZE]; private final Node[] mNodes = new Node[MEMORY_SIZE]; private final Resources mResources; private final Handler mHandler; private final ViewRef mViewRef = new ViewRef(); private int mFrameIndex = -1; private int mFrameIndexBg = -1; private final long[] mFrameTimesBg = new long[MEMORY_SIZE]; private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE]; // Pool used for capturing view tree on the UI thread. private ViewRef mPool = new ViewRef(); /** * @param root the root view for the capture data */ public ViewCapture(View root) { mRoot = root; mResources = root.getResources(); mHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::captureViewPropertiesBg); } /** * Attaches the ViewCapture to the root */ public void attach() { mHandler.post(this::initPool); } @Override public void onDraw() { Trace.beginSection("view_capture"); long now = SystemClock.elapsedRealtimeNanos(); mFrameIndex++; if (mFrameIndex >= MEMORY_SIZE) { mFrameIndex = 0; } mFrameTimes[mFrameIndex] = now; mNodes[mFrameIndex] = captureView(mRoot, mNodes[mFrameIndex]); captureViewTree(mRoot, mViewRef); Message m = Message.obtain(mHandler); m.obj = mViewRef.next; mHandler.sendMessage(m); Trace.endSection(); } /** * Creates a proto of all the data captured so far. * Captures the View property on the background thread, and transfer all the ViewRef objects * back to the pool */ public void dump(FileDescriptor out) { Handler handler = mRoot.getHandler(); if (handler == null) { handler = Executors.MAIN_EXECUTOR.getHandler(); @WorkerThread private boolean captureViewPropertiesBg(Message msg) { ViewRef start = (ViewRef) msg.obj; long time = msg.getWhen(); if (start == null) { return false; } mFrameIndexBg++; if (mFrameIndexBg >= MEMORY_SIZE) { mFrameIndexBg = 0; } mFrameTimesBg[mFrameIndexBg] = time; ViewPropertyRef recycle = mNodesBg[mFrameIndexBg]; ViewPropertyRef result = null; ViewPropertyRef resultEnd = null; ViewRef current = start; ViewRef last = start; while (current != null) { ViewPropertyRef propertyRef = recycle; if (propertyRef == null) { propertyRef = new ViewPropertyRef(); } else { recycle = recycle.next; propertyRef.next = null; } FutureTask<ExportedData> task = new FutureTask<>(this::dumpToProtoUI); if (Looper.myLooper() == handler.getLooper()) { task.run(); propertyRef.transfer(current); last = current; current = current.next; if (result == null) { result = propertyRef; resultEnd = result; } else { handler.post(task); resultEnd.next = propertyRef; resultEnd = propertyRef; } } mNodesBg[mFrameIndexBg] = result; ViewRef end = last; Executors.MAIN_EXECUTOR.execute(() -> addToPool(start, end)); return true; } @UiThread private void addToPool(ViewRef start, ViewRef end) { end.next = mPool; mPool = start; } @WorkerThread private void initPool() { ViewRef start = new ViewRef(); ViewRef current = start; for (int i = 0; i < INIT_POOL_SIZE; i++) { current.next = new ViewRef(); current = current.next; } ViewRef end = current; Executors.MAIN_EXECUTOR.execute(() -> { addToPool(start, end); if (mRoot.isAttachedToWindow()) { mRoot.getViewTreeObserver().addOnDrawListener(this); } }); } /** * Creates a proto of all the data captured so far. */ public void dump(FileDescriptor out) { Future<ExportedData> task = UI_HELPER_EXECUTOR.submit(this::dumpToProto); try (OutputStream os = new FileOutputStream(out)) { ExportedData data = task.get(); Base64OutputStream encodedOS = new Base64OutputStream(os, Loading @@ -100,70 +185,53 @@ public class ViewCapture implements OnDrawListener { } } @UiThread private ExportedData dumpToProtoUI() { @WorkerThread private ExportedData dumpToProto() { ExportedData.Builder dataBuilder = ExportedData.newBuilder(); Resources res = mRoot.getResources(); Resources res = mResources; int size = (mNodes[MEMORY_SIZE - 1] == null) ? mFrameIndex + 1 : MEMORY_SIZE; int size = (mNodesBg[MEMORY_SIZE - 1] == null) ? mFrameIndexBg + 1 : MEMORY_SIZE; for (int i = size - 1; i >= 0; i--) { int index = (MEMORY_SIZE + mFrameIndex - i) % MEMORY_SIZE; int index = (MEMORY_SIZE + mFrameIndexBg - i) % MEMORY_SIZE; ViewNode.Builder nodeBuilder = ViewNode.newBuilder(); mNodesBg[index].toProto(res, nodeBuilder); dataBuilder.addFrameData(FrameData.newBuilder() .setNode(mNodes[index].toProto(res)) .setTimestamp(mFrameTimes[index])); .setNode(nodeBuilder) .setTimestamp(mFrameTimesBg[index])); } return dataBuilder.build(); } private Node captureView(View view, Node recycle) { Node result = recycle == null ? new Node() : recycle; result.clazz = view.getClass(); result.hashCode = view.hashCode(); result.id = view.getId(); result.left = view.getLeft(); result.top = view.getTop(); result.right = view.getRight(); result.bottom = view.getBottom(); result.scrollX = view.getScrollX(); result.scrollY = view.getScrollY(); result.translateX = view.getTranslationX(); result.translateY = view.getTranslationY(); result.scaleX = view.getScaleX(); result.scaleY = view.getScaleY(); result.alpha = view.getAlpha(); result.visibility = view.getVisibility(); result.willNotDraw = view.willNotDraw(); if (view instanceof ViewGroup) { ViewGroup parent = (ViewGroup) view; result.clipChildren = parent.getClipChildren(); int childCount = parent.getChildCount(); if (childCount == 0) { result.children = null; private ViewRef captureViewTree(View view, ViewRef start) { ViewRef ref; if (mPool != null) { ref = mPool; mPool = mPool.next; ref.next = null; } else { result.children = captureView(parent.getChildAt(0), result.children); Node lastChild = result.children; for (int i = 1; i < childCount; i++) { lastChild.sibling = captureView(parent.getChildAt(i), lastChild.sibling); lastChild = lastChild.sibling; ref = new ViewRef(); } lastChild.sibling = null; ref.view = view; start.next = ref; if (view instanceof ViewGroup) { ViewRef result = ref; ViewGroup parent = (ViewGroup) view; int childCount = ref.childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { result = captureViewTree(parent.getChildAt(i), result); } return result; } else { result.clipChildren = false; result.children = null; ref.childCount = 0; return ref; } return result; } private static class Node { private static class ViewPropertyRef { // We store reference in memory to avoid generating and storing too many strings public Class clazz; public int hashCode; public int childCount = 0; public int id; public int left, top, right, bottom; Loading @@ -177,10 +245,41 @@ public class ViewCapture implements OnDrawListener { public boolean willNotDraw; public boolean clipChildren; public Node sibling; public Node children; public ViewPropertyRef next; public void transfer(ViewRef viewRef) { childCount = viewRef.childCount; View view = viewRef.view; viewRef.view = null; clazz = view.getClass(); hashCode = view.hashCode(); id = view.getId(); left = view.getLeft(); top = view.getTop(); right = view.getRight(); bottom = view.getBottom(); scrollX = view.getScrollX(); scrollY = view.getScrollY(); translateX = view.getTranslationX(); translateY = view.getTranslationY(); scaleX = view.getScaleX(); scaleY = view.getScaleY(); alpha = view.getAlpha(); visibility = view.getVisibility(); willNotDraw = view.willNotDraw(); } public ViewNode toProto(Resources res) { /** * 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) { String resolvedId; if (id >= 0) { try { Loading @@ -191,9 +290,7 @@ public class ViewCapture implements OnDrawListener { } else { resolvedId = "NO_ID"; } ViewNode.Builder result = ViewNode.newBuilder() .setClassname(clazz.getName() + "@" + hashCode) outBuilder.setClassname(clazz.getName() + "@" + hashCode) .setId(resolvedId) .setLeft(left) .setTop(top) Loading @@ -207,13 +304,20 @@ public class ViewCapture implements OnDrawListener { .setVisibility(visibility) .setWillNotDraw(willNotDraw) .setClipChildren(clipChildren); Node child = children; while (child != null) { result.addChildren(child.toProto(res)); child = child.sibling; ViewPropertyRef result = next; for (int i = 0; (i < childCount) && (result != null); i++) { ViewNode.Builder childBuilder = ViewNode.newBuilder(); result = result.toProto(res, childBuilder); outBuilder.addChildren(childBuilder); } return result; } return result.build(); } private static class ViewRef { public View view; public int childCount = 0; public ViewRef next; } } Loading
src/com/android/launcher3/Launcher.java +1 −1 Original line number Diff line number Diff line Loading @@ -1494,7 +1494,7 @@ public class Launcher extends StatefulActivity<LauncherState> root.getViewTreeObserver().removeOnDrawListener(mViewCapture); } mViewCapture = new ViewCapture(root); root.getViewTreeObserver().addOnDrawListener(mViewCapture); mViewCapture.attach(); } } Loading
src/com/android/launcher3/util/ViewCapture.java +184 −80 Original line number Diff line number Diff line Loading @@ -15,10 +15,11 @@ */ package com.android.launcher3.util; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.content.res.Resources; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.os.Message; import android.os.Trace; import android.util.Base64; import android.util.Base64OutputStream; Loading @@ -28,6 +29,7 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver.OnDrawListener; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import com.android.launcher3.view.ViewCaptureData.ExportedData; import com.android.launcher3.view.ViewCaptureData.FrameData; Loading @@ -36,7 +38,7 @@ import com.android.launcher3.view.ViewCaptureData.ViewNode; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.concurrent.FutureTask; import java.util.concurrent.Future; /** * Utility class for capturing view data every frame Loading @@ -45,49 +47,132 @@ public class ViewCapture implements OnDrawListener { private static final String TAG = "ViewCapture"; // Number of frames to keep in memory private static final int MEMORY_SIZE = 2000; // Initial size of the reference pool. This is at least be 5 * total number of views in // Launcher. This allows the first free frames avoid object allocation during view capture. private static final int INIT_POOL_SIZE = 300; private final View mRoot; private final long[] mFrameTimes = new long[MEMORY_SIZE]; private final Node[] mNodes = new Node[MEMORY_SIZE]; private final Resources mResources; private final Handler mHandler; private final ViewRef mViewRef = new ViewRef(); private int mFrameIndex = -1; private int mFrameIndexBg = -1; private final long[] mFrameTimesBg = new long[MEMORY_SIZE]; private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE]; // Pool used for capturing view tree on the UI thread. private ViewRef mPool = new ViewRef(); /** * @param root the root view for the capture data */ public ViewCapture(View root) { mRoot = root; mResources = root.getResources(); mHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::captureViewPropertiesBg); } /** * Attaches the ViewCapture to the root */ public void attach() { mHandler.post(this::initPool); } @Override public void onDraw() { Trace.beginSection("view_capture"); long now = SystemClock.elapsedRealtimeNanos(); mFrameIndex++; if (mFrameIndex >= MEMORY_SIZE) { mFrameIndex = 0; } mFrameTimes[mFrameIndex] = now; mNodes[mFrameIndex] = captureView(mRoot, mNodes[mFrameIndex]); captureViewTree(mRoot, mViewRef); Message m = Message.obtain(mHandler); m.obj = mViewRef.next; mHandler.sendMessage(m); Trace.endSection(); } /** * Creates a proto of all the data captured so far. * Captures the View property on the background thread, and transfer all the ViewRef objects * back to the pool */ public void dump(FileDescriptor out) { Handler handler = mRoot.getHandler(); if (handler == null) { handler = Executors.MAIN_EXECUTOR.getHandler(); @WorkerThread private boolean captureViewPropertiesBg(Message msg) { ViewRef start = (ViewRef) msg.obj; long time = msg.getWhen(); if (start == null) { return false; } mFrameIndexBg++; if (mFrameIndexBg >= MEMORY_SIZE) { mFrameIndexBg = 0; } mFrameTimesBg[mFrameIndexBg] = time; ViewPropertyRef recycle = mNodesBg[mFrameIndexBg]; ViewPropertyRef result = null; ViewPropertyRef resultEnd = null; ViewRef current = start; ViewRef last = start; while (current != null) { ViewPropertyRef propertyRef = recycle; if (propertyRef == null) { propertyRef = new ViewPropertyRef(); } else { recycle = recycle.next; propertyRef.next = null; } FutureTask<ExportedData> task = new FutureTask<>(this::dumpToProtoUI); if (Looper.myLooper() == handler.getLooper()) { task.run(); propertyRef.transfer(current); last = current; current = current.next; if (result == null) { result = propertyRef; resultEnd = result; } else { handler.post(task); resultEnd.next = propertyRef; resultEnd = propertyRef; } } mNodesBg[mFrameIndexBg] = result; ViewRef end = last; Executors.MAIN_EXECUTOR.execute(() -> addToPool(start, end)); return true; } @UiThread private void addToPool(ViewRef start, ViewRef end) { end.next = mPool; mPool = start; } @WorkerThread private void initPool() { ViewRef start = new ViewRef(); ViewRef current = start; for (int i = 0; i < INIT_POOL_SIZE; i++) { current.next = new ViewRef(); current = current.next; } ViewRef end = current; Executors.MAIN_EXECUTOR.execute(() -> { addToPool(start, end); if (mRoot.isAttachedToWindow()) { mRoot.getViewTreeObserver().addOnDrawListener(this); } }); } /** * Creates a proto of all the data captured so far. */ public void dump(FileDescriptor out) { Future<ExportedData> task = UI_HELPER_EXECUTOR.submit(this::dumpToProto); try (OutputStream os = new FileOutputStream(out)) { ExportedData data = task.get(); Base64OutputStream encodedOS = new Base64OutputStream(os, Loading @@ -100,70 +185,53 @@ public class ViewCapture implements OnDrawListener { } } @UiThread private ExportedData dumpToProtoUI() { @WorkerThread private ExportedData dumpToProto() { ExportedData.Builder dataBuilder = ExportedData.newBuilder(); Resources res = mRoot.getResources(); Resources res = mResources; int size = (mNodes[MEMORY_SIZE - 1] == null) ? mFrameIndex + 1 : MEMORY_SIZE; int size = (mNodesBg[MEMORY_SIZE - 1] == null) ? mFrameIndexBg + 1 : MEMORY_SIZE; for (int i = size - 1; i >= 0; i--) { int index = (MEMORY_SIZE + mFrameIndex - i) % MEMORY_SIZE; int index = (MEMORY_SIZE + mFrameIndexBg - i) % MEMORY_SIZE; ViewNode.Builder nodeBuilder = ViewNode.newBuilder(); mNodesBg[index].toProto(res, nodeBuilder); dataBuilder.addFrameData(FrameData.newBuilder() .setNode(mNodes[index].toProto(res)) .setTimestamp(mFrameTimes[index])); .setNode(nodeBuilder) .setTimestamp(mFrameTimesBg[index])); } return dataBuilder.build(); } private Node captureView(View view, Node recycle) { Node result = recycle == null ? new Node() : recycle; result.clazz = view.getClass(); result.hashCode = view.hashCode(); result.id = view.getId(); result.left = view.getLeft(); result.top = view.getTop(); result.right = view.getRight(); result.bottom = view.getBottom(); result.scrollX = view.getScrollX(); result.scrollY = view.getScrollY(); result.translateX = view.getTranslationX(); result.translateY = view.getTranslationY(); result.scaleX = view.getScaleX(); result.scaleY = view.getScaleY(); result.alpha = view.getAlpha(); result.visibility = view.getVisibility(); result.willNotDraw = view.willNotDraw(); if (view instanceof ViewGroup) { ViewGroup parent = (ViewGroup) view; result.clipChildren = parent.getClipChildren(); int childCount = parent.getChildCount(); if (childCount == 0) { result.children = null; private ViewRef captureViewTree(View view, ViewRef start) { ViewRef ref; if (mPool != null) { ref = mPool; mPool = mPool.next; ref.next = null; } else { result.children = captureView(parent.getChildAt(0), result.children); Node lastChild = result.children; for (int i = 1; i < childCount; i++) { lastChild.sibling = captureView(parent.getChildAt(i), lastChild.sibling); lastChild = lastChild.sibling; ref = new ViewRef(); } lastChild.sibling = null; ref.view = view; start.next = ref; if (view instanceof ViewGroup) { ViewRef result = ref; ViewGroup parent = (ViewGroup) view; int childCount = ref.childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { result = captureViewTree(parent.getChildAt(i), result); } return result; } else { result.clipChildren = false; result.children = null; ref.childCount = 0; return ref; } return result; } private static class Node { private static class ViewPropertyRef { // We store reference in memory to avoid generating and storing too many strings public Class clazz; public int hashCode; public int childCount = 0; public int id; public int left, top, right, bottom; Loading @@ -177,10 +245,41 @@ public class ViewCapture implements OnDrawListener { public boolean willNotDraw; public boolean clipChildren; public Node sibling; public Node children; public ViewPropertyRef next; public void transfer(ViewRef viewRef) { childCount = viewRef.childCount; View view = viewRef.view; viewRef.view = null; clazz = view.getClass(); hashCode = view.hashCode(); id = view.getId(); left = view.getLeft(); top = view.getTop(); right = view.getRight(); bottom = view.getBottom(); scrollX = view.getScrollX(); scrollY = view.getScrollY(); translateX = view.getTranslationX(); translateY = view.getTranslationY(); scaleX = view.getScaleX(); scaleY = view.getScaleY(); alpha = view.getAlpha(); visibility = view.getVisibility(); willNotDraw = view.willNotDraw(); } public ViewNode toProto(Resources res) { /** * 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) { String resolvedId; if (id >= 0) { try { Loading @@ -191,9 +290,7 @@ public class ViewCapture implements OnDrawListener { } else { resolvedId = "NO_ID"; } ViewNode.Builder result = ViewNode.newBuilder() .setClassname(clazz.getName() + "@" + hashCode) outBuilder.setClassname(clazz.getName() + "@" + hashCode) .setId(resolvedId) .setLeft(left) .setTop(top) Loading @@ -207,13 +304,20 @@ public class ViewCapture implements OnDrawListener { .setVisibility(visibility) .setWillNotDraw(willNotDraw) .setClipChildren(clipChildren); Node child = children; while (child != null) { result.addChildren(child.toProto(res)); child = child.sibling; ViewPropertyRef result = next; for (int i = 0; (i < childCount) && (result != null); i++) { ViewNode.Builder childBuilder = ViewNode.newBuilder(); result = result.toProto(res, childBuilder); outBuilder.addChildren(childBuilder); } return result; } return result.build(); } private static class ViewRef { public View view; public int childCount = 0; public ViewRef next; } }