Merge "Moving view capture dump to a service to allow longer timeout" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index c9bc260..31fa57c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -120,10 +120,12 @@
 import com.android.launcher3.util.PendingRequestArgs;
 import com.android.launcher3.util.PendingSplitSelectInfo;
 import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
+import com.android.launcher3.util.ViewCapture;
 import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.RecentsModel;
@@ -195,6 +197,8 @@
      */
     private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
 
+    private SafeCloseable mViewCapture;
+
     @Override
     protected void setupViews() {
         super.setupViews();
@@ -410,6 +414,7 @@
 
         super.onDestroy();
         mHotseatPredictionController.destroy();
+        mViewCapture.close();
     }
 
     @Override
@@ -509,6 +514,7 @@
         }
         addMultiWindowModeChangedListener(mDepthController);
         initUnfoldTransitionProgressProvider();
+        mViewCapture = ViewCapture.INSTANCE.get(this).startCapture(getWindow());
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index ba4f549..c650f29 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -82,6 +82,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.ViewCapture;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
@@ -1213,6 +1214,8 @@
                 createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
             }
             mTaskbarManager.dumpLogs("", pw);
+
+            ViewCapture.INSTANCE.get(this).dump(pw, fd);
         }
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4a52d3e..4312c3d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -195,7 +195,6 @@
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.UiThreadHelper;
-import com.android.launcher3.util.ViewCapture;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.FloatingIconView;
@@ -393,7 +392,6 @@
     private LauncherState mPrevLauncherState;
 
     private StringCache mStringCache;
-    private ViewCapture mViewCapture;
 
     @Override
     @TargetApi(Build.VERSION_CODES.S)
@@ -1488,23 +1486,11 @@
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
         mOverlayManager.onAttachedToWindow();
-        if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
-            View root = getDragLayer().getRootView();
-            if (mViewCapture != null) {
-                mViewCapture.detach();
-            }
-            mViewCapture = new ViewCapture(getWindow());
-            mViewCapture.attach();
-        }
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        if (mViewCapture != null) {
-            mViewCapture.detach();
-            mViewCapture = null;
-        }
         mOverlayManager.onDetachedFromWindow();
         closeContextMenu();
     }
@@ -2985,7 +2971,6 @@
      */
     @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")) {
@@ -3026,10 +3011,6 @@
         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 e368ac3..0cf3ad7 100644
--- a/src/com/android/launcher3/util/ViewCapture.java
+++ b/src/com/android/launcher3/util/ViewCapture.java
@@ -17,18 +17,25 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
 
 import static java.util.stream.Collectors.toList;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.Trace;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Base64OutputStream;
 import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
 import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver.OnDrawListener;
 import android.view.Window;
@@ -36,6 +43,7 @@
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.view.ViewCaptureData.ExportedData;
 import com.android.launcher3.view.ViewCaptureData.FrameData;
 import com.android.launcher3.view.ViewCaptureData.ViewNode;
@@ -45,13 +53,14 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Future;
 import java.util.zip.GZIPOutputStream;
 
 /**
  * Utility class for capturing view data every frame
  */
-public class ViewCapture implements OnDrawListener {
+public class ViewCapture {
 
     private static final String TAG = "ViewCapture";
 
@@ -61,104 +70,27 @@
     // 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;
+    public static final MainThreadInitializedObject<ViewCapture> INSTANCE =
+            new MainThreadInitializedObject<>(ViewCapture::new);
 
-    private final Handler mHandler;
-    private final ViewRef mViewRef = new ViewRef();
+    private final List<WindowListener> mListeners = new ArrayList<>();
 
-    private int mFrameIndexBg = -1;
-    private final long[] mFrameTimesBg = new long[MEMORY_SIZE];
-    private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE];
+    private final Context mContext;
+    private final LooperExecutor mExecutor;
 
     // Pool used for capturing view tree on the UI thread.
     private ViewRef mPool = new ViewRef();
 
-    /**
-     * @param window the window for the capture data
-     */
-    public ViewCapture(Window window) {
-        mWindow = window;
-        mRoot = mWindow.getDecorView();
-        mResources = mRoot.getResources();
-        mHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::captureViewPropertiesBg);
-    }
-
-    /**
-     * Attaches the ViewCapture to the root
-     */
-    public void attach() {
-        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");
-        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;
+    private ViewCapture(Context context) {
+        mContext = context;
+        if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+            Looper looper = createAndStartNewLooper("ViewCapture",
+                    Process.THREAD_PRIORITY_FOREGROUND);
+            mExecutor = new LooperExecutor(looper);
+            mExecutor.execute(this::initPool);
+        } else {
+            mExecutor = UI_HELPER_EXECUTOR;
         }
-        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;
-        MAIN_EXECUTOR.execute(() -> addToPool(start, end));
-        return true;
     }
 
     @UiThread
@@ -177,36 +109,59 @@
             current = current.next;
         }
 
-        ViewRef end = current;
-        MAIN_EXECUTOR.execute(() ->  {
-            addToPool(start, end);
-            if (mRoot.isAttachedToWindow()) {
-                mRoot.getViewTreeObserver().addOnDrawListener(this);
-            }
-        });
-    }
-
-    private String getName() {
-        String title = mWindow.getAttributes().getTitle().toString();
-        return TextUtils.isEmpty(title) ? mWindow.toString() : title;
+        ViewRef finalCurrent = current;
+        MAIN_EXECUTOR.execute(() -> addToPool(start, finalCurrent));
     }
 
     /**
-     * Starts the dump process which is completed on closing the returned object.
+     * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
      */
-    public SafeCloseable beginDump(PrintWriter writer, FileDescriptor out) {
-        Future<ExportedData> task = UI_HELPER_EXECUTOR.submit(this::dumpToProto);
+    public SafeCloseable startCapture(Window window) {
+        String title = window.getAttributes().getTitle().toString();
+        String name = TextUtils.isEmpty(title) ? window.toString() : title;
+        return startCapture(window.getDecorView(), name);
+    }
 
+    /**
+     * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
+     */
+    public SafeCloseable startCapture(View view, String name) {
+        if (!FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+            return () -> { };
+        }
+
+        WindowListener listener = new WindowListener(view, name);
+        mExecutor.execute(() -> MAIN_EXECUTOR.execute(listener::attachToRoot));
+        mListeners.add(listener);
         return () -> {
+            mListeners.remove(listener);
+            listener.destroy();
+        };
+    }
+
+    /**
+     * Dumps all the active view captures
+     */
+    public void dump(PrintWriter writer, FileDescriptor out) {
+        if (!FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+            return;
+        }
+        ViewIdProvider idProvider = new ViewIdProvider(mContext.getResources());
+
+        // Collect all the tasks first so that all the tasks are posted on the executor
+        List<Pair<String, Future<ExportedData>>> tasks = mListeners.stream()
+                .map(l -> Pair.create(l.name, mExecutor.submit(() -> l.dumpToProto(idProvider))))
+                .collect(toList());
+
+        tasks.forEach(pair -> {
             writer.println();
             writer.println(" ContinuousViewCapture:");
-            writer.println(" window " + getName() + ":");
-            writer.println("  pkg:" + mRoot.getContext().getPackageName());
+            writer.println(" window " + pair.first + ":");
+            writer.println("  pkg:" + mContext.getPackageName());
             writer.print("  data:");
             writer.flush();
-
             try (OutputStream os = new FileOutputStream(out)) {
-                ExportedData data = task.get();
+                ExportedData data = pair.second.get();
                 OutputStream encodedOS = new GZIPOutputStream(new Base64OutputStream(os,
                         Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP));
                 data.writeTo(encodedOS);
@@ -217,51 +172,156 @@
             }
             writer.println();
             writer.println("--end--");
-        };
+        });
     }
 
-    @WorkerThread
-    private ExportedData dumpToProto() {
-        ExportedData.Builder dataBuilder = ExportedData.newBuilder();
-        Resources res = mResources;
-        ArrayList<Class> classList = new ArrayList<>();
+    private class WindowListener implements OnDrawListener {
 
-        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, classList, nodeBuilder);
-            dataBuilder.addFrameData(FrameData.newBuilder()
-                    .setNode(nodeBuilder)
-                    .setTimestamp(mFrameTimesBg[index]));
-        }
-        return dataBuilder
-                .addAllClassname(classList.stream().map(Class::getName).collect(toList()))
-                .build();
-    }
+        private final View mRoot;
+        public final String name;
 
-    private ViewRef captureViewTree(View view, ViewRef start) {
-        ViewRef ref;
-        if (mPool != null) {
-            ref = mPool;
-            mPool = mPool.next;
-            ref.next = null;
-        } else {
-            ref = new ViewRef();
+        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];
+
+        private boolean mDestroyed = false;
+
+        WindowListener(View view, String name) {
+            mRoot = view;
+            this.name = name;
+            mHandler = new Handler(mExecutor.getLooper(), this::captureViewPropertiesBg);
         }
-        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);
+
+        @Override
+        public void onDraw() {
+            Trace.beginSection("view_capture");
+            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;
             }
-            return result;
-        } else {
-            ref.childCount = 0;
-            return ref;
+            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;
+            MAIN_EXECUTOR.execute(() -> addToPool(start, end));
+            return true;
+        }
+
+        void attachToRoot() {
+            if (mRoot.isAttachedToWindow()) {
+                mRoot.getViewTreeObserver().addOnDrawListener(this);
+            } else {
+                mRoot.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View v) {
+                        if (!mDestroyed) {
+                            mRoot.getViewTreeObserver().addOnDrawListener(WindowListener.this);
+                        }
+                        mRoot.removeOnAttachStateChangeListener(this);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) { }
+                });
+            }
+        }
+
+        void destroy() {
+            mRoot.getViewTreeObserver().removeOnDrawListener(this);
+            mDestroyed = true;
+        }
+
+        @WorkerThread
+        private ExportedData dumpToProto(ViewIdProvider idProvider) {
+            ExportedData.Builder dataBuilder = ExportedData.newBuilder();
+            ArrayList<Class> 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(idProvider, classList, nodeBuilder);
+                dataBuilder.addFrameData(FrameData.newBuilder()
+                        .setNode(nodeBuilder)
+                        .setTimestamp(mFrameTimesBg[index]));
+            }
+            return dataBuilder
+                    .addAllClassname(classList.stream().map(Class::getName).collect(toList()))
+                    .build();
+        }
+
+        private ViewRef captureViewTree(View view, ViewRef start) {
+            ViewRef ref;
+            if (mPool != null) {
+                ref = mPool;
+                mPool = mPool.next;
+                ref.next = null;
+            } else {
+                ref = new ViewRef();
+            }
+            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;
+            }
         }
     }
 
@@ -318,18 +378,8 @@
          * at the end of the iteration.
          * @return
          */
-        public ViewPropertyRef toProto(Resources res, ArrayList<Class> classList,
+        public ViewPropertyRef toProto(ViewIdProvider idProvider, ArrayList<Class> classList,
                 ViewNode.Builder outBuilder) {
-            String resolvedId;
-            if (id >= 0) {
-                try {
-                    resolvedId = res.getResourceTypeName(id) + '/' + res.getResourceEntryName(id);
-                } catch (Resources.NotFoundException e) {
-                    resolvedId = "id/" + "0x" + Integer.toHexString(id).toUpperCase();
-                }
-            } else {
-                resolvedId = "NO_ID";
-            }
             int classnameIndex = classList.indexOf(clazz);
             if (classnameIndex < 0) {
                 classnameIndex = classList.size();
@@ -338,7 +388,7 @@
             outBuilder
                     .setClassnameIndex(classnameIndex)
                     .setHashcode(hashCode)
-                    .setId(resolvedId)
+                    .setId(idProvider.getName(id))
                     .setLeft(left)
                     .setTop(top)
                     .setWidth(right - left)
@@ -356,7 +406,7 @@
             ViewPropertyRef result = next;
             for (int i = 0; (i < childCount) && (result != null); i++) {
                 ViewNode.Builder childBuilder = ViewNode.newBuilder();
-                result = result.toProto(res, classList, childBuilder);
+                result = result.toProto(idProvider, classList, childBuilder);
                 outBuilder.addChildren(childBuilder);
             }
             return result;
@@ -368,4 +418,31 @@
         public int childCount = 0;
         public ViewRef next;
     }
+
+    private static final class ViewIdProvider {
+
+        private final SparseArray<String> mNames = new SparseArray<>();
+        private final Resources mRes;
+
+        ViewIdProvider(Resources res) {
+            mRes = res;
+        }
+
+        String getName(int id) {
+            String name = mNames.get(id);
+            if (name == null) {
+                if (id >= 0) {
+                    try {
+                        name = mRes.getResourceTypeName(id) + '/' + mRes.getResourceEntryName(id);
+                    } catch (Resources.NotFoundException e) {
+                        name = "id/" + "0x" + Integer.toHexString(id).toUpperCase();
+                    }
+                } else {
+                    name = "NO_ID";
+                }
+                mNames.put(id, name);
+            }
+            return name;
+        }
+    }
 }