diff options
| author | 2022-08-02 10:32:27 +0200 | |
|---|---|---|
| committer | 2022-08-08 12:26:57 +0200 | |
| commit | 359df6fe7a28d32956055a3617baf2b51a9d98b9 (patch) | |
| tree | edd1bab2ba2882430fb17582ba0e64ba1bd31d99 | |
| parent | 592f8bf611e997bca0c5a208a8e8a0ee1f024c4f (diff) | |
Move the compose code to tm-qpr-dev (1/2)
This CL moves all the current Compose code from master to tm-qpr-dev,
which is possible now that all Compose dependencies have been cherry
picked to tm-qpr-dev.
Note that I used the SHA of ag/19398175 for this CL Change-Id and
Merged-In tags, which is the last CL in master that changed code in the
compose/ folder. That way, we make sure that we don't try to merge this
code back into master.
Bug: 231131244
Test: m SystemUIComposeGallery
Change-Id: Ic3efb42d371c6aac8c2a6297a0cb192a780903cd
Merged-In: Ic3efb42d371c6aac8c2a6297a0cb192a780903cd
51 files changed, 2448 insertions, 0 deletions
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp new file mode 100644 index 000000000000..4cfe39225a9b --- /dev/null +++ b/packages/SystemUI/compose/core/Android.bp @@ -0,0 +1,38 @@ +// 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: "SystemUIComposeCore", + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + ], + + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/compose/core/AndroidManifest.xml b/packages/SystemUI/compose/core/AndroidManifest.xml new file mode 100644 index 000000000000..83c442d2d6f2 --- /dev/null +++ b/packages/SystemUI/compose/core/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?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.compose.core"> + + +</manifest> diff --git a/packages/SystemUI/compose/core/TEST_MAPPING b/packages/SystemUI/compose/core/TEST_MAPPING new file mode 100644 index 000000000000..dc243d2fd8f5 --- /dev/null +++ b/packages/SystemUI/compose/core/TEST_MAPPING @@ -0,0 +1,37 @@ +{ + "presubmit": [ + { + "name": "SystemUIComposeCoreTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeFeaturesTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeGalleryTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt new file mode 100644 index 000000000000..c9470c8dbe3a --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt @@ -0,0 +1,294 @@ +/* + * 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.compose + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.os.Build +import android.view.View +import android.view.Window +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.window.DialogWindowProvider +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat + +/** + * ************************************************************************************************* + * This file was forked from + * https://github.com/google/accompanist/blob/main/systemuicontroller/src/main/java/com/google/accompanist/systemuicontroller/SystemUiController.kt + * and will be removed once it lands in AndroidX. + */ + +/** + * A class which provides easy-to-use utilities for updating the System UI bar colors within Jetpack + * Compose. + * + * @sample com.google.accompanist.sample.systemuicontroller.SystemUiControllerSample + */ +@Stable +interface SystemUiController { + + /** + * Property which holds the status bar visibility. If set to true, show the status bar, + * otherwise hide the status bar. + */ + var isStatusBarVisible: Boolean + + /** + * Property which holds the navigation bar visibility. If set to true, show the navigation bar, + * otherwise hide the navigation bar. + */ + var isNavigationBarVisible: Boolean + + /** + * Property which holds the status & navigation bar visibility. If set to true, show both bars, + * otherwise hide both bars. + */ + var isSystemBarsVisible: Boolean + get() = isNavigationBarVisible && isStatusBarVisible + set(value) { + isStatusBarVisible = value + isNavigationBarVisible = value + } + + /** + * Set the status bar color. + * + * @param color The **desired** [Color] to set. This may require modification if running on an + * API level that only supports white status bar icons. + * @param darkIcons Whether dark status bar icons would be preferable. + * @param transformColorForLightContent A lambda which will be invoked to transform [color] if + * dark icons were requested but are not available. Defaults to applying a black scrim. + * + * @see statusBarDarkContentEnabled + */ + fun setStatusBarColor( + color: Color, + darkIcons: Boolean = color.luminance() > 0.5f, + transformColorForLightContent: (Color) -> Color = BlackScrimmed + ) + + /** + * Set the navigation bar color. + * + * @param color The **desired** [Color] to set. This may require modification if running on an + * API level that only supports white navigation bar icons. Additionally this will be ignored + * and [Color.Transparent] will be used on API 29+ where gesture navigation is preferred or the + * system UI automatically applies background protection in other navigation modes. + * @param darkIcons Whether dark navigation bar icons would be preferable. + * @param navigationBarContrastEnforced Whether the system should ensure that the navigation bar + * has enough contrast when a fully transparent background is requested. Only supported on API + * 29+. + * @param transformColorForLightContent A lambda which will be invoked to transform [color] if + * dark icons were requested but are not available. Defaults to applying a black scrim. + * + * @see navigationBarDarkContentEnabled + * @see navigationBarContrastEnforced + */ + fun setNavigationBarColor( + color: Color, + darkIcons: Boolean = color.luminance() > 0.5f, + navigationBarContrastEnforced: Boolean = true, + transformColorForLightContent: (Color) -> Color = BlackScrimmed + ) + + /** + * Set the status and navigation bars to [color]. + * + * @see setStatusBarColor + * @see setNavigationBarColor + */ + fun setSystemBarsColor( + color: Color, + darkIcons: Boolean = color.luminance() > 0.5f, + isNavigationBarContrastEnforced: Boolean = true, + transformColorForLightContent: (Color) -> Color = BlackScrimmed + ) { + setStatusBarColor(color, darkIcons, transformColorForLightContent) + setNavigationBarColor( + color, + darkIcons, + isNavigationBarContrastEnforced, + transformColorForLightContent + ) + } + + /** Property which holds whether the status bar icons + content are 'dark' or not. */ + var statusBarDarkContentEnabled: Boolean + + /** Property which holds whether the navigation bar icons + content are 'dark' or not. */ + var navigationBarDarkContentEnabled: Boolean + + /** + * Property which holds whether the status & navigation bar icons + content are 'dark' or not. + */ + var systemBarsDarkContentEnabled: Boolean + get() = statusBarDarkContentEnabled && navigationBarDarkContentEnabled + set(value) { + statusBarDarkContentEnabled = value + navigationBarDarkContentEnabled = value + } + + /** + * Property which holds whether the system is ensuring that the navigation bar has enough + * contrast when a fully transparent background is requested. Only has an affect when running on + * Android API 29+ devices. + */ + var isNavigationBarContrastEnforced: Boolean +} + +/** + * Remembers a [SystemUiController] for the given [window]. + * + * If no [window] is provided, an attempt to find the correct [Window] is made. + * + * First, if the [LocalView]'s parent is a [DialogWindowProvider], then that dialog's [Window] will + * be used. + * + * Second, we attempt to find [Window] for the [Activity] containing the [LocalView]. + * + * If none of these are found (such as may happen in a preview), then the functionality of the + * returned [SystemUiController] will be degraded, but won't throw an exception. + */ +@Composable +fun rememberSystemUiController( + window: Window? = findWindow(), +): SystemUiController { + val view = LocalView.current + return remember(view, window) { AndroidSystemUiController(view, window) } +} + +@Composable +private fun findWindow(): Window? = + (LocalView.current.parent as? DialogWindowProvider)?.window + ?: LocalView.current.context.findWindow() + +private tailrec fun Context.findWindow(): Window? = + when (this) { + is Activity -> window + is ContextWrapper -> baseContext.findWindow() + else -> null + } + +/** + * A helper class for setting the navigation and status bar colors for a [View], gracefully + * degrading behavior based upon API level. + * + * Typically you would use [rememberSystemUiController] to remember an instance of this. + */ +internal class AndroidSystemUiController(private val view: View, private val window: Window?) : + SystemUiController { + private val windowInsetsController = window?.let { WindowCompat.getInsetsController(it, view) } + + override fun setStatusBarColor( + color: Color, + darkIcons: Boolean, + transformColorForLightContent: (Color) -> Color + ) { + statusBarDarkContentEnabled = darkIcons + + window?.statusBarColor = + when { + darkIcons && windowInsetsController?.isAppearanceLightStatusBars != true -> { + // If we're set to use dark icons, but our windowInsetsController call didn't + // succeed (usually due to API level), we instead transform the color to + // maintain contrast + transformColorForLightContent(color) + } + else -> color + }.toArgb() + } + + override fun setNavigationBarColor( + color: Color, + darkIcons: Boolean, + navigationBarContrastEnforced: Boolean, + transformColorForLightContent: (Color) -> Color + ) { + navigationBarDarkContentEnabled = darkIcons + isNavigationBarContrastEnforced = navigationBarContrastEnforced + + window?.navigationBarColor = + when { + darkIcons && windowInsetsController?.isAppearanceLightNavigationBars != true -> { + // If we're set to use dark icons, but our windowInsetsController call didn't + // succeed (usually due to API level), we instead transform the color to + // maintain contrast + transformColorForLightContent(color) + } + else -> color + }.toArgb() + } + + override var isStatusBarVisible: Boolean + get() { + return ViewCompat.getRootWindowInsets(view) + ?.isVisible(WindowInsetsCompat.Type.statusBars()) == true + } + set(value) { + if (value) { + windowInsetsController?.show(WindowInsetsCompat.Type.statusBars()) + } else { + windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars()) + } + } + + override var isNavigationBarVisible: Boolean + get() { + return ViewCompat.getRootWindowInsets(view) + ?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true + } + set(value) { + if (value) { + windowInsetsController?.show(WindowInsetsCompat.Type.navigationBars()) + } else { + windowInsetsController?.hide(WindowInsetsCompat.Type.navigationBars()) + } + } + + override var statusBarDarkContentEnabled: Boolean + get() = windowInsetsController?.isAppearanceLightStatusBars == true + set(value) { + windowInsetsController?.isAppearanceLightStatusBars = value + } + + override var navigationBarDarkContentEnabled: Boolean + get() = windowInsetsController?.isAppearanceLightNavigationBars == true + set(value) { + windowInsetsController?.isAppearanceLightNavigationBars = value + } + + override var isNavigationBarContrastEnforced: Boolean + get() = Build.VERSION.SDK_INT >= 29 && window?.isNavigationBarContrastEnforced == true + set(value) { + if (Build.VERSION.SDK_INT >= 29) { + window?.isNavigationBarContrastEnforced = value + } + } +} + +private val BlackScrim = Color(0f, 0f, 0f, 0.3f) // 30% opaque black +private val BlackScrimmed: (Color) -> Color = { original -> BlackScrim.compositeOver(original) } diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt new file mode 100644 index 000000000000..b8639e64e002 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt @@ -0,0 +1,74 @@ +/* + * 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.compose.theme + +import android.annotation.ColorInt +import android.content.Context +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import com.android.internal.R + +/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */ +val LocalAndroidColorScheme = + staticCompositionLocalOf<AndroidColorScheme> { + throw IllegalStateException( + "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " + + "Composable surrounded by a SystemUITheme {}." + ) + } + +/** + * The Android color scheme. + * + * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future, + * most of the colors in this class will be removed in favor of their M3 counterpart. + */ +class AndroidColorScheme internal constructor(context: Context) { + val colorPrimary = getColor(context, R.attr.colorPrimary) + val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark) + val colorAccent = getColor(context, R.attr.colorAccent) + val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary) + val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary) + val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary) + val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant) + val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant) + val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant) + val colorSurface = getColor(context, R.attr.colorSurface) + val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight) + val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant) + val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader) + val colorError = getColor(context, R.attr.colorError) + val colorBackground = getColor(context, R.attr.colorBackground) + val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating) + val panelColorBackground = getColor(context, R.attr.panelColorBackground) + val textColorPrimary = getColor(context, R.attr.textColorPrimary) + val textColorSecondary = getColor(context, R.attr.textColorSecondary) + val textColorTertiary = getColor(context, R.attr.textColorTertiary) + val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse) + val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse) + val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse) + val textColorOnAccent = getColor(context, R.attr.textColorOnAccent) + val colorForeground = getColor(context, R.attr.colorForeground) + val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse) + + private fun getColor(context: Context, attr: Int): Color { + val ta = context.obtainStyledAttributes(intArrayOf(attr)) + @ColorInt val color = ta.getColor(0, 0) + ta.recycle() + return Color(color) + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt new file mode 100644 index 000000000000..79e3d3d475a8 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt @@ -0,0 +1,53 @@ +/* + * 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.compose.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Typography +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext + +/** The Material 3 theme that should wrap all SystemUI Composables. */ +@Composable +fun SystemUITheme( + isDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val context = LocalContext.current + + // TODO(b/230605885): Define our typography and color scheme. + val colorScheme = + if (isDarkTheme) { + dynamicDarkColorScheme(context) + } else { + dynamicLightColorScheme(context) + } + val androidColorScheme = AndroidColorScheme(context) + val typography = Typography() + + MaterialTheme(colorScheme, typography = typography) { + CompositionLocalProvider( + LocalAndroidColorScheme provides androidColorScheme, + ) { + content() + } + } +} diff --git a/packages/SystemUI/compose/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp new file mode 100644 index 000000000000..f8023e2519f8 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/Android.bp @@ -0,0 +1,48 @@ +// 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"], +} + +// TODO(b/230606318): Make those host tests instead of device tests. +android_test { + name: "SystemUIComposeCoreTests", + manifest: "AndroidManifest.xml", + test_suites: ["device-tests"], + sdk_version: "current", + certificate: "platform", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SystemUIComposeCore", + + "androidx.test.runner", + "androidx.test.ext.junit", + + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + ], + + kotlincflags: ["-Xjvm-default=enable"], +} diff --git a/packages/SystemUI/compose/core/tests/AndroidManifest.xml b/packages/SystemUI/compose/core/tests/AndroidManifest.xml new file mode 100644 index 000000000000..729ab989cdc0 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?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.compose.core.tests" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.systemui.compose.core.tests" + android:label="Tests for SystemUIComposeCore"/> + +</manifest>
\ No newline at end of file diff --git a/packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt new file mode 100644 index 000000000000..20249f66d0d0 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt @@ -0,0 +1,57 @@ +/* + * 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.compose.theme + +import androidx.compose.material3.Text +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertThrows +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SystemUIThemeTest { + @get:Rule val composeRule = createComposeRule() + + @Test + fun testThemeShowsContent() { + composeRule.setContent { SystemUITheme { Text("foo") } } + + composeRule.onNodeWithText("foo").assertIsDisplayed() + } + + @Test + fun testAndroidColorsAreAvailableInsideTheme() { + composeRule.setContent { + SystemUITheme { Text("foo", color = LocalAndroidColorScheme.current.colorAccent) } + } + + composeRule.onNodeWithText("foo").assertIsDisplayed() + } + + @Test + fun testAccessingAndroidColorsWithoutThemeThrows() { + assertThrows(IllegalStateException::class.java) { + composeRule.setContent { + Text("foo", color = LocalAndroidColorScheme.current.colorAccent) + } + } + } +} diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp new file mode 100644 index 000000000000..40218de94258 --- /dev/null +++ b/packages/SystemUI/compose/features/Android.bp @@ -0,0 +1,40 @@ +// 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: "SystemUIComposeFeatures", + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SystemUIComposeCore", + + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + ], + + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml new file mode 100644 index 000000000000..0aea99d4e960 --- /dev/null +++ b/packages/SystemUI/compose/features/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?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.compose.features"> + + +</manifest> diff --git a/packages/SystemUI/compose/features/TEST_MAPPING b/packages/SystemUI/compose/features/TEST_MAPPING new file mode 100644 index 000000000000..7430acb2e900 --- /dev/null +++ b/packages/SystemUI/compose/features/TEST_MAPPING @@ -0,0 +1,26 @@ +{ + "presubmit": [ + { + "name": "SystemUIComposeFeaturesTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeGalleryTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt new file mode 100644 index 000000000000..c58c16259abe --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt @@ -0,0 +1,94 @@ +/* + * 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 + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.math.roundToInt + +/** + * This is an example Compose feature, which shows a text and a count that is incremented when + * clicked. We also show the max width available to this component, which is displayed either next + * to or below the text depending on that max width. + */ +@Composable +fun ExampleFeature(text: String, modifier: Modifier = Modifier) { + BoxWithConstraints(modifier) { + val maxWidth = maxWidth + if (maxWidth < 600.dp) { + Column { + CounterTile(text) + Spacer(Modifier.size(16.dp)) + MaxWidthTile(maxWidth) + } + } else { + Row { + CounterTile(text) + Spacer(Modifier.size(16.dp)) + MaxWidthTile(maxWidth) + } + } + } +} + +@Composable +private fun CounterTile(text: String, modifier: Modifier = Modifier) { + Surface( + modifier, + color = MaterialTheme.colorScheme.primaryContainer, + shape = RoundedCornerShape(28.dp), + ) { + var count by remember { mutableStateOf(0) } + Column( + Modifier.clickable { count++ }.padding(16.dp), + ) { + Text(text) + Text("I was clicked $count times.") + } + } +} + +@Composable +private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) { + Surface( + modifier, + color = MaterialTheme.colorScheme.tertiaryContainer, + shape = RoundedCornerShape(28.dp), + ) { + Text( + "The max available width to me is: ${maxWidth.value.roundToInt()}dp", + Modifier.padding(16.dp) + ) + } +} diff --git a/packages/SystemUI/compose/features/tests/Android.bp b/packages/SystemUI/compose/features/tests/Android.bp new file mode 100644 index 000000000000..ff534bd01fd3 --- /dev/null +++ b/packages/SystemUI/compose/features/tests/Android.bp @@ -0,0 +1,48 @@ +// 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"], +} + +// TODO(b/230606318): Make those host tests instead of device tests. +android_test { + name: "SystemUIComposeFeaturesTests", + manifest: "AndroidManifest.xml", + test_suites: ["device-tests"], + sdk_version: "current", + certificate: "platform", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SystemUIComposeFeatures", + + "androidx.test.runner", + "androidx.test.ext.junit", + + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + ], + + kotlincflags: ["-Xjvm-default=enable"], +} diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml new file mode 100644 index 000000000000..5e54c1f353d2 --- /dev/null +++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?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.compose.features.tests" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.systemui.compose.features.tests" + android:label="Tests for SystemUIComposeFeatures"/> + +</manifest>
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt new file mode 100644 index 000000000000..1c2e8fab0337 --- /dev/null +++ b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt @@ -0,0 +1,46 @@ +/* + * 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 + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ExampleFeatureTest { + @get:Rule val composeRule = createComposeRule() + + @Test + fun testProvidedTextIsDisplayed() { + composeRule.setContent { ExampleFeature("foo") } + + composeRule.onNodeWithText("foo").assertIsDisplayed() + } + + @Test + fun testCountIsIncreasedWhenClicking() { + composeRule.setContent { ExampleFeature("foo") } + + composeRule.onNodeWithText("I was clicked 0 times.").assertIsDisplayed().performClick() + composeRule.onNodeWithText("I was clicked 1 times.").assertIsDisplayed() + } +} diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp new file mode 100644 index 000000000000..40504dc30c33 --- /dev/null +++ b/packages/SystemUI/compose/gallery/Android.bp @@ -0,0 +1,72 @@ +// 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: "SystemUIComposeGalleryLib", + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.kt", + ], + + resource_dirs: [ + "res", + ], + + static_libs: [ + "SystemUI-core", + "SystemUIComposeCore", + "SystemUIComposeFeatures", + + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + "androidx.compose.material_material-icons-extended", + "androidx.activity_activity-compose", + "androidx.navigation_navigation-compose", + + "androidx.appcompat_appcompat", + ], + + kotlincflags: ["-Xjvm-default=all"], +} + +android_app { + name: "SystemUIComposeGallery", + defaults: ["platform_app_defaults"], + manifest: "app/AndroidManifest.xml", + + static_libs: [ + "SystemUIComposeGalleryLib", + ], + + platform_apis: true, + system_ext_specific: true, + certificate: "platform", + privileged: true, + + optimize: { + proguard_flags_files: ["proguard-rules.pro"], + }, + + dxflags: ["--multi-dex"], +} diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml new file mode 100644 index 000000000000..2f30651a6acf --- /dev/null +++ b/packages/SystemUI/compose/gallery/AndroidManifest.xml @@ -0,0 +1,55 @@ +<?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.compose.gallery"> + <!-- To emulate a display size and density. --> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + + <application + android:name="android.app.Application" + android:appComponentFactory="androidx.core.app.AppComponentFactory" + tools:replace="android:name,android:appComponentFactory"> + <!-- Disable providers from SystemUI --> + <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider" + android:authorities="com.android.systemui.test.keyguard.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle" + android:authorities="com.android.systemui.test.keyguard.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="com.android.keyguard.clock.ClockOptionsProvider" + android:authorities="com.android.systemui.test.keyguard.clock.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="com.android.systemui.people.PeopleProvider" + android:authorities="com.android.systemui.test.people.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="androidx.core.content.FileProvider" + android:authorities="com.android.systemui.test.fileprovider.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove"/> + </application> +</manifest> diff --git a/packages/SystemUI/compose/gallery/TEST_MAPPING b/packages/SystemUI/compose/gallery/TEST_MAPPING new file mode 100644 index 000000000000..c7f8a9216418 --- /dev/null +++ b/packages/SystemUI/compose/gallery/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit": [ + { + "name": "SystemUIComposeGalleryTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml new file mode 100644 index 000000000000..1f3fd8c312d9 --- /dev/null +++ b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml @@ -0,0 +1,39 @@ +<?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.compose.gallery.app"> + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.SystemUI.Gallery" + tools:replace="android:icon,android:theme,android:label"> + <activity + android:name="com.android.systemui.compose.gallery.GalleryActivity" + android:exported="true" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/packages/SystemUI/compose/gallery/proguard-rules.pro b/packages/SystemUI/compose/gallery/proguard-rules.pro new file mode 100644 index 000000000000..481bb4348141 --- /dev/null +++ b/packages/SystemUI/compose/gallery/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml b/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000000..966abaff2074 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeColor="#00000000" + android:strokeWidth="1" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml b/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000000..61bb79edb709 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillColor="#3DDC84" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> +</vector> diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000000..03eed2533da2 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000000..03eed2533da2 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..c209e78ecd37 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..b2dfe3d1ba5c --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..4f0f1d64e58b --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..62b611da0816 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..948a3070fe34 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..1b9a6956b3ac --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..28d4b77f9f03 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..9287f5083623 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..aa7d6427e6fa --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..9126ae37cbc3 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/values/colors.xml b/packages/SystemUI/compose/gallery/res/values/colors.xml new file mode 100644 index 000000000000..a2fcbffc26c0 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/values/colors.xml @@ -0,0 +1,19 @@ +<?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> + <color name="ic_launcher_background">#FFFFFF</color> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/values/strings.xml b/packages/SystemUI/compose/gallery/res/values/strings.xml new file mode 100644 index 000000000000..86bdb0568837 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/values/strings.xml @@ -0,0 +1,20 @@ +<?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> + <!-- Application name [CHAR LIMIT=NONE] --> + <string name="app_name">SystemUI Gallery</string> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/values/themes.xml b/packages/SystemUI/compose/gallery/res/values/themes.xml new file mode 100644 index 000000000000..45fa1f5dfb5c --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/values/themes.xml @@ -0,0 +1,30 @@ +<?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 xmlns:tools="http://schemas.android.com/tools"> + <style name="Theme.SystemUI.Gallery"> + <item name="android:windowActionBar">false</item> + <item name="android:windowNoTitle">true</item> + + <item name="android:statusBarColor" tools:targetApi="l"> + @android:color/transparent + </item> + <item name="android:navigationBarColor" tools:targetApi="l"> + @android:color/transparent + </item> + <item name="android:windowLightStatusBar">true</item> + </style> +</resources> diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt new file mode 100644 index 000000000000..dfa1b26f464e --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt @@ -0,0 +1,139 @@ +/* + * 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.compose.gallery + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.android.systemui.compose.theme.LocalAndroidColorScheme + +/** The screen that shows all the Material 3 colors. */ +@Composable +fun MaterialColorsScreen() { + val colors = MaterialTheme.colorScheme + ColorsScreen( + listOf( + "primary" to colors.primary, + "onPrimary" to colors.onPrimary, + "primaryContainer" to colors.primaryContainer, + "onPrimaryContainer" to colors.onPrimaryContainer, + "inversePrimary" to colors.inversePrimary, + "secondary" to colors.secondary, + "onSecondary" to colors.onSecondary, + "secondaryContainer" to colors.secondaryContainer, + "onSecondaryContainer" to colors.onSecondaryContainer, + "tertiary" to colors.tertiary, + "onTertiary" to colors.onTertiary, + "tertiaryContainer" to colors.tertiaryContainer, + "onTertiaryContainer" to colors.onTertiaryContainer, + "background" to colors.background, + "onBackground" to colors.onBackground, + "surface" to colors.surface, + "onSurface" to colors.onSurface, + "surfaceVariant" to colors.surfaceVariant, + "onSurfaceVariant" to colors.onSurfaceVariant, + "inverseSurface" to colors.inverseSurface, + "inverseOnSurface" to colors.inverseOnSurface, + "error" to colors.error, + "onError" to colors.onError, + "errorContainer" to colors.errorContainer, + "onErrorContainer" to colors.onErrorContainer, + "outline" to colors.outline, + ) + ) +} + +/** The screen that shows all the Android colors. */ +@Composable +fun AndroidColorsScreen() { + val colors = LocalAndroidColorScheme.current + ColorsScreen( + listOf( + "colorPrimary" to colors.colorPrimary, + "colorPrimaryDark" to colors.colorPrimaryDark, + "colorAccent" to colors.colorAccent, + "colorAccentPrimary" to colors.colorAccentPrimary, + "colorAccentSecondary" to colors.colorAccentSecondary, + "colorAccentTertiary" to colors.colorAccentTertiary, + "colorAccentPrimaryVariant" to colors.colorAccentPrimaryVariant, + "colorAccentSecondaryVariant" to colors.colorAccentSecondaryVariant, + "colorAccentTertiaryVariant" to colors.colorAccentTertiaryVariant, + "colorSurface" to colors.colorSurface, + "colorSurfaceHighlight" to colors.colorSurfaceHighlight, + "colorSurfaceVariant" to colors.colorSurfaceVariant, + "colorSurfaceHeader" to colors.colorSurfaceHeader, + "colorError" to colors.colorError, + "colorBackground" to colors.colorBackground, + "colorBackgroundFloating" to colors.colorBackgroundFloating, + "panelColorBackground" to colors.panelColorBackground, + "textColorPrimary" to colors.textColorPrimary, + "textColorSecondary" to colors.textColorSecondary, + "textColorTertiary" to colors.textColorTertiary, + "textColorPrimaryInverse" to colors.textColorPrimaryInverse, + "textColorSecondaryInverse" to colors.textColorSecondaryInverse, + "textColorTertiaryInverse" to colors.textColorTertiaryInverse, + "textColorOnAccent" to colors.textColorOnAccent, + "colorForeground" to colors.colorForeground, + "colorForegroundInverse" to colors.colorForegroundInverse, + ) + ) +} + +@Composable +private fun ColorsScreen( + colors: List<Pair<String, Color>>, +) { + LazyColumn( + Modifier.fillMaxWidth(), + ) { + colors.forEach { (name, color) -> item { ColorTile(color, name) } } + } +} + +@Composable +private fun ColorTile( + color: Color, + name: String, +) { + Row( + Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + val shape = RoundedCornerShape(16.dp) + Spacer( + Modifier.border(1.dp, MaterialTheme.colorScheme.onBackground, shape) + .background(color, shape) + .size(64.dp) + ) + Spacer(Modifier.width(16.dp)) + Text(name) + } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt new file mode 100644 index 000000000000..990d060207df --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt @@ -0,0 +1,210 @@ +package com.android.systemui.compose.gallery + +import android.graphics.Point +import android.os.UserHandle +import android.view.Display +import android.view.WindowManagerGlobal +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DarkMode +import androidx.compose.material.icons.filled.FormatSize +import androidx.compose.material.icons.filled.FormatTextdirectionLToR +import androidx.compose.material.icons.filled.FormatTextdirectionRToL +import androidx.compose.material.icons.filled.InvertColors +import androidx.compose.material.icons.filled.LightMode +import androidx.compose.material.icons.filled.Smartphone +import androidx.compose.material.icons.filled.Tablet +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import kotlin.math.max +import kotlin.math.min + +enum class FontScale(val scale: Float) { + Small(0.85f), + Normal(1f), + Big(1.15f), + Bigger(1.30f), +} + +/** A configuration panel that allows to toggle the theme, font scale and layout direction. */ +@Composable +fun ConfigurationControls( + theme: Theme, + fontScale: FontScale, + layoutDirection: LayoutDirection, + onChangeTheme: () -> Unit, + onChangeLayoutDirection: () -> Unit, + onChangeFontScale: () -> Unit, + modifier: Modifier = Modifier, +) { + // The display we are emulating, if any. + var emulatedDisplayName by rememberSaveable { mutableStateOf<String?>(null) } + val emulatedDisplay = + emulatedDisplayName?.let { name -> EmulatedDisplays.firstOrNull { it.name == name } } + + LaunchedEffect(emulatedDisplay) { + val wm = WindowManagerGlobal.getWindowManagerService() + + val defaultDisplayId = Display.DEFAULT_DISPLAY + if (emulatedDisplay == null) { + wm.clearForcedDisplayDensityForUser(defaultDisplayId, UserHandle.myUserId()) + wm.clearForcedDisplaySize(defaultDisplayId) + } else { + val density = emulatedDisplay.densityDpi + + // Emulate the display and make sure that we use the maximum available space possible. + val initialSize = Point() + wm.getInitialDisplaySize(defaultDisplayId, initialSize) + val width = emulatedDisplay.width + val height = emulatedDisplay.height + val minOfSize = min(width, height) + val maxOfSize = max(width, height) + if (initialSize.x < initialSize.y) { + wm.setForcedDisplaySize(defaultDisplayId, minOfSize, maxOfSize) + } else { + wm.setForcedDisplaySize(defaultDisplayId, maxOfSize, minOfSize) + } + wm.setForcedDisplayDensityForUser(defaultDisplayId, density, UserHandle.myUserId()) + } + } + + // TODO(b/231131244): Fork FlowRow from Accompanist and use that instead to make sure that users + // don't miss any available configuration. + LazyRow(modifier) { + // Dark/light theme. + item { + TextButton(onChangeTheme) { + val text: String + val icon: ImageVector + + when (theme) { + Theme.System -> { + icon = Icons.Default.InvertColors + text = "System" + } + Theme.Dark -> { + icon = Icons.Default.DarkMode + text = "Dark" + } + Theme.Light -> { + icon = Icons.Default.LightMode + text = "Light" + } + } + + Icon(icon, null) + Spacer(Modifier.width(8.dp)) + Text(text) + } + } + + // Font scale. + item { + TextButton(onChangeFontScale) { + Icon(Icons.Default.FormatSize, null) + Spacer(Modifier.width(8.dp)) + + Text(fontScale.name) + } + } + + // Layout direction. + item { + TextButton(onChangeLayoutDirection) { + when (layoutDirection) { + LayoutDirection.Ltr -> { + Icon(Icons.Default.FormatTextdirectionLToR, null) + Spacer(Modifier.width(8.dp)) + Text("LTR") + } + LayoutDirection.Rtl -> { + Icon(Icons.Default.FormatTextdirectionRToL, null) + Spacer(Modifier.width(8.dp)) + Text("RTL") + } + } + } + } + + // Display emulation. + EmulatedDisplays.forEach { display -> + item { + DisplayButton( + display, + emulatedDisplay == display, + { emulatedDisplayName = it?.name }, + ) + } + } + } +} + +@Composable +private fun DisplayButton( + display: EmulatedDisplay, + selected: Boolean, + onChangeEmulatedDisplay: (EmulatedDisplay?) -> Unit, +) { + val onClick = { + if (selected) { + onChangeEmulatedDisplay(null) + } else { + onChangeEmulatedDisplay(display) + } + } + + val content: @Composable RowScope.() -> Unit = { + Icon(display.icon, null) + Spacer(Modifier.width(8.dp)) + Text(display.name) + } + + if (selected) { + Button(onClick, contentPadding = ButtonDefaults.TextButtonContentPadding, content = content) + } else { + TextButton(onClick, content = content) + } +} + +/** The displays that can be emulated from this Gallery app. */ +private val EmulatedDisplays = + listOf( + EmulatedDisplay( + "Phone", + Icons.Default.Smartphone, + width = 1440, + height = 3120, + densityDpi = 560, + ), + EmulatedDisplay( + "Tablet", + Icons.Default.Tablet, + width = 2560, + height = 1600, + densityDpi = 320, + ), + ) + +private data class EmulatedDisplay( + val name: String, + val icon: ImageVector, + val width: Int, + val height: Int, + val densityDpi: Int, +) diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt new file mode 100644 index 000000000000..6e1721490f98 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt @@ -0,0 +1,28 @@ +/* + * 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.compose.gallery + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.systemui.ExampleFeature + +/** The screen that shows ExampleFeature. */ +@Composable +fun ExampleFeatureScreen(modifier: Modifier = Modifier) { + Column(modifier) { ExampleFeature("This is an example feature!") } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt new file mode 100644 index 000000000000..bb2d2feba39f --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt @@ -0,0 +1,80 @@ +/* + * 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.compose.gallery + +import android.app.UiModeManager +import android.content.Context +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color +import androidx.core.view.WindowCompat +import com.android.systemui.compose.rememberSystemUiController + +class GalleryActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager + + setContent { + var theme by rememberSaveable { mutableStateOf(Theme.System) } + val onChangeTheme = { + // Change to the next theme for a toggle behavior. + theme = + when (theme) { + Theme.System -> Theme.Dark + Theme.Dark -> Theme.Light + Theme.Light -> Theme.System + } + } + + val isSystemInDarkTheme = isSystemInDarkTheme() + val isDark = theme == Theme.Dark || (theme == Theme.System && isSystemInDarkTheme) + val useDarkIcons = !isDark + val systemUiController = rememberSystemUiController() + SideEffect { + systemUiController.setSystemBarsColor( + color = Color.Transparent, + darkIcons = useDarkIcons, + ) + + uiModeManager.setApplicationNightMode( + when (theme) { + Theme.System -> UiModeManager.MODE_NIGHT_AUTO + Theme.Dark -> UiModeManager.MODE_NIGHT_YES + Theme.Light -> UiModeManager.MODE_NIGHT_NO + } + ) + } + + GalleryApp(theme, onChangeTheme) + } + } +} + +enum class Theme { + System, + Dark, + Light, +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt new file mode 100644 index 000000000000..c341867bfb59 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt @@ -0,0 +1,125 @@ +package com.android.systemui.compose.gallery + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import com.android.systemui.compose.theme.SystemUITheme + +/** The gallery app screens. */ +object GalleryAppScreens { + val Typography = ChildScreen("typography") { TypographyScreen() } + val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() } + val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() } + val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() } + + val Home = + ParentScreen( + "home", + mapOf( + "Typography" to Typography, + "Material colors" to MaterialColors, + "Android colors" to AndroidColors, + "Example feature" to ExampleFeature, + ) + ) +} + +/** The main content of the app, that shows [GalleryAppScreens.Home] by default. */ +@Composable +private fun MainContent() { + Box(Modifier.fillMaxSize()) { + val navController = rememberNavController() + NavHost( + navController = navController, + startDestination = GalleryAppScreens.Home.identifier, + ) { + screen(GalleryAppScreens.Home, navController) + } + } +} + +/** + * The top-level composable shown when starting the app. This composable always shows a + * [ConfigurationControls] at the top of the screen, above the [MainContent]. + */ +@Composable +fun GalleryApp( + theme: Theme, + onChangeTheme: () -> Unit, +) { + val systemFontScale = LocalDensity.current.fontScale + var fontScale: FontScale by remember { + mutableStateOf( + FontScale.values().firstOrNull { it.scale == systemFontScale } ?: FontScale.Normal + ) + } + val context = LocalContext.current + val density = Density(context.resources.displayMetrics.density, fontScale.scale) + val onChangeFontScale = { + fontScale = + when (fontScale) { + FontScale.Small -> FontScale.Normal + FontScale.Normal -> FontScale.Big + FontScale.Big -> FontScale.Bigger + FontScale.Bigger -> FontScale.Small + } + } + + val systemLayoutDirection = LocalLayoutDirection.current + var layoutDirection by remember { mutableStateOf(systemLayoutDirection) } + val onChangeLayoutDirection = { + layoutDirection = + when (layoutDirection) { + LayoutDirection.Ltr -> LayoutDirection.Rtl + LayoutDirection.Rtl -> LayoutDirection.Ltr + } + } + + CompositionLocalProvider( + LocalDensity provides density, + LocalLayoutDirection provides layoutDirection, + ) { + SystemUITheme { + Surface( + Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + ) { + Column(Modifier.fillMaxSize().systemBarsPadding().padding(16.dp)) { + ConfigurationControls( + theme, + fontScale, + layoutDirection, + onChangeTheme, + onChangeLayoutDirection, + onChangeFontScale, + ) + + Spacer(Modifier.height(4.dp)) + + MainContent() + } + } + } + } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt new file mode 100644 index 000000000000..467dac044b79 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt @@ -0,0 +1,95 @@ +/* + * 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.compose.gallery + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import androidx.navigation.compose.navigation + +/** + * A screen in an app. It is either an [ParentScreen] which lists its child screens to navigate to + * them or a [ChildScreen] which shows some content. + */ +sealed class Screen(val identifier: String) + +class ParentScreen( + identifier: String, + val children: Map<String, Screen>, +) : Screen(identifier) + +class ChildScreen( + identifier: String, + val content: @Composable (NavController) -> Unit, +) : Screen(identifier) + +/** Create the navigation graph for [screen]. */ +fun NavGraphBuilder.screen(screen: Screen, navController: NavController) { + when (screen) { + is ChildScreen -> composable(screen.identifier) { screen.content(navController) } + is ParentScreen -> { + val menuRoute = "${screen.identifier}_menu" + navigation(startDestination = menuRoute, route = screen.identifier) { + // The menu to navigate to one of the children screens. + composable(menuRoute) { ScreenMenu(screen, navController) } + + // The content of the child screens. + screen.children.forEach { (_, child) -> screen(child, navController) } + } + } + } +} + +@Composable +private fun ScreenMenu( + screen: ParentScreen, + navController: NavController, +) { + LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) { + screen.children.forEach { (name, child) -> + item { + Surface( + Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.secondaryContainer, + shape = CircleShape, + ) { + Column( + Modifier.clickable { navController.navigate(child.identifier) } + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(name) + } + } + } + } + } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt new file mode 100644 index 000000000000..147025ed1d60 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt @@ -0,0 +1,67 @@ +/* + * 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.compose.gallery + +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow + +/** The screen that shows the Material text styles. */ +@Composable +fun TypographyScreen() { + val typography = MaterialTheme.typography + + Column( + Modifier.fillMaxSize() + .horizontalScroll(rememberScrollState()) + .verticalScroll(rememberScrollState()), + ) { + FontLine("displayLarge", typography.displayLarge) + FontLine("displayMedium", typography.displayMedium) + FontLine("displaySmall", typography.displaySmall) + FontLine("headlineLarge", typography.headlineLarge) + FontLine("headlineMedium", typography.headlineMedium) + FontLine("headlineSmall", typography.headlineSmall) + FontLine("titleLarge", typography.titleLarge) + FontLine("titleMedium", typography.titleMedium) + FontLine("titleSmall", typography.titleSmall) + FontLine("bodyLarge", typography.bodyLarge) + FontLine("bodyMedium", typography.bodyMedium) + FontLine("bodySmall", typography.bodySmall) + FontLine("labelLarge", typography.labelLarge) + FontLine("labelMedium", typography.labelMedium) + FontLine("labelSmall", typography.labelSmall) + } +} + +@Composable +private fun FontLine(name: String, style: TextStyle) { + Text( + "$name (${style.fontSize}/${style.lineHeight}, W${style.fontWeight?.weight})", + style = style, + maxLines = 1, + overflow = TextOverflow.Visible, + ) +} diff --git a/packages/SystemUI/compose/gallery/tests/Android.bp b/packages/SystemUI/compose/gallery/tests/Android.bp new file mode 100644 index 000000000000..3e01f7d2c431 --- /dev/null +++ b/packages/SystemUI/compose/gallery/tests/Android.bp @@ -0,0 +1,47 @@ +// 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_test { + name: "SystemUIComposeGalleryTests", + manifest: "AndroidManifest.xml", + test_suites: ["device-tests"], + sdk_version: "current", + certificate: "platform", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SystemUIComposeGalleryLib", + + "androidx.test.runner", + "androidx.test.ext.junit", + + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + ], + + kotlincflags: ["-Xjvm-default=enable"], +} diff --git a/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml b/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml new file mode 100644 index 000000000000..5eeb3ad24e5a --- /dev/null +++ b/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?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.compose.gallery.tests" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.systemui.compose.gallery.tests" + android:label="Tests for SystemUIComposeGallery"/> + +</manifest>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt b/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt new file mode 100644 index 000000000000..66ecc8d4fde5 --- /dev/null +++ b/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt @@ -0,0 +1,36 @@ +/* + * 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.compose.gallery + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.systemui.compose.theme.SystemUITheme +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ScreenshotsTests { + @get:Rule val composeRule = createComposeRule() + + @Test + fun exampleFeatureScreenshotTest() { + // TODO(b/230832101): Wire this with the screenshot diff testing infra. We should reuse the + // configuration of the features in the gallery app to populate the UIs. + composeRule.setContent { SystemUITheme { ExampleFeatureScreen() } } + } +} diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp new file mode 100644 index 000000000000..293e51f68b98 --- /dev/null +++ b/packages/SystemUI/compose/testing/Android.bp @@ -0,0 +1,43 @@ +// 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: [ + "SystemUIComposeCore", + "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 new file mode 100644 index 000000000000..b1f7c3be2796 --- /dev/null +++ b/packages/SystemUI/compose/testing/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?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 new file mode 100644 index 000000000000..e611e8bf0068 --- /dev/null +++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt @@ -0,0 +1,89 @@ +/* + * 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.systemui.compose.theme.SystemUITheme +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) : TestRule { + private val colorsRule = MaterialYouColorsRule() + private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) + private val screenshotRule = + ScreenshotTestRule( + SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) + ) + 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 { + SystemUITheme { + Surface( + color = MaterialTheme.colorScheme.background, + ) { + content() + } + } + } + composeRule.waitForIdle() + + val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view + screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher) + } +} |