Loading libs/WindowManager/Shell/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -206,6 +206,7 @@ android_library { "androidx.core_core-animation", "androidx.core_core-ktx", "androidx.arch.core_core-runtime", "androidx.compose.material3_material3", "androidx-constraintlayout_constraintlayout", "androidx.dynamicanimation_dynamicanimation", "androidx.recyclerview_recyclerview", Loading libs/WindowManager/Shell/res/drawable/desktop_mode_header_background.xmldeleted 100644 → 0 +0 −28 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2024 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. --> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/backLayer"> <shape android:shape="rectangle"> <solid android:color="#000000" /> </shape> </item> <item android:id="@+id/frontLayer"> <shape android:shape="rectangle"> <solid android:color="#000000" /> </shape> </item> </layer-list> No newline at end of file libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml +0 −1 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/desktop_mode_caption" android:background="@drawable/desktop_mode_header_background" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.wm.shell.windowdecor.common import android.app.ActivityManager.RunningTaskInfo import android.content.Context import android.content.res.Configuration import android.content.res.Configuration.UI_MODE_NIGHT_MASK import android.graphics.Color /** The theme of a window decoration. */ internal enum class Theme { LIGHT, DARK } /** Whether a [Theme] is light. */ internal fun Theme.isLight(): Boolean = this == Theme.LIGHT /** Whether a [Theme] is dark. */ internal fun Theme.isDark(): Boolean = this == Theme.DARK /** * Utility class for determining themes based on system settings and app's [RunningTaskInfo]. */ internal class DecorThemeUtil(private val context: Context) { private val systemTheme: Theme get() = if ((context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) { Theme.DARK } else { Theme.LIGHT } /** * Returns the [Theme] used by the app with the given [RunningTaskInfo]. */ fun getAppTheme(task: RunningTaskInfo): Theme { // TODO: use app's uiMode to find its actual light/dark value. It needs to be added to the // TaskInfo/TaskDescription. val backgroundColor = task.taskDescription?.backgroundColor ?: return systemTheme return if (Color.valueOf(backgroundColor).luminance() < 0.5) { Theme.DARK } else { Theme.LIGHT } } } libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +74 −215 Original line number Diff line number Diff line Loading @@ -19,10 +19,8 @@ import android.annotation.ColorInt import android.app.ActivityManager.RunningTaskInfo import android.content.res.ColorStateList import android.content.res.Configuration import android.content.res.Configuration.UI_MODE_NIGHT_MASK import android.graphics.Bitmap import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.graphics.drawable.ShapeDrawable Loading @@ -32,19 +30,22 @@ import android.view.View.OnLongClickListener import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.ui.graphics.toArgb import androidx.core.content.withStyledAttributes import androidx.core.view.isVisible import com.android.internal.R.attr.materialColorOnSecondaryContainer import com.android.internal.R.attr.materialColorOnSurface import com.android.internal.R.attr.materialColorOnSurfaceInverse import com.android.internal.R.attr.materialColorSecondaryContainer import com.android.internal.R.attr.materialColorSurfaceContainerHigh import com.android.internal.R.attr.materialColorSurfaceContainerLow import com.android.internal.R.attr.materialColorSurfaceDim import com.android.internal.R.attr.materialColorSurfaceInverse import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.windowdecor.MaximizeButtonView import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.Theme import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance Loading @@ -64,6 +65,10 @@ internal class AppHeaderViewHolder( onMaximizeHoverAnimationFinishedListener: () -> Unit ) : WindowDecorationViewHolder(rootView) { private val decorThemeUtil = DecorThemeUtil(context) private val lightColors = dynamicLightColorScheme(context) private val darkColors = dynamicDarkColorScheme(context) /** * The corner radius to apply to the app chip, maximize and close button's background drawable. **/ Loading Loading @@ -168,19 +173,12 @@ internal class AppHeaderViewHolder( val headerStyle = getHeaderStyle(header) // Caption Background val headerBackground = captionView.background as LayerDrawable val backLayer = headerBackground.findDrawableByLayerId(R.id.backLayer) as GradientDrawable val frontLayer = headerBackground.findDrawableByLayerId(R.id.frontLayer) as GradientDrawable when (headerStyle.background) { is HeaderStyle.Background.Opaque -> { backLayer.setColor(headerStyle.background.backLayerColor ?: Color.BLACK) frontLayer.setColor(headerStyle.background.frontLayerColor) frontLayer.alpha = headerStyle.background.frontLayerOpacity captionView.setBackgroundColor(headerStyle.background.color) } HeaderStyle.Background.Transparent -> { backLayer.setColor(Color.TRANSPARENT) frontLayer.setColor(Color.TRANSPARENT) frontLayer.alpha = OPACITY_100 captionView.setBackgroundColor(Color.TRANSPARENT) } } Loading @@ -204,7 +202,7 @@ internal class AppHeaderViewHolder( } // Maximize button. maximizeButtonView.setAnimationTints( darkMode = header.appTheme == Header.Theme.DARK, darkMode = header.appTheme == Theme.DARK, iconForegroundColor = colorStateList, baseForegroundColor = foregroundColor, rippleDrawable = createRippleDrawable( Loading Loading @@ -251,186 +249,88 @@ internal class AppHeaderViewHolder( ) } private fun getHeaderBackground( header: Header ): HeaderStyle.Background { when (header.type) { private fun getHeaderBackground(header: Header): HeaderStyle.Background { return when (header.type) { Header.Type.DEFAULT -> { if (header.systemTheme.isLight() && header.appTheme.isLight() && header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSecondaryContainer), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isLight() && header.appTheme.isLight() && !header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceContainerLow), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isDark() && header.appTheme.isDark() && header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceContainerHigh), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isDark() && header.appTheme.isDark() && !header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceDim), frontLayerOpacity = OPACITY_100, backLayerColor = null ) when (header.appTheme) { Theme.LIGHT -> { if (header.isFocused) { HeaderStyle.Background.Opaque(lightColors.secondaryContainer.toArgb()) } else { HeaderStyle.Background.Opaque(lightColors.surfaceContainerLow.toArgb()) } if (header.systemTheme.isLight() && header.appTheme.isDark() && header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceInverse), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isLight() && header.appTheme.isDark() && !header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceInverse), frontLayerOpacity = OPACITY_30, backLayerColor = Color.BLACK ) Theme.DARK -> { if (header.isFocused) { HeaderStyle.Background.Opaque(darkColors.surfaceContainerHigh.toArgb()) } else { HeaderStyle.Background.Opaque(darkColors.surfaceDim.toArgb()) } if (header.systemTheme.isDark() && header.appTheme.isLight() && header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceInverse), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isDark() && header.appTheme.isLight() && !header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceInverse), frontLayerOpacity = OPACITY_55, backLayerColor = Color.WHITE ) } error("No other combination expected header=$header") } Header.Type.CUSTOM -> return HeaderStyle.Background.Transparent Header.Type.CUSTOM -> HeaderStyle.Background.Transparent } } private fun getHeaderForeground(header: Header): HeaderStyle.Foreground { when (header.type) { return when (header.type) { Header.Type.DEFAULT -> { if (header.systemTheme.isLight() && header.appTheme.isLight() && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSecondaryContainer), when (header.appTheme) { Theme.LIGHT -> { if (header.isFocused) { HeaderStyle.Foreground( color = lightColors.onSecondaryContainer.toArgb(), opacity = OPACITY_100 ) } if (header.systemTheme.isLight() && header.appTheme.isLight() && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSecondaryContainer), } else { HeaderStyle.Foreground( color = lightColors.onSecondaryContainer.toArgb(), opacity = OPACITY_65 ) } if (header.systemTheme.isDark() && header.appTheme.isDark() && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurface), opacity = OPACITY_100 ) } if (header.systemTheme.isDark() && header.appTheme.isDark() && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurface), opacity = OPACITY_55 ) } if (header.systemTheme.isLight() && header.appTheme.isDark() && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), Theme.DARK -> { if (header.isFocused) { HeaderStyle.Foreground( color = darkColors.onSurface.toArgb(), opacity = OPACITY_100 ) } if (header.systemTheme.isLight() && header.appTheme.isDark() && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_65 } else { HeaderStyle.Foreground( color = darkColors.onSurface.toArgb(), opacity = OPACITY_55 ) } if (header.systemTheme.isDark() && header.appTheme.isLight() && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_100 ) } if (header.systemTheme.isDark() && header.appTheme.isLight() && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_70 ) } error("No other combination expected header=$header") } Header.Type.CUSTOM -> { if (header.systemTheme.isLight() && header.isAppearanceCaptionLight && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSecondaryContainer), Header.Type.CUSTOM -> when { header.isAppearanceCaptionLight && header.isFocused -> { HeaderStyle.Foreground( color = lightColors.onSecondaryContainer.toArgb(), opacity = OPACITY_100 ) } if (header.systemTheme.isLight() && header.isAppearanceCaptionLight && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSecondaryContainer), header.isAppearanceCaptionLight && !header.isFocused -> { HeaderStyle.Foreground( color = lightColors.onSecondaryContainer.toArgb(), opacity = OPACITY_65 ) } if (header.systemTheme.isDark() && !header.isAppearanceCaptionLight && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurface), !header.isAppearanceCaptionLight && header.isFocused -> { HeaderStyle.Foreground( color = darkColors.onSurface.toArgb(), opacity = OPACITY_100 ) } if (header.systemTheme.isDark() && !header.isAppearanceCaptionLight && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurface), !header.isAppearanceCaptionLight && !header.isFocused -> { HeaderStyle.Foreground( color = darkColors.onSurface.toArgb(), opacity = OPACITY_55 ) } if (header.systemTheme.isLight() && !header.isAppearanceCaptionLight && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_100 ) } if (header.systemTheme.isLight() && !header.isAppearanceCaptionLight && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_65 ) } if (header.systemTheme.isDark() && header.isAppearanceCaptionLight && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_100 ) } if (header.systemTheme.isDark() && header.isAppearanceCaptionLight && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_70 ) } error("No other combination expected header=$header") else -> error("No other combination expected header=$header") } } } Loading @@ -442,41 +342,12 @@ internal class AppHeaderViewHolder( } else { Header.Type.DEFAULT }, systemTheme = getSystemTheme(), appTheme = getAppTheme(taskInfo), appTheme = decorThemeUtil.getAppTheme(taskInfo), isFocused = taskInfo.isFocused, isAppearanceCaptionLight = taskInfo.isLightCaptionBarAppearance ) } private fun getSystemTheme(): Header.Theme { return if ((context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) { Header.Theme.DARK } else { Header.Theme.LIGHT } } private fun getAppTheme(taskInfo: RunningTaskInfo): Header.Theme { // TODO: use app's uiMode to find its actual light/dark value. It needs to be added to the // TaskInfo/TaskDescription. val backgroundColor = taskInfo.taskDescription?.backgroundColor ?: return getSystemTheme() return if (Color.valueOf(backgroundColor).luminance() < 0.5) { Header.Theme.DARK } else { Header.Theme.LIGHT } } @ColorInt private fun attrToColor(attr: Int): Int { context.withStyledAttributes(null, intArrayOf(attr), 0, 0) { return getColor(0, 0) } return Color.WHITE } @ColorInt private fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int { return Color.argb( Loading Loading @@ -530,19 +401,13 @@ internal class AppHeaderViewHolder( private data class Header( val type: Type, val systemTheme: Theme, val appTheme: Theme, val isFocused: Boolean, val isAppearanceCaptionLight: Boolean, ) { enum class Type { DEFAULT, CUSTOM } enum class Theme { LIGHT, DARK } } private fun Header.Theme.isLight(): Boolean = this == Header.Theme.LIGHT private fun Header.Theme.isDark(): Boolean = this == Header.Theme.DARK private data class HeaderStyle( val background: Background, val foreground: Foreground Loading @@ -554,11 +419,7 @@ internal class AppHeaderViewHolder( sealed class Background { data object Transparent : Background() data class Opaque( @ColorInt val frontLayerColor: Int, val frontLayerOpacity: Int, @ColorInt val backLayerColor: Int? ) : Background() data class Opaque(@ColorInt val color: Int) : Background() } } Loading Loading @@ -634,9 +495,7 @@ internal class AppHeaderViewHolder( private const val OPACITY_100 = 255 private const val OPACITY_11 = 28 private const val OPACITY_15 = 38 private const val OPACITY_30 = 77 private const val OPACITY_55 = 140 private const val OPACITY_65 = 166 private const val OPACITY_70 = 179 } } Loading
libs/WindowManager/Shell/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -206,6 +206,7 @@ android_library { "androidx.core_core-animation", "androidx.core_core-ktx", "androidx.arch.core_core-runtime", "androidx.compose.material3_material3", "androidx-constraintlayout_constraintlayout", "androidx.dynamicanimation_dynamicanimation", "androidx.recyclerview_recyclerview", Loading
libs/WindowManager/Shell/res/drawable/desktop_mode_header_background.xmldeleted 100644 → 0 +0 −28 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2024 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. --> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/backLayer"> <shape android:shape="rectangle"> <solid android:color="#000000" /> </shape> </item> <item android:id="@+id/frontLayer"> <shape android:shape="rectangle"> <solid android:color="#000000" /> </shape> </item> </layer-list> No newline at end of file
libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml +0 −1 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/desktop_mode_caption" android:background="@drawable/desktop_mode_header_background" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.wm.shell.windowdecor.common import android.app.ActivityManager.RunningTaskInfo import android.content.Context import android.content.res.Configuration import android.content.res.Configuration.UI_MODE_NIGHT_MASK import android.graphics.Color /** The theme of a window decoration. */ internal enum class Theme { LIGHT, DARK } /** Whether a [Theme] is light. */ internal fun Theme.isLight(): Boolean = this == Theme.LIGHT /** Whether a [Theme] is dark. */ internal fun Theme.isDark(): Boolean = this == Theme.DARK /** * Utility class for determining themes based on system settings and app's [RunningTaskInfo]. */ internal class DecorThemeUtil(private val context: Context) { private val systemTheme: Theme get() = if ((context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) { Theme.DARK } else { Theme.LIGHT } /** * Returns the [Theme] used by the app with the given [RunningTaskInfo]. */ fun getAppTheme(task: RunningTaskInfo): Theme { // TODO: use app's uiMode to find its actual light/dark value. It needs to be added to the // TaskInfo/TaskDescription. val backgroundColor = task.taskDescription?.backgroundColor ?: return systemTheme return if (Color.valueOf(backgroundColor).luminance() < 0.5) { Theme.DARK } else { Theme.LIGHT } } }
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +74 −215 Original line number Diff line number Diff line Loading @@ -19,10 +19,8 @@ import android.annotation.ColorInt import android.app.ActivityManager.RunningTaskInfo import android.content.res.ColorStateList import android.content.res.Configuration import android.content.res.Configuration.UI_MODE_NIGHT_MASK import android.graphics.Bitmap import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.graphics.drawable.ShapeDrawable Loading @@ -32,19 +30,22 @@ import android.view.View.OnLongClickListener import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.ui.graphics.toArgb import androidx.core.content.withStyledAttributes import androidx.core.view.isVisible import com.android.internal.R.attr.materialColorOnSecondaryContainer import com.android.internal.R.attr.materialColorOnSurface import com.android.internal.R.attr.materialColorOnSurfaceInverse import com.android.internal.R.attr.materialColorSecondaryContainer import com.android.internal.R.attr.materialColorSurfaceContainerHigh import com.android.internal.R.attr.materialColorSurfaceContainerLow import com.android.internal.R.attr.materialColorSurfaceDim import com.android.internal.R.attr.materialColorSurfaceInverse import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.windowdecor.MaximizeButtonView import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.Theme import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance Loading @@ -64,6 +65,10 @@ internal class AppHeaderViewHolder( onMaximizeHoverAnimationFinishedListener: () -> Unit ) : WindowDecorationViewHolder(rootView) { private val decorThemeUtil = DecorThemeUtil(context) private val lightColors = dynamicLightColorScheme(context) private val darkColors = dynamicDarkColorScheme(context) /** * The corner radius to apply to the app chip, maximize and close button's background drawable. **/ Loading Loading @@ -168,19 +173,12 @@ internal class AppHeaderViewHolder( val headerStyle = getHeaderStyle(header) // Caption Background val headerBackground = captionView.background as LayerDrawable val backLayer = headerBackground.findDrawableByLayerId(R.id.backLayer) as GradientDrawable val frontLayer = headerBackground.findDrawableByLayerId(R.id.frontLayer) as GradientDrawable when (headerStyle.background) { is HeaderStyle.Background.Opaque -> { backLayer.setColor(headerStyle.background.backLayerColor ?: Color.BLACK) frontLayer.setColor(headerStyle.background.frontLayerColor) frontLayer.alpha = headerStyle.background.frontLayerOpacity captionView.setBackgroundColor(headerStyle.background.color) } HeaderStyle.Background.Transparent -> { backLayer.setColor(Color.TRANSPARENT) frontLayer.setColor(Color.TRANSPARENT) frontLayer.alpha = OPACITY_100 captionView.setBackgroundColor(Color.TRANSPARENT) } } Loading @@ -204,7 +202,7 @@ internal class AppHeaderViewHolder( } // Maximize button. maximizeButtonView.setAnimationTints( darkMode = header.appTheme == Header.Theme.DARK, darkMode = header.appTheme == Theme.DARK, iconForegroundColor = colorStateList, baseForegroundColor = foregroundColor, rippleDrawable = createRippleDrawable( Loading Loading @@ -251,186 +249,88 @@ internal class AppHeaderViewHolder( ) } private fun getHeaderBackground( header: Header ): HeaderStyle.Background { when (header.type) { private fun getHeaderBackground(header: Header): HeaderStyle.Background { return when (header.type) { Header.Type.DEFAULT -> { if (header.systemTheme.isLight() && header.appTheme.isLight() && header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSecondaryContainer), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isLight() && header.appTheme.isLight() && !header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceContainerLow), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isDark() && header.appTheme.isDark() && header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceContainerHigh), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isDark() && header.appTheme.isDark() && !header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceDim), frontLayerOpacity = OPACITY_100, backLayerColor = null ) when (header.appTheme) { Theme.LIGHT -> { if (header.isFocused) { HeaderStyle.Background.Opaque(lightColors.secondaryContainer.toArgb()) } else { HeaderStyle.Background.Opaque(lightColors.surfaceContainerLow.toArgb()) } if (header.systemTheme.isLight() && header.appTheme.isDark() && header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceInverse), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isLight() && header.appTheme.isDark() && !header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceInverse), frontLayerOpacity = OPACITY_30, backLayerColor = Color.BLACK ) Theme.DARK -> { if (header.isFocused) { HeaderStyle.Background.Opaque(darkColors.surfaceContainerHigh.toArgb()) } else { HeaderStyle.Background.Opaque(darkColors.surfaceDim.toArgb()) } if (header.systemTheme.isDark() && header.appTheme.isLight() && header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceInverse), frontLayerOpacity = OPACITY_100, backLayerColor = null ) } if (header.systemTheme.isDark() && header.appTheme.isLight() && !header.isFocused) { return HeaderStyle.Background.Opaque( frontLayerColor = attrToColor(materialColorSurfaceInverse), frontLayerOpacity = OPACITY_55, backLayerColor = Color.WHITE ) } error("No other combination expected header=$header") } Header.Type.CUSTOM -> return HeaderStyle.Background.Transparent Header.Type.CUSTOM -> HeaderStyle.Background.Transparent } } private fun getHeaderForeground(header: Header): HeaderStyle.Foreground { when (header.type) { return when (header.type) { Header.Type.DEFAULT -> { if (header.systemTheme.isLight() && header.appTheme.isLight() && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSecondaryContainer), when (header.appTheme) { Theme.LIGHT -> { if (header.isFocused) { HeaderStyle.Foreground( color = lightColors.onSecondaryContainer.toArgb(), opacity = OPACITY_100 ) } if (header.systemTheme.isLight() && header.appTheme.isLight() && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSecondaryContainer), } else { HeaderStyle.Foreground( color = lightColors.onSecondaryContainer.toArgb(), opacity = OPACITY_65 ) } if (header.systemTheme.isDark() && header.appTheme.isDark() && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurface), opacity = OPACITY_100 ) } if (header.systemTheme.isDark() && header.appTheme.isDark() && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurface), opacity = OPACITY_55 ) } if (header.systemTheme.isLight() && header.appTheme.isDark() && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), Theme.DARK -> { if (header.isFocused) { HeaderStyle.Foreground( color = darkColors.onSurface.toArgb(), opacity = OPACITY_100 ) } if (header.systemTheme.isLight() && header.appTheme.isDark() && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_65 } else { HeaderStyle.Foreground( color = darkColors.onSurface.toArgb(), opacity = OPACITY_55 ) } if (header.systemTheme.isDark() && header.appTheme.isLight() && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_100 ) } if (header.systemTheme.isDark() && header.appTheme.isLight() && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_70 ) } error("No other combination expected header=$header") } Header.Type.CUSTOM -> { if (header.systemTheme.isLight() && header.isAppearanceCaptionLight && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSecondaryContainer), Header.Type.CUSTOM -> when { header.isAppearanceCaptionLight && header.isFocused -> { HeaderStyle.Foreground( color = lightColors.onSecondaryContainer.toArgb(), opacity = OPACITY_100 ) } if (header.systemTheme.isLight() && header.isAppearanceCaptionLight && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSecondaryContainer), header.isAppearanceCaptionLight && !header.isFocused -> { HeaderStyle.Foreground( color = lightColors.onSecondaryContainer.toArgb(), opacity = OPACITY_65 ) } if (header.systemTheme.isDark() && !header.isAppearanceCaptionLight && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurface), !header.isAppearanceCaptionLight && header.isFocused -> { HeaderStyle.Foreground( color = darkColors.onSurface.toArgb(), opacity = OPACITY_100 ) } if (header.systemTheme.isDark() && !header.isAppearanceCaptionLight && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurface), !header.isAppearanceCaptionLight && !header.isFocused -> { HeaderStyle.Foreground( color = darkColors.onSurface.toArgb(), opacity = OPACITY_55 ) } if (header.systemTheme.isLight() && !header.isAppearanceCaptionLight && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_100 ) } if (header.systemTheme.isLight() && !header.isAppearanceCaptionLight && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_65 ) } if (header.systemTheme.isDark() && header.isAppearanceCaptionLight && header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_100 ) } if (header.systemTheme.isDark() && header.isAppearanceCaptionLight && !header.isFocused) { return HeaderStyle.Foreground( color = attrToColor(materialColorOnSurfaceInverse), opacity = OPACITY_70 ) } error("No other combination expected header=$header") else -> error("No other combination expected header=$header") } } } Loading @@ -442,41 +342,12 @@ internal class AppHeaderViewHolder( } else { Header.Type.DEFAULT }, systemTheme = getSystemTheme(), appTheme = getAppTheme(taskInfo), appTheme = decorThemeUtil.getAppTheme(taskInfo), isFocused = taskInfo.isFocused, isAppearanceCaptionLight = taskInfo.isLightCaptionBarAppearance ) } private fun getSystemTheme(): Header.Theme { return if ((context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) { Header.Theme.DARK } else { Header.Theme.LIGHT } } private fun getAppTheme(taskInfo: RunningTaskInfo): Header.Theme { // TODO: use app's uiMode to find its actual light/dark value. It needs to be added to the // TaskInfo/TaskDescription. val backgroundColor = taskInfo.taskDescription?.backgroundColor ?: return getSystemTheme() return if (Color.valueOf(backgroundColor).luminance() < 0.5) { Header.Theme.DARK } else { Header.Theme.LIGHT } } @ColorInt private fun attrToColor(attr: Int): Int { context.withStyledAttributes(null, intArrayOf(attr), 0, 0) { return getColor(0, 0) } return Color.WHITE } @ColorInt private fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int { return Color.argb( Loading Loading @@ -530,19 +401,13 @@ internal class AppHeaderViewHolder( private data class Header( val type: Type, val systemTheme: Theme, val appTheme: Theme, val isFocused: Boolean, val isAppearanceCaptionLight: Boolean, ) { enum class Type { DEFAULT, CUSTOM } enum class Theme { LIGHT, DARK } } private fun Header.Theme.isLight(): Boolean = this == Header.Theme.LIGHT private fun Header.Theme.isDark(): Boolean = this == Header.Theme.DARK private data class HeaderStyle( val background: Background, val foreground: Foreground Loading @@ -554,11 +419,7 @@ internal class AppHeaderViewHolder( sealed class Background { data object Transparent : Background() data class Opaque( @ColorInt val frontLayerColor: Int, val frontLayerOpacity: Int, @ColorInt val backLayerColor: Int? ) : Background() data class Opaque(@ColorInt val color: Int) : Background() } } Loading Loading @@ -634,9 +495,7 @@ internal class AppHeaderViewHolder( private const val OPACITY_100 = 255 private const val OPACITY_11 = 28 private const val OPACITY_15 = 38 private const val OPACITY_30 = 77 private const val OPACITY_55 = 140 private const val OPACITY_65 = 166 private const val OPACITY_70 = 179 } }