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

Commit f4510907 authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 13235988 from a7914bb3 to 25Q2-release

Change-Id: I23deb8fa7efdd2e7679f9736db4f9a82062b686f
parents deeec97a a7914bb3
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -98,6 +98,9 @@ public abstract class ViewCapture {

    private boolean mIsEnabled = true;

    @VisibleForTesting
    public boolean mIsStarted = false;

    protected ViewCapture(int memorySize, int initPoolSize, Executor bgExecutor) {
        mMemorySize = memorySize;
        mBgExecutor = bgExecutor;
@@ -128,6 +131,7 @@ public abstract class ViewCapture {
    @AnyThread
    @NonNull
    public SafeCloseable startCapture(@NonNull View view, @NonNull String name) {
        mIsStarted = true;
        WindowListener listener = new WindowListener(view, name);

        if (mIsEnabled) {
@@ -136,7 +140,7 @@ public abstract class ViewCapture {

        mListeners.add(listener);

        runOnUiThread(() -> view.getContext().registerComponentCallbacks(listener), view);
        view.getContext().registerComponentCallbacks(listener);

        return () -> {
            if (listener.mRoot != null && listener.mRoot.getContext() != null) {
@@ -160,6 +164,7 @@ public abstract class ViewCapture {
    @VisibleForTesting
    @AnyThread
    public void stopCapture(@NonNull View rootView) {
        mIsStarted = false;
        mListeners.forEach(it -> {
            if (rootView == it.mRoot) {
                runOnUiThread(() -> {
+22 −28
Original line number Diff line number Diff line
@@ -16,57 +16,51 @@

package com.android.app.viewcapture

import android.content.Context
import android.media.permission.SafeCloseable
import android.os.IBinder
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager

/** Tag for debug logging. */
private const val TAG = "ViewCaptureWindowManager"
import android.view.WindowManagerImpl

/**
 * Wrapper class for [WindowManager]. Adds [ViewCapture] to associated window when it is added to
 * view hierarchy.
 * [WindowManager] implementation to enable view tracing. Adds [ViewCapture] to associated window
 * when it is added to view hierarchy. Use [ViewCaptureAwareWindowManagerFactory] to create an
 * instance of this class.
 */
class ViewCaptureAwareWindowManager(
    private val windowManager: WindowManager,
    private val lazyViewCapture: Lazy<ViewCapture>,
    private val isViewCaptureEnabled: Boolean,
) : WindowManager by windowManager {
internal class ViewCaptureAwareWindowManager(
    private val context: Context,
    private val parent: Window? = null,
    private val windowContextToken: IBinder? = null,
) : WindowManagerImpl(context, parent, windowContextToken) {

    private var viewCaptureCloseableMap: MutableMap<View, SafeCloseable> = mutableMapOf()

    override fun addView(view: View, params: ViewGroup.LayoutParams?) {
        windowManager.addView(view, params)
        if (isViewCaptureEnabled) {
    override fun addView(view: View, params: ViewGroup.LayoutParams) {
        super.addView(view, params)
        val viewCaptureCloseable: SafeCloseable =
                lazyViewCapture.value.startCapture(view, getViewName(view))
            ViewCaptureFactory.getInstance(context).startCapture(view, getViewName(view))
        viewCaptureCloseableMap[view] = viewCaptureCloseable
    }
    }

    override fun removeView(view: View?) {
        removeViewFromCloseableMap(view)
        windowManager.removeView(view)
        super.removeView(view)
    }

    override fun removeViewImmediate(view: View?) {
        removeViewFromCloseableMap(view)
        windowManager.removeViewImmediate(view)
        super.removeViewImmediate(view)
    }

    private fun getViewName(view: View) = "." + view.javaClass.name

    private fun removeViewFromCloseableMap(view: View?) {
        if (isViewCaptureEnabled) {
        if (viewCaptureCloseableMap.containsKey(view)) {
            viewCaptureCloseableMap[view]?.close()
            viewCaptureCloseableMap.remove(view)
        }
    }
}

    interface Factory {
        fun create(windowManager: WindowManager): ViewCaptureAwareWindowManager
    }
}
+63 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.app.viewcapture

import android.content.Context
import android.os.IBinder
import android.os.Trace
import android.os.Trace.TRACE_TAG_APP
import android.view.Window
import android.view.WindowManager
import java.lang.ref.WeakReference
import java.util.Collections
import java.util.WeakHashMap


/** Factory to create [Context] specific instances of [ViewCaptureAwareWindowManager]. */
object ViewCaptureAwareWindowManagerFactory {

    /**
     * Keeps track of [ViewCaptureAwareWindowManager] instance for a [Context]. It is a
     * [WeakHashMap] to ensure that if a [Context] mapped in the [instanceMap] is destroyed, the map
     * entry is garbage collected as well.
     */
    private val instanceMap =
        Collections.synchronizedMap(WeakHashMap<Context, WeakReference<WindowManager>>())

    /**
     * Returns the weakly cached [ViewCaptureAwareWindowManager] instance for a given [Context]. If
     * no instance is cached; it creates, caches and returns a new instance.
     */
    @JvmStatic
    fun getInstance(
        context: Context,
        parent: Window? = null,
        windowContextToken: IBinder? = null,
    ): WindowManager {
        Trace.traceCounter(TRACE_TAG_APP,
            "ViewCaptureAwareWindowManagerFactory#instanceMap.size", instanceMap.size)

        val cachedWindowManager = instanceMap[context]?.get()
        if (cachedWindowManager != null) {
            return cachedWindowManager
        } else {
            val windowManager = ViewCaptureAwareWindowManager(context, parent, windowContextToken)
            instanceMap[context] = WeakReference(windowManager)
            return windowManager
        }
    }
}
+0 −16
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import android.content.Context
import android.os.Process
import android.tracing.Flags
import android.util.Log
import android.view.WindowManager

/**
 * Factory to create polymorphic instances of ViewCapture according to build configurations and
@@ -68,19 +67,4 @@ object ViewCaptureFactory {
        }
        return instance
    }

    /** Returns an instance of [ViewCaptureAwareWindowManager]. */
    @JvmStatic
    fun getViewCaptureAwareWindowManagerInstance(
        context: Context,
        isViewCaptureTracingEnabled: Boolean,
    ): ViewCaptureAwareWindowManager {
        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        val lazyViewCapture = lazy { getInstance(context) }
        return ViewCaptureAwareWindowManager(
            windowManager,
            lazyViewCapture,
            isViewCaptureTracingEnabled,
        )
    }
}
+23 −50
Original line number Diff line number Diff line
@@ -17,69 +17,42 @@
package com.android.app.viewcapture

import android.content.Context
import android.content.Intent
import android.testing.AndroidTestingRunner
import android.view.View
import android.view.WindowManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SmallTest
import org.junit.Before
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.invocation.InvocationOnMock
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

@RunWith(AndroidTestingRunner::class)
@SmallTest
class ViewCaptureAwareWindowManagerTest {
    private val context: Context = ApplicationProvider.getApplicationContext()
    private val mockRootView = mock<View>()
    private val windowManager = mock<WindowManager>()
    private val viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
    private val lazyViewCapture = mock<Lazy<ViewCapture>> { on { value } doReturn viewCaptureSpy }
    private var mViewCaptureAwareWindowManager: ViewCaptureAwareWindowManager? = null
    private val mContext: Context = InstrumentationRegistry.getInstrumentation().context
    private lateinit var mViewCaptureAwareWindowManager: ViewCaptureAwareWindowManager

    @Before
    fun setUp() {
        doAnswer { invocation: InvocationOnMock ->
                val view = invocation.getArgument<View>(0)
                val lp = invocation.getArgument<WindowManager.LayoutParams>(1)
                view.layoutParams = lp
                null
            }
            .`when`(windowManager)
            .addView(any(View::class.java), any(WindowManager.LayoutParams::class.java))
        `when`(mockRootView.context).thenReturn(context)
    }
    private val activityIntent = Intent(mContext, TestActivity::class.java)

    @Test
    fun testAddView_viewCaptureEnabled_verifyStartCaptureCall() {
        mViewCaptureAwareWindowManager =
            ViewCaptureAwareWindowManager(
                windowManager,
                lazyViewCapture,
                isViewCaptureEnabled = true
            )
        mViewCaptureAwareWindowManager?.addView(mockRootView, mockRootView.layoutParams)
        verify(viewCaptureSpy).startCapture(any(), anyString())
    }
    @get:Rule val activityScenarioRule = ActivityScenarioRule<TestActivity>(activityIntent)

    @Test
    fun testAddView_viewCaptureNotEnabled_verifyStartCaptureCall() {
        mViewCaptureAwareWindowManager =
            ViewCaptureAwareWindowManager(
                windowManager,
                lazyViewCapture,
                isViewCaptureEnabled = false
    fun testAddView_verifyStartCaptureCall() {
        activityScenarioRule.scenario.onActivity { activity ->
            mViewCaptureAwareWindowManager = ViewCaptureAwareWindowManager(mContext)

            val activityDecorView = activity.window.decorView
            // removing view since it is already added to view hierarchy on declaration
            mViewCaptureAwareWindowManager.removeView(activityDecorView)
            val viewCapture = ViewCaptureFactory.getInstance(mContext)

            mViewCaptureAwareWindowManager.addView(
                activityDecorView,
                activityDecorView.layoutParams as WindowManager.LayoutParams,
            )
        mViewCaptureAwareWindowManager?.addView(mockRootView, mockRootView.layoutParams)
        verify(viewCaptureSpy, times(0)).startCapture(any(), anyString())
            assertTrue(viewCapture.mIsStarted)
        }
    }
}