Loading viewcapturelib/src/com/android/app/viewcapture/SettingsAwareViewCapture.ktdeleted 100644 → 0 +0 −86 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.content.pm.LauncherApps import android.database.ContentObserver import android.os.Handler import android.os.ParcelFileDescriptor import android.provider.Settings import android.util.Log 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 * developer tile in the System developer options. */ internal class SettingsAwareViewCapture internal constructor(private val context: Context, executor: Executor) : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, 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) { try { ParcelFileDescriptor.AutoCloseOutputStream(out).use { os -> dumpTo(os, context) } } catch (e: Exception) { Log.e(TAG, "failed to dump data to wm trace", e) } } } init { enableOrDisableWindowListeners() mBgExecutor.execute { context.contentResolver.registerContentObserver( Settings.Global.getUriFor(VIEW_CAPTURE_ENABLED), false, object : ContentObserver(Handler()) { override fun onChange(selfChange: Boolean) { enableOrDisableWindowListeners() } }) } } @AnyThread private fun enableOrDisableWindowListeners() { mBgExecutor.execute { val isEnabled = Settings.Global.getInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 0) != 0 MAIN_EXECUTOR.execute { enableOrDisableWindowListeners(isEnabled) } val launcherApps = context.getSystemService(LauncherApps::class.java) if (isEnabled) { launcherApps?.registerDumpCallback(mDumpCallback) } else { launcherApps?.unRegisterDumpCallback(mDumpCallback) } } } companion object { @VisibleForTesting internal const val VIEW_CAPTURE_ENABLED = "view_capture_enabled" } } No newline at end of file viewcapturelib/src/com/android/app/viewcapture/ViewCaptureFactory.kt +12 −26 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.app.viewcapture import android.content.Context import android.os.Process import android.tracing.Flags import android.util.Log /** Loading @@ -31,22 +30,10 @@ object ViewCaptureFactory { private lateinit var appContext: Context private fun createInstance(): ViewCapture { return when { !android.os.Build.IS_DEBUGGABLE -> { return if (!android.os.Build.IS_DEBUGGABLE) { Log.i(TAG, "instantiating ${NoOpViewCapture::class.java.simpleName}") NoOpViewCapture() } !Flags.perfettoViewCaptureTracing() -> { Log.i(TAG, "instantiating ${SettingsAwareViewCapture::class.java.simpleName}") SettingsAwareViewCapture( appContext, ViewCapture.createAndStartNewLooperExecutor( "SAViewCapture", Process.THREAD_PRIORITY_FOREGROUND, ), ) } else -> { } else { Log.i(TAG, "instantiating ${PerfettoViewCapture::class.java.simpleName}") PerfettoViewCapture( appContext, Loading @@ -57,7 +44,6 @@ object ViewCaptureFactory { ) } } } /** Returns an instance of [ViewCapture]. */ @JvmStatic Loading viewcapturelib/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.ktdeleted 100644 → 0 +0 −100 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.Manifest import android.content.Context import android.content.Intent import android.media.permission.SafeCloseable import android.provider.Settings import android.testing.AndroidTestingRunner import android.view.Choreographer import android.view.View import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import com.android.app.viewcapture.SettingsAwareViewCapture.Companion.VIEW_CAPTURE_ENABLED import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR import junit.framework.Assert.assertEquals import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) class SettingsAwareViewCaptureTest { private val context: Context = InstrumentationRegistry.getInstrumentation().context private val activityIntent = Intent(context, TestActivity::class.java) @get:Rule val activityScenarioRule = ActivityScenarioRule<TestActivity>(activityIntent) @get:Rule val grantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_SECURE_SETTINGS) @Test fun do_not_capture_view_hierarchies_if_setting_is_disabled() { Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 0) activityScenarioRule.scenario.onActivity { activity -> val viewCapture: ViewCapture = SettingsAwareViewCapture(context, MAIN_EXECUTOR) val rootView: View = activity.requireViewById(android.R.id.content) val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") Choreographer.getInstance().postFrameCallback { rootView.viewTreeObserver.dispatchOnDraw() assertEquals( 0, viewCapture .getDumpTask(activity.requireViewById(android.R.id.content)) .get() .get() .frameDataList .size ) closeable.close() } } } @Test fun capture_view_hierarchies_if_setting_is_enabled() { Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 1) activityScenarioRule.scenario.onActivity { activity -> val viewCapture: ViewCapture = SettingsAwareViewCapture(context, MAIN_EXECUTOR) val rootView: View = activity.requireViewById(android.R.id.content) val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") Choreographer.getInstance().postFrameCallback { rootView.viewTreeObserver.dispatchOnDraw() assertEquals( 1, viewCapture .getDumpTask(activity.requireViewById(android.R.id.content)) .get() .get() .frameDataList .size ) closeable.close() } } } } Loading
viewcapturelib/src/com/android/app/viewcapture/SettingsAwareViewCapture.ktdeleted 100644 → 0 +0 −86 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.content.pm.LauncherApps import android.database.ContentObserver import android.os.Handler import android.os.ParcelFileDescriptor import android.provider.Settings import android.util.Log 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 * developer tile in the System developer options. */ internal class SettingsAwareViewCapture internal constructor(private val context: Context, executor: Executor) : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, 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) { try { ParcelFileDescriptor.AutoCloseOutputStream(out).use { os -> dumpTo(os, context) } } catch (e: Exception) { Log.e(TAG, "failed to dump data to wm trace", e) } } } init { enableOrDisableWindowListeners() mBgExecutor.execute { context.contentResolver.registerContentObserver( Settings.Global.getUriFor(VIEW_CAPTURE_ENABLED), false, object : ContentObserver(Handler()) { override fun onChange(selfChange: Boolean) { enableOrDisableWindowListeners() } }) } } @AnyThread private fun enableOrDisableWindowListeners() { mBgExecutor.execute { val isEnabled = Settings.Global.getInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 0) != 0 MAIN_EXECUTOR.execute { enableOrDisableWindowListeners(isEnabled) } val launcherApps = context.getSystemService(LauncherApps::class.java) if (isEnabled) { launcherApps?.registerDumpCallback(mDumpCallback) } else { launcherApps?.unRegisterDumpCallback(mDumpCallback) } } } companion object { @VisibleForTesting internal const val VIEW_CAPTURE_ENABLED = "view_capture_enabled" } } No newline at end of file
viewcapturelib/src/com/android/app/viewcapture/ViewCaptureFactory.kt +12 −26 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.app.viewcapture import android.content.Context import android.os.Process import android.tracing.Flags import android.util.Log /** Loading @@ -31,22 +30,10 @@ object ViewCaptureFactory { private lateinit var appContext: Context private fun createInstance(): ViewCapture { return when { !android.os.Build.IS_DEBUGGABLE -> { return if (!android.os.Build.IS_DEBUGGABLE) { Log.i(TAG, "instantiating ${NoOpViewCapture::class.java.simpleName}") NoOpViewCapture() } !Flags.perfettoViewCaptureTracing() -> { Log.i(TAG, "instantiating ${SettingsAwareViewCapture::class.java.simpleName}") SettingsAwareViewCapture( appContext, ViewCapture.createAndStartNewLooperExecutor( "SAViewCapture", Process.THREAD_PRIORITY_FOREGROUND, ), ) } else -> { } else { Log.i(TAG, "instantiating ${PerfettoViewCapture::class.java.simpleName}") PerfettoViewCapture( appContext, Loading @@ -57,7 +44,6 @@ object ViewCaptureFactory { ) } } } /** Returns an instance of [ViewCapture]. */ @JvmStatic Loading
viewcapturelib/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.ktdeleted 100644 → 0 +0 −100 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.Manifest import android.content.Context import android.content.Intent import android.media.permission.SafeCloseable import android.provider.Settings import android.testing.AndroidTestingRunner import android.view.Choreographer import android.view.View import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import com.android.app.viewcapture.SettingsAwareViewCapture.Companion.VIEW_CAPTURE_ENABLED import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR import junit.framework.Assert.assertEquals import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) class SettingsAwareViewCaptureTest { private val context: Context = InstrumentationRegistry.getInstrumentation().context private val activityIntent = Intent(context, TestActivity::class.java) @get:Rule val activityScenarioRule = ActivityScenarioRule<TestActivity>(activityIntent) @get:Rule val grantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_SECURE_SETTINGS) @Test fun do_not_capture_view_hierarchies_if_setting_is_disabled() { Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 0) activityScenarioRule.scenario.onActivity { activity -> val viewCapture: ViewCapture = SettingsAwareViewCapture(context, MAIN_EXECUTOR) val rootView: View = activity.requireViewById(android.R.id.content) val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") Choreographer.getInstance().postFrameCallback { rootView.viewTreeObserver.dispatchOnDraw() assertEquals( 0, viewCapture .getDumpTask(activity.requireViewById(android.R.id.content)) .get() .get() .frameDataList .size ) closeable.close() } } } @Test fun capture_view_hierarchies_if_setting_is_enabled() { Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 1) activityScenarioRule.scenario.onActivity { activity -> val viewCapture: ViewCapture = SettingsAwareViewCapture(context, MAIN_EXECUTOR) val rootView: View = activity.requireViewById(android.R.id.content) val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") Choreographer.getInstance().postFrameCallback { rootView.viewTreeObserver.dispatchOnDraw() assertEquals( 1, viewCapture .getDumpTask(activity.requireViewById(android.R.id.content)) .get() .get() .frameDataList .size ) closeable.close() } } } }