diff options
| author | 2023-07-07 03:37:22 +0000 | |
|---|---|---|
| committer | 2023-07-07 03:37:22 +0000 | |
| commit | dc180e5e1a0f1036ffc2d3a04f07e5c6f5b36b4a (patch) | |
| tree | d54cd58b804fc9bda6511ea476d8f7b32d841849 | |
| parent | c41e070125d6c627cdc06fe07b4cdeec1b6e5d53 (diff) | |
| parent | aae1baa5c8f2bd9e723251c68b647a97d3e869b1 (diff) | |
Merge "Migrating code from systemui to common library." into udc-qpr-dev am: e13db76147 am: aae1baa5c8
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23923591
Change-Id: Ic847bc0e6db53eadb2b5a16a82e48b18d53054eb
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
15 files changed, 0 insertions, 1065 deletions
diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp deleted file mode 100644 index 555f42ef9881..000000000000 --- a/packages/SystemUI/compose/testing/Android.bp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2022 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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_library { - name: "SystemUIComposeTesting", - manifest: "AndroidManifest.xml", - - srcs: [ - "src/**/*.kt", - ], - - static_libs: [ - "PlatformComposeCore", - "SystemUIScreenshotLib", - - "androidx.compose.runtime_runtime", - "androidx.compose.material3_material3", - "androidx.compose.ui_ui-test-junit4", - "androidx.compose.ui_ui-test-manifest", - ], - - kotlincflags: ["-Xjvm-default=all"], -} diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml deleted file mode 100644 index b1f7c3be2796..000000000000 --- a/packages/SystemUI/compose/testing/AndroidManifest.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - package="com.android.systemui.testing.compose"> - <application - android:appComponentFactory="androidx.core.app.AppComponentFactory" - tools:replace="android:appComponentFactory"> - </application> -</manifest> diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt deleted file mode 100644 index cb9e53ce4d33..000000000000 --- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2022 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.systemui.testing.compose - -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.ViewRootForTest -import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onRoot -import com.android.compose.theme.PlatformTheme -import com.android.systemui.testing.screenshot.ScreenshotActivity -import com.android.systemui.testing.screenshot.SystemUIGoldenImagePathManager -import com.android.systemui.testing.screenshot.UnitTestBitmapMatcher -import com.android.systemui.testing.screenshot.drawIntoBitmap -import org.junit.rules.RuleChain -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import platform.test.screenshot.DeviceEmulationRule -import platform.test.screenshot.DeviceEmulationSpec -import platform.test.screenshot.MaterialYouColorsRule -import platform.test.screenshot.ScreenshotTestRule -import platform.test.screenshot.getEmulatedDevicePathConfig - -/** A rule for Compose screenshot diff tests. */ -class ComposeScreenshotTestRule( - emulationSpec: DeviceEmulationSpec, - assetPathRelativeToBuildRoot: String -) : TestRule { - private val colorsRule = MaterialYouColorsRule() - private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) - private val screenshotRule = - ScreenshotTestRule( - SystemUIGoldenImagePathManager( - getEmulatedDevicePathConfig(emulationSpec), - assetPathRelativeToBuildRoot - ) - ) - private val composeRule = createAndroidComposeRule<ScreenshotActivity>() - private val delegateRule = - RuleChain.outerRule(colorsRule) - .around(deviceEmulationRule) - .around(screenshotRule) - .around(composeRule) - private val matcher = UnitTestBitmapMatcher - - override fun apply(base: Statement, description: Description): Statement { - return delegateRule.apply(base, description) - } - - /** - * Compare [content] with the golden image identified by [goldenIdentifier] in the context of - * [testSpec]. - */ - fun screenshotTest( - goldenIdentifier: String, - content: @Composable () -> Unit, - ) { - // Make sure that the activity draws full screen and fits the whole display instead of the - // system bars. - val activity = composeRule.activity - activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) } - - // Set the content using the AndroidComposeRule to make sure that the Activity is set up - // correctly. - composeRule.setContent { - PlatformTheme { - Surface( - color = MaterialTheme.colorScheme.background, - ) { - content() - } - } - } - composeRule.waitForIdle() - - val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view - screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher) - } -} diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp deleted file mode 100644 index f449398fc9f8..000000000000 --- a/packages/SystemUI/screenshot/Android.bp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2022 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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_library { - name: "SystemUIScreenshotLib", - manifest: "AndroidManifest.xml", - - srcs: [ - "src/**/*.kt", - ], - - resource_dirs: [ - "res", - ], - - static_libs: [ - "SystemUI-core", - "androidx.test.espresso.core", - "androidx.appcompat_appcompat", - "platform-screenshot-diff-core", - "guava", - ], - - kotlincflags: ["-Xjvm-default=all"], -} diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml deleted file mode 100644 index ba3dc8c53fff..000000000000 --- a/packages/SystemUI/screenshot/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.systemui.testing.screenshot"> - <application> - <activity - android:name="com.android.systemui.testing.screenshot.ScreenshotActivity" - android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" - android:exported="true" - android:theme="@style/Theme.SystemUI.Screenshot" /> - </application> -</manifest> diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml deleted file mode 100644 index a7f8a264e892..000000000000 --- a/packages/SystemUI/screenshot/res/values/themes.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 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. ---> -<resources> - <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI"> - <item name="android:windowActionBar">false</item> - <item name="android:windowNoTitle">true</item> - - <!-- We make the status and navigation bars transparent so that the screenshotted content is - not clipped by the status bar height when drawn into the Bitmap (which is what happens - given that we draw the view into the Bitmap using hardware acceleration). --> - <item name="android:statusBarColor">@android:color/transparent</item> - <item name="android:navigationBarColor">@android:color/transparent</item> - - <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests --> - <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> - </style> -</resources>
\ No newline at end of file diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt deleted file mode 100644 index a4a70a49fce3..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2022 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.systemui.testing.screenshot - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.os.Build -import android.view.View -import platform.test.screenshot.matchers.MSSIMMatcher -import platform.test.screenshot.matchers.PixelPerfectMatcher - -/** Draw this [View] into a [Bitmap]. */ -// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their -// tests. -fun View.drawIntoBitmap(): Bitmap { - val bitmap = - Bitmap.createBitmap( - measuredWidth, - measuredHeight, - Bitmap.Config.ARGB_8888, - ) - val canvas = Canvas(bitmap) - draw(canvas) - return bitmap -} - -/** - * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for - * screenshot *unit* tests. - */ -val UnitTestBitmapMatcher = - if (Build.CPU_ABI == "x86_64") { - // Different CPU architectures can sometimes end up rendering differently, so we can't do - // pixel-perfect matching on different architectures using the same golden. Given that our - // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the - // x86_64 architecture and use the Structural Similarity Index on others. - // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can - // do pixel perfect matching both at presubmit time and at development time with actual - // devices. - PixelPerfectMatcher() - } else { - MSSIMMatcher() - } - -/** - * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for - * screenshot *unit* tests. - * - * We use the Structural Similarity Index for integration tests because they usually contain - * additional information and noise that shouldn't break the test. - */ -val IntegrationTestBitmapMatcher = MSSIMMatcher() diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt deleted file mode 100644 index e032bb9b7e30..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2022 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.systemui.testing.screenshot - -import android.app.Activity -import android.graphics.Color -import android.view.View -import android.view.Window -import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowInsetsControllerCompat -import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.rules.RuleChain -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import platform.test.screenshot.* - -/** - * A rule that allows to run a screenshot diff test on a view that is hosted in another activity. - */ -class ExternalViewScreenshotTestRule( - emulationSpec: DeviceEmulationSpec, - assetPathRelativeToBuildRoot: String -) : TestRule { - - private val colorsRule = MaterialYouColorsRule() - private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) - private val screenshotRule = - ScreenshotTestRule( - SystemUIGoldenImagePathManager( - getEmulatedDevicePathConfig(emulationSpec), - assetPathRelativeToBuildRoot - ) - ) - private val delegateRule = - RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule) - private val matcher = UnitTestBitmapMatcher - - override fun apply(base: Statement, description: Description): Statement { - return delegateRule.apply(base, description) - } - - /** - * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in - * the context of [emulationSpec]. Window must be specified to capture views that render - * hardware buffers. - */ - fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) { - view.removeElevationRecursively() - - ScreenshotRuleAsserter.Builder(screenshotRule) - .setScreenshotProvider { view.toBitmap(window) } - .withMatcher(matcher) - .build() - .assertGoldenImage(goldenIdentifier) - } - - /** - * Compare the content of the [activity] with the golden image identified by [goldenIdentifier] - * in the context of [emulationSpec]. - */ - fun activityScreenshotTest( - goldenIdentifier: String, - activity: Activity, - ) { - val rootView = activity.window.decorView - - // Hide system bars, remove insets, focus and make sure device-specific cutouts - // don't affect screenshots - InstrumentationRegistry.getInstrumentation().runOnMainSync { - val window = activity.window - window.setDecorFitsSystemWindows(false) - WindowInsetsControllerCompat(window, rootView).apply { - hide(WindowInsetsCompat.Type.systemBars()) - systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - } - - window.statusBarColor = Color.TRANSPARENT - window.navigationBarColor = Color.TRANSPARENT - window.attributes = - window.attributes.apply { - layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES - } - - rootView.removeInsetsRecursively() - activity.currentFocus?.clearFocus() - } - - screenshotTest(goldenIdentifier, rootView, activity.window) - } -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt deleted file mode 100644 index 2a55a80eb7f4..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2022 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.systemui.testing.screenshot - -import androidx.activity.ComponentActivity - -/** The Activity that is launched and whose content is set for screenshot tests. */ -class ScreenshotActivity : ComponentActivity() diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt deleted file mode 100644 index 72d8c5a09852..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2022 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.systemui.testing.screenshot - -import androidx.test.platform.app.InstrumentationRegistry -import platform.test.screenshot.GoldenImagePathManager -import platform.test.screenshot.PathConfig - -/** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */ -class SystemUIGoldenImagePathManager( - pathConfig: PathConfig, - assetsPathRelativeToBuildRoot: String -) : - GoldenImagePathManager( - appContext = InstrumentationRegistry.getInstrumentation().context, - assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot, - deviceLocalPath = - InstrumentationRegistry.getInstrumentation() - .targetContext - .filesDir - .absolutePath - .toString() + "/sysui_screenshots", - pathConfig = pathConfig, - ) { - override fun toString(): String { - // This string is appended to all actual/expected screenshots on the device, so make sure - // it is a static value. - return "SystemUIGoldenImagePathManager" - } -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt deleted file mode 100644 index 98e9aaf44ceb..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2022 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.systemui.testing.screenshot - -import android.app.Activity -import android.content.Intent -import androidx.core.app.AppComponentFactory - -class TestAppComponentFactory : AppComponentFactory() { - - init { - instance = this - } - - private val overrides: MutableMap<String, () -> Activity> = hashMapOf() - - fun clearOverrides() { - overrides.clear() - } - - fun <T : Activity> registerActivityOverride(activity: Class<T>, provider: () -> T) { - overrides[activity.name] = provider - } - - override fun instantiateActivityCompat( - cl: ClassLoader, - className: String, - intent: Intent? - ): Activity { - return overrides - .getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) } - .invoke() - } - - companion object { - - private var instance: TestAppComponentFactory? = null - - fun getInstance(): TestAppComponentFactory = - instance - ?: error( - "TestAppComponentFactory is not initialized, " + - "did you specify it in the manifest?" - ) - } -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt deleted file mode 100644 index b84d26a7034a..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022 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.systemui.testing.screenshot - -import android.view.View -import android.view.ViewGroup -import com.android.systemui.util.children -import android.view.WindowInsets - -/** - * Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to - * disable it for any view in the hierarchy. - */ -fun View.removeElevationRecursively() { - this.elevation = 0f - (this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively) -} - -/** - * Different devices could have different insets (e.g. different height of the navigation bar or - * taskbar). This method dispatches empty insets to the whole view hierarchy and removes - * the original listener, so the views won't receive real insets. - */ -fun View.removeInsetsRecursively() { - this.dispatchApplyWindowInsets(WindowInsets.CONSUMED) - this.setOnApplyWindowInsetsListener(null) - (this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively) -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt deleted file mode 100644 index dedf0a7a742d..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt +++ /dev/null @@ -1,233 +0,0 @@ -package com.android.systemui.testing.screenshot - -import android.annotation.WorkerThread -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.HardwareRenderer -import android.graphics.Rect -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.util.Log -import android.view.PixelCopy -import android.view.SurfaceView -import android.view.View -import android.view.ViewTreeObserver -import android.view.Window -import androidx.annotation.RequiresApi -import androidx.concurrent.futures.ResolvableFuture -import androidx.test.annotation.ExperimentalTestApi -import androidx.test.core.internal.os.HandlerExecutor -import androidx.test.espresso.Espresso -import androidx.test.platform.graphics.HardwareRendererCompat -import com.google.common.util.concurrent.FutureCallback -import com.google.common.util.concurrent.Futures -import com.google.common.util.concurrent.ListenableFuture -import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.runBlocking - -/* - * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to - * [View.captureToBitmap]. - * TODO(b/195673633): Remove this fork and use the AndroidX version instead. - */ - -/** - * Asynchronously captures an image of the underlying view into a [Bitmap]. - * - * For devices below [Build.VERSION_CODES#O] (or if the view's window cannot be determined), the - * image is obtained using [View#draw]. Otherwise, [PixelCopy] is used. - * - * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required. - * - * This API is primarily intended for use in lower layer libraries or frameworks. For test authors, - * its recommended to use espresso or compose's captureToImage. - * - * This API is currently experimental and subject to change or removal. - */ -@ExperimentalTestApi -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) -fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> { - val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create() - val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper())) - val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false - - // disable drawing again if necessary once work is complete - if (!HardwareRendererCompat.isDrawingEnabled()) { - HardwareRendererCompat.setDrawingEnabled(true) - bitmapFuture.addListener({ HardwareRendererCompat.setDrawingEnabled(false) }, mainExecutor) - } - - mainExecutor.execute { - if (isRobolectric) { - generateBitmap(bitmapFuture) - } else { - val forceRedrawFuture = forceRedraw() - forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor) - } - } - - return bitmapFuture -} - -/** - * Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of - * [captureToBitmap]. - */ -@WorkerThread -@ExperimentalTestApi -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) -fun View.toBitmap(window: Window? = null): Bitmap { - if (Looper.getMainLooper() == Looper.myLooper()) { - error("toBitmap() can't be called from the main thread") - } - - if (!HardwareRenderer.isDrawingEnabled()) { - error("Hardware rendering is not enabled") - } - - // Make sure we are idle. - Espresso.onIdle() - - val mainExecutor = context.mainExecutor - return runBlocking { - suspendCoroutine { continuation -> - Futures.addCallback( - captureToBitmap(window), - object : FutureCallback<Bitmap> { - override fun onSuccess(result: Bitmap?) { - continuation.resumeWith(Result.success(result!!)) - } - - override fun onFailure(t: Throwable) { - continuation.resumeWith(Result.failure(t)) - } - }, - // We know that we are not on the main thread, so we can block the current - // thread and wait for the result in the main thread. - mainExecutor, - ) - } - } -} - -/** - * Trigger a redraw of the given view. - * - * Should only be called on UI thread. - * - * @return a [ListenableFuture] that will be complete once ui drawing is complete - */ -// NoClassDefFoundError occurs on API 15 -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) -// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -@ExperimentalTestApi -fun View.forceRedraw(): ListenableFuture<Void> { - val future: ResolvableFuture<Void> = ResolvableFuture.create() - - if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated) { - viewTreeObserver.registerFrameCommitCallback() { future.set(null) } - } else { - viewTreeObserver.addOnDrawListener( - object : ViewTreeObserver.OnDrawListener { - var handled = false - override fun onDraw() { - if (!handled) { - handled = true - future.set(null) - // cannot remove on draw listener inside of onDraw - Handler(Looper.getMainLooper()).post { - viewTreeObserver.removeOnDrawListener(this) - } - } - } - } - ) - } - invalidate() - return future -} - -private fun View.generateBitmap( - bitmapFuture: ResolvableFuture<Bitmap>, - window: Window? = null, -) { - if (bitmapFuture.isCancelled) { - return - } - val destBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - when { - Build.VERSION.SDK_INT < 26 -> generateBitmapFromDraw(destBitmap, bitmapFuture) - this is SurfaceView -> generateBitmapFromSurfaceViewPixelCopy(destBitmap, bitmapFuture) - else -> { - val window = window ?: getActivity()?.window - if (window != null) { - generateBitmapFromPixelCopy(window, destBitmap, bitmapFuture) - } else { - Log.i( - "View.captureToImage", - "Could not find window for view. Falling back to View#draw instead of PixelCopy" - ) - generateBitmapFromDraw(destBitmap, bitmapFuture) - } - } - } -} - -@SuppressWarnings("NewApi") -private fun SurfaceView.generateBitmapFromSurfaceViewPixelCopy( - destBitmap: Bitmap, - bitmapFuture: ResolvableFuture<Bitmap> -) { - val onCopyFinished = - PixelCopy.OnPixelCopyFinishedListener { result -> - if (result == PixelCopy.SUCCESS) { - bitmapFuture.set(destBitmap) - } else { - bitmapFuture.setException( - RuntimeException(String.format("PixelCopy failed: %d", result)) - ) - } - } - PixelCopy.request(this, null, destBitmap, onCopyFinished, handler) -} - -internal fun View.generateBitmapFromDraw( - destBitmap: Bitmap, - bitmapFuture: ResolvableFuture<Bitmap> -) { - destBitmap.density = resources.displayMetrics.densityDpi - computeScroll() - val canvas = Canvas(destBitmap) - canvas.translate((-scrollX).toFloat(), (-scrollY).toFloat()) - draw(canvas) - bitmapFuture.set(destBitmap) -} - -private fun View.getActivity(): Activity? { - fun Context.getActivity(): Activity? { - return when (this) { - is Activity -> this - is ContextWrapper -> this.baseContext.getActivity() - else -> null - } - } - return context.getActivity() -} - -private fun View.generateBitmapFromPixelCopy( - window: Window, - destBitmap: Bitmap, - bitmapFuture: ResolvableFuture<Bitmap> -) { - val locationInWindow = intArrayOf(0, 0) - getLocationInWindow(locationInWindow) - val x = locationInWindow[0] - val y = locationInWindow[1] - val boundsInWindow = Rect(x, y, x + width, y + height) - - return window.generateBitmapFromPixelCopy(boundsInWindow, destBitmap, bitmapFuture) -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt deleted file mode 100644 index 070a45170d04..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2022 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.systemui.testing.screenshot - -import android.app.Activity -import android.app.Dialog -import android.graphics.Bitmap -import android.os.Build -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import androidx.activity.ComponentActivity -import androidx.test.ext.junit.rules.ActivityScenarioRule -import java.util.concurrent.TimeUnit -import org.junit.Assert.assertEquals -import org.junit.rules.RuleChain -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import platform.test.screenshot.DeviceEmulationRule -import platform.test.screenshot.DeviceEmulationSpec -import platform.test.screenshot.MaterialYouColorsRule -import platform.test.screenshot.ScreenshotTestRule -import platform.test.screenshot.getEmulatedDevicePathConfig -import platform.test.screenshot.matchers.BitmapMatcher - -/** A rule for View screenshot diff unit tests. */ -open class ViewScreenshotTestRule( - emulationSpec: DeviceEmulationSpec, - private val matcher: BitmapMatcher = UnitTestBitmapMatcher, - assetsPathRelativeToBuildRoot: String -) : TestRule { - private val colorsRule = MaterialYouColorsRule() - private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) - protected val screenshotRule = - ScreenshotTestRule( - SystemUIGoldenImagePathManager( - getEmulatedDevicePathConfig(emulationSpec), - assetsPathRelativeToBuildRoot - ) - ) - private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java) - private val roboRule = - RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule) - private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule) - private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false - - override fun apply(base: Statement, description: Description): Statement { - if (isRobolectric) { - // In robolectric mode, we enable NATIVE graphics and unpack font and icu files. - // We need to use reflection, as this library is only needed and therefore - // only available in deviceless mode. - val nativeLoaderClassName = "org.robolectric.nativeruntime.DefaultNativeRuntimeLoader" - val defaultNativeRuntimeLoader = Class.forName(nativeLoaderClassName) - System.setProperty("robolectric.graphicsMode", "NATIVE") - defaultNativeRuntimeLoader.getMethod("injectAndLoad").invoke(null) - } - val ruleToApply = if (isRobolectric) roboRule else delegateRule - return ruleToApply.apply(base, description) - } - - protected fun takeScreenshot( - mode: Mode = Mode.WrapContent, - viewProvider: (ComponentActivity) -> View, - beforeScreenshot: (ComponentActivity) -> Unit = {} - ): Bitmap { - activityRule.scenario.onActivity { activity -> - // Make sure that the activity draws full screen and fits the whole display instead of - // the system bars. - val window = activity.window - window.setDecorFitsSystemWindows(false) - - // Set the content. - activity.setContentView(viewProvider(activity), mode.layoutParams) - - // Elevation/shadows is not deterministic when doing hardware rendering, so we disable - // it for any view in the hierarchy. - window.decorView.removeElevationRecursively() - - activity.currentFocus?.clearFocus() - } - - // We call onActivity again because it will make sure that our Activity is done measuring, - // laying out and drawing its content (that we set in the previous onActivity lambda). - var contentView: View? = null - activityRule.scenario.onActivity { activity -> - // Check that the content is what we expected. - val content = activity.requireViewById<ViewGroup>(android.R.id.content) - assertEquals(1, content.childCount) - contentView = content.getChildAt(0) - beforeScreenshot(activity) - } - - return if (isRobolectric) { - contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS) - ?: error("timeout while trying to capture view to bitmap") - } else { - contentView?.toBitmap() ?: error("contentView is null") - } - } - - /** - * Compare the content of the view provided by [viewProvider] with the golden image identified - * by [goldenIdentifier] in the context of [emulationSpec]. - */ - fun screenshotTest( - goldenIdentifier: String, - mode: Mode = Mode.WrapContent, - beforeScreenshot: (ComponentActivity) -> Unit = {}, - viewProvider: (ComponentActivity) -> View - ) { - val bitmap = takeScreenshot(mode, viewProvider, beforeScreenshot) - screenshotRule.assertBitmapAgainstGolden( - bitmap, - goldenIdentifier, - matcher, - ) - } - - /** - * Compare the content of the dialog provided by [dialogProvider] with the golden image - * identified by [goldenIdentifier] in the context of [emulationSpec]. - */ - fun dialogScreenshotTest( - goldenIdentifier: String, - dialogProvider: (Activity) -> Dialog, - ) { - var dialog: Dialog? = null - activityRule.scenario.onActivity { activity -> - dialog = - dialogProvider(activity).apply { - // Make sure that the dialog draws full screen and fits the whole display - // instead of the system bars. - window.setDecorFitsSystemWindows(false) - - // Disable enter/exit animations. - create() - window.setWindowAnimations(0) - - // Elevation/shadows is not deterministic when doing hardware rendering, so we - // disable it for any view in the hierarchy. - window.decorView.removeElevationRecursively() - - // Show the dialog. - show() - } - } - - try { - val bitmap = dialog?.toBitmap() ?: error("dialog is null") - screenshotRule.assertBitmapAgainstGolden( - bitmap, - goldenIdentifier, - matcher, - ) - } finally { - dialog?.dismiss() - } - } - - private fun Dialog.toBitmap(): Bitmap { - val window = window - return window.decorView.toBitmap(window) - } - - enum class Mode(val layoutParams: LayoutParams) { - WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)), - MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)), - MatchWidth(LayoutParams(MATCH_PARENT, WRAP_CONTENT)), - MatchHeight(LayoutParams(WRAP_CONTENT, MATCH_PARENT)), - } -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt deleted file mode 100644 index d34f46bf48a6..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.android.systemui.testing.screenshot - -import android.graphics.Bitmap -import android.graphics.Rect -import android.os.Handler -import android.os.Looper -import android.view.PixelCopy -import android.view.Window -import androidx.concurrent.futures.ResolvableFuture - -/* - * This file was forked from androidx/test/core/view/WindowCapture.kt. - * TODO(b/195673633): Remove this fork and use the AndroidX version instead. - */ -fun Window.generateBitmapFromPixelCopy( - boundsInWindow: Rect? = null, - destBitmap: Bitmap, - bitmapFuture: ResolvableFuture<Bitmap> -) { - val onCopyFinished = - PixelCopy.OnPixelCopyFinishedListener { result -> - if (result == PixelCopy.SUCCESS) { - bitmapFuture.set(destBitmap) - } else { - bitmapFuture.setException( - RuntimeException(String.format("PixelCopy failed: %d", result)) - ) - } - } - PixelCopy.request( - this, - boundsInWindow, - destBitmap, - onCopyFinished, - Handler(Looper.getMainLooper()) - ) -} |