Merge "Move the compose code to tm-qpr-dev (1/2)" into tm-qpr-dev am: b8ea807587
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19494150 Change-Id: Ia9ead9a0c07c13a6f8f07ed91f1cd87214e1053c Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
38
packages/SystemUI/compose/core/Android.bp
Normal file
@@ -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"],
|
||||||
|
}
|
||||||
22
packages/SystemUI/compose/core/AndroidManifest.xml
Normal file
@@ -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>
|
||||||
37
packages/SystemUI/compose/core/TEST_MAPPING
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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) }
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
packages/SystemUI/compose/core/tests/Android.bp
Normal file
@@ -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"],
|
||||||
|
}
|
||||||
28
packages/SystemUI/compose/core/tests/AndroidManifest.xml
Normal file
@@ -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>
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
packages/SystemUI/compose/features/Android.bp
Normal file
@@ -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"],
|
||||||
|
}
|
||||||
22
packages/SystemUI/compose/features/AndroidManifest.xml
Normal file
@@ -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>
|
||||||
26
packages/SystemUI/compose/features/TEST_MAPPING
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
48
packages/SystemUI/compose/features/tests/Android.bp
Normal file
@@ -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"],
|
||||||
|
}
|
||||||
28
packages/SystemUI/compose/features/tests/AndroidManifest.xml
Normal file
@@ -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>
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
72
packages/SystemUI/compose/gallery/Android.bp
Normal file
@@ -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"],
|
||||||
|
}
|
||||||
55
packages/SystemUI/compose/gallery/AndroidManifest.xml
Normal file
@@ -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>
|
||||||
15
packages/SystemUI/compose/gallery/TEST_MAPPING
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"presubmit": [
|
||||||
|
{
|
||||||
|
"name": "SystemUIComposeGalleryTests",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"exclude-annotation": "org.junit.Ignore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exclude-annotation": "androidx.test.filters.FlakyTest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
39
packages/SystemUI/compose/gallery/app/AndroidManifest.xml
Normal file
@@ -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>
|
||||||
21
packages/SystemUI/compose/gallery/proguard-rules.pro
vendored
Normal file
@@ -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
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
19
packages/SystemUI/compose/gallery/res/values/colors.xml
Normal file
@@ -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>
|
||||||
20
packages/SystemUI/compose/gallery/res/values/strings.xml
Normal file
@@ -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>
|
||||||
30
packages/SystemUI/compose/gallery/res/values/themes.xml
Normal file
@@ -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>
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
@@ -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!") }
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
47
packages/SystemUI/compose/gallery/tests/Android.bp
Normal file
@@ -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"],
|
||||||
|
}
|
||||||
28
packages/SystemUI/compose/gallery/tests/AndroidManifest.xml
Normal file
@@ -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>
|
||||||
@@ -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() } }
|
||||||
|
}
|
||||||
|
}
|
||||||
43
packages/SystemUI/compose/testing/Android.bp
Normal file
@@ -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"],
|
||||||
|
}
|
||||||
25
packages/SystemUI/compose/testing/AndroidManifest.xml
Normal file
@@ -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>
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||