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

Commit 88a869d1 authored by Stefan Andonian's avatar Stefan Andonian
Browse files

Add ViewCapture to all tests which implement AbstractLauncherUiTest.

This enables developers to watch failed tests in classes like
TaplTestsQuickstep in the go/web-hv tool. The view capture is only
attached to the application under test. If the test opens a different
application, then those frames will be missed.

Bug: 242867462
Test: Failed a test and verified that the TimeLapse bugreport entry
showed up properly in the go/web-hv tool.

Change-Id: I4a0741b5279f10ccaca92d0b9d846f5a8cce018a
parent ea924f3d
Loading
Loading
Loading
Loading
+2 −5
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.view.Choreographer
import android.view.View
import android.view.WindowManagerGlobal
import androidx.annotation.VisibleForTesting
import com.android.app.viewcapture.SimpleViewCapture
import com.android.app.viewcapture.ViewCapture
import com.android.app.viewcapture.data.MotionWindowData

@@ -43,7 +44,7 @@ import com.android.app.viewcapture.data.MotionWindowData
 * @see [DdmHandleMotionTool]
 */
class MotionToolManager private constructor(private val windowManagerGlobal: WindowManagerGlobal) {
    private val viewCapture: ViewCapture = SimpleViewCapture()
    private val viewCapture: ViewCapture = SimpleViewCapture("MTViewCapture")

    companion object {
        private const val TAG = "MotionToolManager"
@@ -135,10 +136,6 @@ class MotionToolManager private constructor(private val windowManagerGlobal: Win
    private fun getRootView(windowId: String): View? {
        return windowManagerGlobal.getRootView(windowId)
    }

    class SimpleViewCapture : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE,
            MAIN_EXECUTOR.submit { Choreographer.getInstance() }.get(),
            createAndStartNewLooperExecutor("MTViewCapture", Process.THREAD_PRIORITY_FOREGROUND))
}

private data class TraceMetadata(
+10 −1
Original line number Diff line number Diff line
@@ -24,12 +24,15 @@ import android.os.Looper
import android.os.ParcelFileDescriptor
import android.os.Process
import android.provider.Settings
import android.util.Log
import android.view.Choreographer
import android.window.IDumpCallback
import androidx.annotation.AnyThread
import androidx.annotation.VisibleForTesting
import java.util.concurrent.Executor

private val TAG = SettingsAwareViewCapture::class.java.simpleName

/**
 * ViewCapture that listens to system updates and enables / disables attached ViewCapture
 * WindowListeners accordingly. The Settings toggle is currently controlled by the Winscope
@@ -41,7 +44,13 @@ internal constructor(private val context: Context, choreographer: Choreographer,
    : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, choreographer, executor) {
    /** Dumps all the active view captures to the wm trace directory via LauncherAppService */
    private val mDumpCallback: IDumpCallback.Stub = object : IDumpCallback.Stub() {
        override fun onDump(out: ParcelFileDescriptor) = dumpTo(out, context)
        override fun onDump(out: ParcelFileDescriptor) {
            try {
                ParcelFileDescriptor.AutoCloseOutputStream(out).use { os -> dumpTo(os, context) }
            } catch (e: Exception) {
                Log.e(TAG, "failed to dump data to wm trace", e)
            }
        }
    }

    init {
+8 −0
Original line number Diff line number Diff line
package com.android.app.viewcapture

import android.os.Process
import android.view.Choreographer

open class SimpleViewCapture(threadName: String) : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE,
    MAIN_EXECUTOR.submit { Choreographer.getInstance() }.get(),
    createAndStartNewLooperExecutor(threadName, Process.THREAD_PRIORITY_FOREGROUND))
 No newline at end of file
+36 −17
Original line number Diff line number Diff line
@@ -21,10 +21,8 @@ import android.content.res.Resources;
import android.media.permission.SafeCloseable;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.Trace;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.View;
@@ -33,8 +31,10 @@ import android.view.ViewTreeObserver;
import android.view.Window;

import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;

import com.android.app.viewcapture.data.ExportedData;
@@ -120,6 +120,7 @@ public abstract class ViewCapture {
    /**
     * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
     */
    @NonNull
    public SafeCloseable startCapture(Window window) {
        String title = window.getAttributes().getTitle().toString();
        String name = TextUtils.isEmpty(title) ? window.toString() : title;
@@ -130,6 +131,7 @@ public abstract class ViewCapture {
     * Attaches the ViewCapture to the provided window and returns a handle to detach the listener.
     * Verifies that ViewCapture is enabled before actually attaching an onDrawListener.
     */
    @NonNull
    public SafeCloseable startCapture(View view, String name) {
        WindowListener listener = new WindowListener(view, name);
        if (mIsEnabled) MAIN_EXECUTOR.execute(listener::attachToRoot);
@@ -140,6 +142,25 @@ public abstract class ViewCapture {
        };
    }

    /**
     * Launcher checks for leaks in many spots during its instrumented tests. The WindowListeners
     * appear to have leaks because they store mRoot views. In reality, attached views close their
     * respective window listeners when they are destroyed.
     * <p>
     * This method deletes detaches and deletes mRoot views from windowListeners. This makes the
     * WindowListeners unusable for anything except dumping previously captured information. They
     * are still technically enabled to allow for dumping.
     */
    @VisibleForTesting
    public void stopCapture(@NonNull View rootView) {
        mListeners.forEach(it -> {
            if (rootView == it.mRoot) {
                it.mRoot.getViewTreeObserver().removeOnDrawListener(it);
                it.mRoot = null;
            }
        });
    }

    @UiThread
    protected void enableOrDisableWindowListeners(boolean isEnabled) {
        mIsEnabled = isEnabled;
@@ -148,23 +169,18 @@ public abstract class ViewCapture {
    }

    @AnyThread
    protected void dumpTo(ParcelFileDescriptor outFd, Context context) {
    public void dumpTo(OutputStream os, Context context)
            throws InterruptedException, ExecutionException, IOException {
        if (!mIsEnabled) {
            return;
        }
        ArrayList<Class> classList = new ArrayList<>();
        try (OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(outFd)) {
        ExportedData.newBuilder()
                .setPackage(context.getPackageName())
                .addAllWindowData(getWindowData(context, classList, l -> l.mIsActive).get())
                .addAllClassname(toStringList(classList))
                .build()
                .writeTo(os);
        } catch (InterruptedException | ExecutionException e) {
            Log.e(TAG, "failed to get window data", e);
        } catch (IOException e) {
            Log.e(TAG, "failed to output data to wm trace", e);
        }
    }

    private static List<String> toStringList(List<Class> classList) {
@@ -232,7 +248,8 @@ public abstract class ViewCapture {
     */
    private class WindowListener implements ViewTreeObserver.OnDrawListener {

        public final View mRoot;
        @Nullable // Nullable in tests only
        public View mRoot;
        public final String name;

        private final ViewRef mViewRef = new ViewRef();
@@ -378,8 +395,10 @@ public abstract class ViewCapture {

        void detachFromRoot() {
            mIsActive = false;
            if (mRoot != null) {
                mRoot.getViewTreeObserver().removeOnDrawListener(this);
            }
        }

        private void safelyEnableOnDrawListener() {
            mRoot.getViewTreeObserver().removeOnDrawListener(this);