Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7a6c6106 authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Moving view capture dump to a service to allow longer timeout" into tm-qpr-dev

parents 2ae4420a f2c6bf83
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -120,10 +120,12 @@ import com.android.launcher3.util.ObjectWrapper;
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 @@ public class QuickstepLauncher extends Launcher {
     */
    private PendingSplitSelectInfo mPendingSplitSelectInfo = null;

    private SafeCloseable mViewCapture;

    @Override
    protected void setupViews() {
        super.setupViews();
@@ -410,6 +414,7 @@ public class QuickstepLauncher extends Launcher {

        super.onDestroy();
        mHotseatPredictionController.destroy();
        mViewCapture.close();
    }

    @Override
@@ -509,6 +514,7 @@ public class QuickstepLauncher extends Launcher {
        }
        addMultiWindowModeChangedListener(mDepthController);
        initUnfoldTransitionProgressProvider();
        mViewCapture = ViewCapture.INSTANCE.get(this).startCapture(getWindow());
    }

    @Override
+3 −0
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
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 @@ public class TouchInteractionService extends Service
                createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
            }
            mTaskbarManager.dumpLogs("", pw);

            ViewCapture.INSTANCE.get(this).dump(pw, fd);
        }
    }

+0 −19
Original line number Diff line number Diff line
@@ -195,7 +195,6 @@ import com.android.launcher3.util.Thunk;
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 @@ public class Launcher extends StatefulActivity<LauncherState>
    private LauncherState mPrevLauncherState;

    private StringCache mStringCache;
    private ViewCapture mViewCapture;

    @Override
    @TargetApi(Build.VERSION_CODES.S)
@@ -1488,23 +1486,11 @@ public class Launcher extends StatefulActivity<LauncherState>
    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 @@ public class Launcher extends StatefulActivity<LauncherState>
     */
    @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 @@ public class Launcher extends StatefulActivity<LauncherState>
        mPopupDataProvider.dump(prefix, writer);
        mDeviceProfile.dump(this, prefix, writer);

        if (viewDump != null) {
            viewDump.close();
        }

        try {
            FileLog.flushAll(writer);
        } catch (Exception e) {
+238 −161
Original line number Diff line number Diff line
@@ -17,18 +17,25 @@ 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 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 android.view.Window;
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.FileOutputStream;
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,43 +70,129 @@ 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;
    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();

    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;
        }
    }

    @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 finalCurrent = current;
        MAIN_EXECUTOR.execute(() -> addToPool(start, finalCurrent));
    }

    /**
     * @param window the window for the capture data
     * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
     */
    public ViewCapture(Window window) {
        mWindow = window;
        mRoot = mWindow.getDecorView();
        mResources = mRoot.getResources();
        mHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::captureViewPropertiesBg);
    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 root
     * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
     */
    public void attach() {
        mHandler.post(this::initPool);
    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();
        };
    }

    /**
     * Removes a previously attached ViewCapture from the root
     * Dumps all the active view captures
     */
    public void detach() {
        mHandler.post(() -> MAIN_EXECUTOR.execute(
                () -> mRoot.getViewTreeObserver().removeOnDrawListener(this)));
    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 " + pair.first + ":");
            writer.println("  pkg:" + mContext.getPackageName());
            writer.print("  data:");
            writer.flush();
            try (OutputStream os = new FileOutputStream(out)) {
                ExportedData data = pair.second.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--");
        });
    }

    private class WindowListener implements OnDrawListener {

        private final View mRoot;
        public final String name;

        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);
        }

        @Override
@@ -161,76 +256,40 @@ public class ViewCapture implements OnDrawListener {
            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;
        MAIN_EXECUTOR.execute(() ->  {
            addToPool(start, end);
        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);
                    }

    private String getName() {
        String title = mWindow.getAttributes().getTitle().toString();
        return TextUtils.isEmpty(title) ? mWindow.toString() : title;
                    @Override
                    public void onViewDetachedFromWindow(View v) { }
                });
            }

    /**
     * Starts the dump process which is completed on closing the returned object.
     */
    public SafeCloseable beginDump(PrintWriter writer, FileDescriptor out) {
        Future<ExportedData> task = UI_HELPER_EXECUTOR.submit(this::dumpToProto);

        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--");
        };

        void destroy() {
            mRoot.getViewTreeObserver().removeOnDrawListener(this);
            mDestroyed = true;
        }

        @WorkerThread
    private ExportedData dumpToProto() {
        private ExportedData dumpToProto(ViewIdProvider idProvider) {
            ExportedData.Builder dataBuilder = ExportedData.newBuilder();
        Resources res = mResources;
            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(res, classList, nodeBuilder);
                mNodesBg[index].toProto(idProvider, classList, nodeBuilder);
                dataBuilder.addFrameData(FrameData.newBuilder()
                        .setNode(nodeBuilder)
                        .setTimestamp(mFrameTimesBg[index]));
@@ -264,6 +323,7 @@ public class ViewCapture implements OnDrawListener {
                return ref;
            }
        }
    }

    private static class ViewPropertyRef {
        // We store reference in memory to avoid generating and storing too many strings
@@ -318,18 +378,8 @@ public class ViewCapture implements OnDrawListener {
         * 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 @@ public class ViewCapture implements OnDrawListener {
            outBuilder
                    .setClassnameIndex(classnameIndex)
                    .setHashcode(hashCode)
                    .setId(resolvedId)
                    .setId(idProvider.getName(id))
                    .setLeft(left)
                    .setTop(top)
                    .setWidth(right - left)
@@ -356,7 +406,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, classList, childBuilder);
                result = result.toProto(idProvider, classList, childBuilder);
                outBuilder.addChildren(childBuilder);
            }
            return result;
@@ -368,4 +418,31 @@ public class ViewCapture implements OnDrawListener {
        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;
        }
    }
}