Merge "Optimizing View capture logic" into tm-qpr-dev
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e55321b..761f198 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1494,7 +1494,7 @@
                 root.getViewTreeObserver().removeOnDrawListener(mViewCapture);
             }
             mViewCapture = new ViewCapture(root);
-            root.getViewTreeObserver().addOnDrawListener(mViewCapture);
+            mViewCapture.attach();
         }
     }
 
diff --git a/src/com/android/launcher3/util/ViewCapture.java b/src/com/android/launcher3/util/ViewCapture.java
index cf9ea69..cf4e84a 100644
--- a/src/com/android/launcher3/util/ViewCapture.java
+++ b/src/com/android/launcher3/util/ViewCapture.java
@@ -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;
@@ -28,6 +29,7 @@
 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;
@@ -36,7 +38,7 @@
 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
@@ -45,49 +47,132 @@
 
     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 int mFrameIndex = -1;
+    private final Handler mHandler;
+    private final ViewRef mViewRef = new ViewRef();
+
+    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();
     }
 
     /**
+     * Captures the View property on the background thread, and transfer all the ViewRef objects
+     * back to the pool
+     */
+    @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;
+            }
+
+            propertyRef.transfer(current);
+            last = current;
+            current = current.next;
+
+            if (result == null) {
+                result = propertyRef;
+                resultEnd = result;
+            } else {
+                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) {
-        Handler handler = mRoot.getHandler();
-        if (handler == null) {
-            handler = Executors.MAIN_EXECUTOR.getHandler();
-        }
-        FutureTask<ExportedData> task = new FutureTask<>(this::dumpToProtoUI);
-        if (Looper.myLooper() == handler.getLooper()) {
-            task.run();
-        } else {
-            handler.post(task);
-        }
+        Future<ExportedData> task = UI_HELPER_EXECUTOR.submit(this::dumpToProto);
         try (OutputStream os = new FileOutputStream(out)) {
             ExportedData data = task.get();
             Base64OutputStream encodedOS = new Base64OutputStream(os,
@@ -100,70 +185,53 @@
         }
     }
 
-    @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;
-            } 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;
-                }
-                lastChild.sibling = null;
-            }
+    private ViewRef captureViewTree(View view, ViewRef start) {
+        ViewRef ref;
+        if (mPool != null) {
+            ref = mPool;
+            mPool = mPool.next;
+            ref.next = null;
         } else {
-            result.clipChildren = false;
-            result.children = null;
+            ref = new ViewRef();
         }
-        return result;
+        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 {
+            ref.childCount = 0;
+            return ref;
+        }
     }
 
-    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;
@@ -177,10 +245,41 @@
         public boolean willNotDraw;
         public boolean clipChildren;
 
-        public Node sibling;
-        public Node children;
+        public ViewPropertyRef next;
 
-        public ViewNode toProto(Resources res) {
+        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();
+        }
+
+        /**
+         * 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 {
@@ -191,9 +290,7 @@
             } else {
                 resolvedId = "NO_ID";
             }
-
-            ViewNode.Builder result = ViewNode.newBuilder()
-                    .setClassname(clazz.getName() + "@" + hashCode)
+            outBuilder.setClassname(clazz.getName() + "@" + hashCode)
                     .setId(resolvedId)
                     .setLeft(left)
                     .setTop(top)
@@ -207,13 +304,20 @@
                     .setVisibility(visibility)
                     .setWillNotDraw(willNotDraw)
                     .setClipChildren(clipChildren);
-            Node child = children;
-            while (child != null) {
-                result.addChildren(child.toProto(res));
-                child = child.sibling;
-            }
-            return result.build();
-        }
 
+            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;
+        }
+    }
+
+    private static class ViewRef {
+        public View view;
+        public int childCount = 0;
+        public ViewRef next;
     }
 }