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

Commit 2f1561e8 authored by Stefan Andonian's avatar Stefan Andonian Committed by Android (Google) Code Review
Browse files

Merge "Add ViewCapture to all tests which implement AbstractLauncherUiTest." into udc-dev

parents ace90b2e 88a869d1
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);