Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9162723 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Bundle header tweaks

* fix bundle expander color to match other expanders
* fix bundle icon background to match expander
* remove the sometimes-transparent background of preview icons
* reduce the stroke width to account for the built-in padding of app icons

Fixes: 423946203
Test: manual & screenshots
Flag: com.android.systemui.notification_bundle_ui
Change-Id: I2740fdc6e644e37b11980d778a9c55403cd7c47d
parent 33b3d738
Loading
Loading
Loading
Loading
+3 −8
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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 {
@@ -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,
        )
    }
+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,
    )
}
+3 −15
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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),
@@ -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(
@@ -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
+16 −2
Original line number Diff line number Diff line
@@ -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
@@ -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