Loading packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt +3 −8 Original line number Diff line number Diff line Loading @@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text Loading @@ -37,7 +36,6 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color Loading Loading @@ -225,7 +223,8 @@ private fun ContentScope.BundlePreviewIcons( check(previewDrawables.isNotEmpty()) val iconSize = 32.dp val borderWidth = 2.5.dp // The design stroke width is 2.5dp but there is a ~4% padding inside app icons; ~1.25dp here. val borderWidth = 1.25.dp HalfOverlappingReversedRow( modifier = modifier.graphicsLayer { Loading Loading @@ -275,14 +274,10 @@ private fun PreviewIcon(drawable: Drawable, modifier: Modifier = Modifier, borde drawContent() } ) { val surfaceColor = notificationElementSurfaceColor() Image( painter = rememberDrawablePainter(drawable), contentDescription = null, modifier = Modifier.fillMaxSize() .clip(CircleShape) .background(color = surfaceColor, shape = CircleShape), modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Fit, ) } Loading packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowColors.kt 0 → 100644 +58 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.notifications.ui.composable.row import androidx.annotation.FloatRange import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.graphics.Color @Composable @ReadOnlyComposable fun notificationProtectionColor(): Color { // Per android.app.Notification.Colors, this is a 90% blend // of materialColorOnSurface over materialColorSurfaceContainerHigh val background = MaterialTheme.colorScheme.surfaceContainerHigh val primaryText = MaterialTheme.colorScheme.onSurface return blendARGB(primaryText, background, 0.9f) } /** * Blend between two ARGB colors using the given ratio. * * A blend ratio of 0.0 will result in [color1], 0.5 will give an even blend, 1.0 will result in * [color2]. * * @param color1 the first ARGB color * @param color2 the second ARGB color * @param ratio the blend ratio of [color1] to [color2] * @see [com.android.internal.graphics.ColorUtils.blendARGB] */ private fun blendARGB( color1: Color, color2: Color, @FloatRange(from = 0.0, to = 1.0) ratio: Float, ): Color { val inverseRatio = 1 - ratio return Color( red = color1.red * inverseRatio + color2.red * ratio, green = color1.green * inverseRatio + color2.green * ratio, blue = color1.blue * inverseRatio + color2.blue * ratio, alpha = color1.alpha * inverseRatio + color2.alpha * ratio, ) } packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt +3 −15 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package com.android.systemui.notifications.ui.composable.row import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize Loading @@ -33,7 +32,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier Loading Loading @@ -68,8 +66,8 @@ object NotificationRowPrimitives { /** The Icon displayed at the start of any notification row. */ @Composable fun BundleIcon(@DrawableRes drawable: Int?, modifier: Modifier = Modifier) { val surfaceColor = notificationElementSurfaceColor() Box(modifier = modifier.size(40.dp).background(color = surfaceColor, shape = CircleShape)) { val iconBackground = notificationProtectionColor() Box(modifier = modifier.size(40.dp).background(color = iconBackground, shape = CircleShape)) { if (drawable == null) return@Box Image( painter = painterResource(drawable), Loading Loading @@ -115,7 +113,7 @@ fun ContentScope.ExpansionControl( @Composable private fun ContentScope.PillBackground(modifier: Modifier = Modifier) { val surfaceColor = notificationElementSurfaceColor() val surfaceColor = notificationProtectionColor() // Needs to be a shared element so it does not overlap while animating ElementWithValues(NotificationRowPrimitives.Elements.PillBackground, modifier) { Box( Loading @@ -130,16 +128,6 @@ private fun ContentScope.PillBackground(modifier: Modifier = Modifier) { } } @Composable @ReadOnlyComposable fun notificationElementSurfaceColor(): Color { return if (isSystemInDarkTheme()) { Color.White.copy(alpha = 0.15f) } else { MaterialTheme.colorScheme.surfaceContainerHighest } } @Composable private fun ContentScope.Chevron(collapsed: Boolean, color: Color, modifier: Modifier = Modifier) { val key = NotificationRowPrimitives.Elements.Chevron Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt +16 −2 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.os.UserHandle import android.util.Log import androidx.annotation.VisibleForTesting import com.android.internal.R import com.android.launcher3.icons.BaseIconFactory import com.android.launcher3.icons.BaseIconFactory.IconOptions Loading Loading @@ -211,13 +212,26 @@ constructor( return iconFactory.createBadgedIconBitmap(icon, options) } @VisibleForTesting fun createAppIconForTest(packageName: String, @UserIconInfo.UserType userType: Int): Drawable { val pm = sysuiContext.packageManager val userHandle = UserHandle.of(pm.userId) val icon = pm.getApplicationInfo(packageName, 0).loadUnbadgedIcon(pm) val options = iconOptions(UserIconInfo(userHandle, userType)) val bitmapInfo = standardIconFactory.createBadgedIconBitmap(icon, options) return bitmapInfo.createIconDrawable(themed = false) } private fun BitmapInfo.createIconDrawable(themed: Boolean): Drawable = newIcon(context = sysuiContext, creationFlags = if (themed) BitmapInfo.FLAG_THEMED else 0) .apply { isAnimationEnabled = false } private fun iconOptions(userHandle: UserHandle, allowProfileBadge: Boolean): IconOptions { private fun iconOptions(userHandle: UserHandle, allowProfileBadge: Boolean): IconOptions = iconOptions(userIconInfo(userHandle, allowProfileBadge = allowProfileBadge)) private fun iconOptions(userIconInfo: UserIconInfo): IconOptions { return IconOptions().apply { setUser(userIconInfo(userHandle, allowProfileBadge = allowProfileBadge)) setUser(userIconInfo) setBitmapGenerationMode(BaseIconFactory.MODE_HARDWARE) // This color will not be used, but we're just setting it so that the icon factory // doesn't try to extract colors from our bitmap (since it won't work, given it's a Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt +3 −8 Original line number Diff line number Diff line Loading @@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text Loading @@ -37,7 +36,6 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color Loading Loading @@ -225,7 +223,8 @@ private fun ContentScope.BundlePreviewIcons( check(previewDrawables.isNotEmpty()) val iconSize = 32.dp val borderWidth = 2.5.dp // The design stroke width is 2.5dp but there is a ~4% padding inside app icons; ~1.25dp here. val borderWidth = 1.25.dp HalfOverlappingReversedRow( modifier = modifier.graphicsLayer { Loading Loading @@ -275,14 +274,10 @@ private fun PreviewIcon(drawable: Drawable, modifier: Modifier = Modifier, borde drawContent() } ) { val surfaceColor = notificationElementSurfaceColor() Image( painter = rememberDrawablePainter(drawable), contentDescription = null, modifier = Modifier.fillMaxSize() .clip(CircleShape) .background(color = surfaceColor, shape = CircleShape), modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Fit, ) } Loading
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowColors.kt 0 → 100644 +58 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.notifications.ui.composable.row import androidx.annotation.FloatRange import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.graphics.Color @Composable @ReadOnlyComposable fun notificationProtectionColor(): Color { // Per android.app.Notification.Colors, this is a 90% blend // of materialColorOnSurface over materialColorSurfaceContainerHigh val background = MaterialTheme.colorScheme.surfaceContainerHigh val primaryText = MaterialTheme.colorScheme.onSurface return blendARGB(primaryText, background, 0.9f) } /** * Blend between two ARGB colors using the given ratio. * * A blend ratio of 0.0 will result in [color1], 0.5 will give an even blend, 1.0 will result in * [color2]. * * @param color1 the first ARGB color * @param color2 the second ARGB color * @param ratio the blend ratio of [color1] to [color2] * @see [com.android.internal.graphics.ColorUtils.blendARGB] */ private fun blendARGB( color1: Color, color2: Color, @FloatRange(from = 0.0, to = 1.0) ratio: Float, ): Color { val inverseRatio = 1 - ratio return Color( red = color1.red * inverseRatio + color2.red * ratio, green = color1.green * inverseRatio + color2.green * ratio, blue = color1.blue * inverseRatio + color2.blue * ratio, alpha = color1.alpha * inverseRatio + color2.alpha * ratio, ) }
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt +3 −15 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package com.android.systemui.notifications.ui.composable.row import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize Loading @@ -33,7 +32,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier Loading Loading @@ -68,8 +66,8 @@ object NotificationRowPrimitives { /** The Icon displayed at the start of any notification row. */ @Composable fun BundleIcon(@DrawableRes drawable: Int?, modifier: Modifier = Modifier) { val surfaceColor = notificationElementSurfaceColor() Box(modifier = modifier.size(40.dp).background(color = surfaceColor, shape = CircleShape)) { val iconBackground = notificationProtectionColor() Box(modifier = modifier.size(40.dp).background(color = iconBackground, shape = CircleShape)) { if (drawable == null) return@Box Image( painter = painterResource(drawable), Loading Loading @@ -115,7 +113,7 @@ fun ContentScope.ExpansionControl( @Composable private fun ContentScope.PillBackground(modifier: Modifier = Modifier) { val surfaceColor = notificationElementSurfaceColor() val surfaceColor = notificationProtectionColor() // Needs to be a shared element so it does not overlap while animating ElementWithValues(NotificationRowPrimitives.Elements.PillBackground, modifier) { Box( Loading @@ -130,16 +128,6 @@ private fun ContentScope.PillBackground(modifier: Modifier = Modifier) { } } @Composable @ReadOnlyComposable fun notificationElementSurfaceColor(): Color { return if (isSystemInDarkTheme()) { Color.White.copy(alpha = 0.15f) } else { MaterialTheme.colorScheme.surfaceContainerHighest } } @Composable private fun ContentScope.Chevron(collapsed: Boolean, color: Color, modifier: Modifier = Modifier) { val key = NotificationRowPrimitives.Elements.Chevron Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt +16 −2 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.os.UserHandle import android.util.Log import androidx.annotation.VisibleForTesting import com.android.internal.R import com.android.launcher3.icons.BaseIconFactory import com.android.launcher3.icons.BaseIconFactory.IconOptions Loading Loading @@ -211,13 +212,26 @@ constructor( return iconFactory.createBadgedIconBitmap(icon, options) } @VisibleForTesting fun createAppIconForTest(packageName: String, @UserIconInfo.UserType userType: Int): Drawable { val pm = sysuiContext.packageManager val userHandle = UserHandle.of(pm.userId) val icon = pm.getApplicationInfo(packageName, 0).loadUnbadgedIcon(pm) val options = iconOptions(UserIconInfo(userHandle, userType)) val bitmapInfo = standardIconFactory.createBadgedIconBitmap(icon, options) return bitmapInfo.createIconDrawable(themed = false) } private fun BitmapInfo.createIconDrawable(themed: Boolean): Drawable = newIcon(context = sysuiContext, creationFlags = if (themed) BitmapInfo.FLAG_THEMED else 0) .apply { isAnimationEnabled = false } private fun iconOptions(userHandle: UserHandle, allowProfileBadge: Boolean): IconOptions { private fun iconOptions(userHandle: UserHandle, allowProfileBadge: Boolean): IconOptions = iconOptions(userIconInfo(userHandle, allowProfileBadge = allowProfileBadge)) private fun iconOptions(userIconInfo: UserIconInfo): IconOptions { return IconOptions().apply { setUser(userIconInfo(userHandle, allowProfileBadge = allowProfileBadge)) setUser(userIconInfo) setBitmapGenerationMode(BaseIconFactory.MODE_HARDWARE) // This color will not be used, but we're just setting it so that the icon factory // doesn't try to extract colors from our bitmap (since it won't work, given it's a Loading