Copying ViewCapture from last frame when possible.

Bug: 242867462
Test: Used system.nanotime to time the performance improvements
before and after. Verified correct behavior by coloring dirty
views differently in the go/web-hv tool and ensuring that the
hierarchies were emitted correctly for dirty and clean views.

Change-Id: I624572170bc914ed2c9d329e37e64a88203f87f4
diff --git a/quickstep/src/com/android/quickstep/util/ViewCapture.java b/quickstep/src/com/android/quickstep/util/ViewCapture.java
index 6071bc8..cfcfce0 100644
--- a/quickstep/src/com/android/quickstep/util/ViewCapture.java
+++ b/quickstep/src/com/android/quickstep/util/ViewCapture.java
@@ -67,6 +67,10 @@
 
     private static final String TAG = "ViewCapture";
 
+    // These flags are copies of two private flags in the View class.
+    private static final int PFLAG_INVALIDATED = 0x80000000;
+    private static final int PFLAG_DIRTY_MASK = 0x00200000;
+
     // 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
@@ -187,6 +191,7 @@
         private final ViewRef mViewRef = new ViewRef();
 
         private int mFrameIndexBg = -1;
+        private boolean mIsFirstFrame = true;
         private final long[] mFrameTimesBg = new long[MEMORY_SIZE];
         private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE];
 
@@ -205,6 +210,7 @@
             Message m = Message.obtain(mHandler);
             m.obj = mViewRef.next;
             mHandler.sendMessage(m);
+            mIsFirstFrame = false;
             Trace.endSection();
         }
 
@@ -214,9 +220,9 @@
          */
         @WorkerThread
         private boolean captureViewPropertiesBg(Message msg) {
-            ViewRef start = (ViewRef) msg.obj;
+            ViewRef viewRefStart = (ViewRef) msg.obj;
             long time = msg.getWhen();
-            if (start == null) {
+            if (viewRefStart == null) {
                 return false;
             }
             mFrameIndexBg++;
@@ -227,12 +233,11 @@
 
             ViewPropertyRef recycle = mNodesBg[mFrameIndexBg];
 
-            ViewPropertyRef result = null;
+            ViewPropertyRef resultStart = null;
             ViewPropertyRef resultEnd = null;
 
-            ViewRef current = start;
-            ViewRef last = start;
-            while (current != null) {
+            ViewRef viewRefEnd = viewRefStart;
+            while (viewRefEnd != null) {
                 ViewPropertyRef propertyRef = recycle;
                 if (propertyRef == null) {
                     propertyRef = new ViewPropertyRef();
@@ -241,24 +246,64 @@
                     propertyRef.next = null;
                 }
 
-                propertyRef.transfer(current);
-                last = current;
-                current = current.next;
+                ViewPropertyRef copy = null;
+                if (viewRefEnd.childCount < 0) {
+                    copy = findInLastFrame(viewRefEnd.view.hashCode());
+                    viewRefEnd.childCount = (copy != null) ? copy.childCount : 0;
+                }
+                viewRefEnd.transferTo(propertyRef);
 
-                if (result == null) {
-                    result = propertyRef;
-                    resultEnd = result;
+                if (resultStart == null) {
+                    resultStart = propertyRef;
+                    resultEnd = resultStart;
                 } else {
                     resultEnd.next = propertyRef;
-                    resultEnd = propertyRef;
+                    resultEnd = resultEnd.next;
                 }
+
+                if (copy != null) {
+                    int pending = copy.childCount;
+                    while (pending > 0) {
+                        copy = copy.next;
+                        pending = pending - 1 + copy.childCount;
+
+                        propertyRef = recycle;
+                        if (propertyRef == null) {
+                            propertyRef = new ViewPropertyRef();
+                        } else {
+                            recycle = recycle.next;
+                            propertyRef.next = null;
+                        }
+
+                        copy.transferTo(propertyRef);
+
+                        resultEnd.next = propertyRef;
+                        resultEnd = resultEnd.next;
+                    }
+                }
+
+                if (viewRefEnd.next == null) {
+                    // The compiler will complain about using a non-final variable from
+                    // an outer class in a lambda if we pass in viewRefEnd directly.
+                    final ViewRef finalViewRefEnd = viewRefEnd;
+                    MAIN_EXECUTOR.execute(() -> addToPool(viewRefStart, finalViewRefEnd));
+                    break;
+                }
+                viewRefEnd = viewRefEnd.next;
             }
-            mNodesBg[mFrameIndexBg] = result;
-            ViewRef end = last;
-            MAIN_EXECUTOR.execute(() -> addToPool(start, end));
+            mNodesBg[mFrameIndexBg] = resultStart;
             return true;
         }
 
+        private ViewPropertyRef findInLastFrame(int hashCode) {
+            int lastFrameIndex = (mFrameIndexBg == 0) ? MEMORY_SIZE - 1 : mFrameIndexBg - 1;
+            ViewPropertyRef viewPropertyRef = mNodesBg[lastFrameIndex];
+            while (viewPropertyRef != null && viewPropertyRef.hashCode != hashCode) {
+                viewPropertyRef = viewPropertyRef.next;
+            }
+            return viewPropertyRef;
+        }
+
         void attachToRoot() {
             if (mRoot.isAttachedToWindow()) {
                 mRoot.getViewTreeObserver().addOnDrawListener(this);
@@ -314,8 +359,16 @@
             ref.view = view;
             start.next = ref;
             if (view instanceof ViewGroup) {
-                ViewRef result = ref;
                 ViewGroup parent = (ViewGroup) view;
+                // If a view has not changed since the last frame, we will copy
+                // its children from the last processed frame's data.
+                if ((view.mPrivateFlags & (PFLAG_INVALIDATED | PFLAG_DIRTY_MASK)) == 0
+                        && !mIsFirstFrame) {
+                    // A negative child count is the signal to copy this view from the last frame.
+                    ref.childCount = -parent.getChildCount();
+                    return ref;
+                }
+                ViewRef result = ref;
                 int childCount = ref.childCount = parent.getChildCount();
                 for (int i = 0; i < childCount; i++) {
                     result = captureViewTree(parent.getChildAt(i), result);
@@ -349,31 +402,27 @@
 
         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();
-            elevation = view.getElevation();
+        public void transferTo(ViewPropertyRef out) {
+            out.clazz = this.clazz;
+            out.hashCode = this.hashCode;
+            out.childCount = this.childCount;
+            out.id = this.id;
+            out.left = this.left;
+            out.top = this.top;
+            out.right = this.right;
+            out.bottom = this.bottom;
+            out.scrollX = this.scrollX;
+            out.scrollY = this.scrollY;
+            out.scaleX = this.scaleX;
+            out.scaleY = this.scaleY;
+            out.translateX = this.translateX;
+            out.translateY = this.translateY;
+            out.alpha = this.alpha;
+            out.visibility = this.visibility;
+            out.willNotDraw = this.willNotDraw;
+            out.clipChildren = this.clipChildren;
+            out.next = this.next;
+            out.elevation = this.elevation;
         }
 
         /**
@@ -420,6 +469,33 @@
         public View view;
         public int childCount = 0;
         public ViewRef next;
+
+        public void transferTo(ViewPropertyRef out) {
+            out.childCount = this.childCount;
+
+            View view = this.view;
+            this.view = null;
+
+            out.clazz = view.getClass();
+            out.hashCode = view.hashCode();
+            out.id = view.getId();
+            out.left = view.getLeft();
+            out.top = view.getTop();
+            out.right = view.getRight();
+            out.bottom = view.getBottom();
+            out.scrollX = view.getScrollX();
+            out.scrollY = view.getScrollY();
+
+            out.translateX = view.getTranslationX();
+            out.translateY = view.getTranslationY();
+            out.scaleX = view.getScaleX();
+            out.scaleY = view.getScaleY();
+            out.alpha = view.getAlpha();
+            out.elevation = view.getElevation();
+
+            out.visibility = view.getVisibility();
+            out.willNotDraw = view.willNotDraw();
+        }
     }
 
     private static final class ViewIdProvider {