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

Commit bc5e753b authored by Shawn Lee's avatar Shawn Lee Committed by Android (Google) Code Review
Browse files

Merge "Added display cutout handling to Shade Header in Flexiglass" into main

parents 49d85122 a16ae9a2
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.compose

import android.content.Context
import android.view.View
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
@@ -26,6 +27,8 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow

/** The Compose facade, when Compose is *not* available. */
object ComposeFacade : BaseComposeFacade {
@@ -52,8 +55,10 @@ object ComposeFacade : BaseComposeFacade {
    }

    override fun createSceneContainerView(
        scope: CoroutineScope,
        context: Context,
        viewModel: SceneContainerViewModel,
        windowInsets: StateFlow<WindowInsets?>,
        sceneByKey: Map<SceneKey, Scene>,
    ): View {
        throwComposeUnavailableError()
+66 −5
Original line number Diff line number Diff line
@@ -17,12 +17,19 @@
package com.android.systemui.compose

import android.content.Context
import android.graphics.Point
import android.view.View
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -32,6 +39,11 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.scene.ui.composable.SceneContainer
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/** The Compose facade, when Compose is available. */
object ComposeFacade : BaseComposeFacade {
@@ -58,13 +70,18 @@ object ComposeFacade : BaseComposeFacade {
    }

    override fun createSceneContainerView(
        scope: CoroutineScope,
        context: Context,
        viewModel: SceneContainerViewModel,
        windowInsets: StateFlow<WindowInsets?>,
        sceneByKey: Map<SceneKey, Scene>,
    ): View {
        return ComposeView(context).apply {
            setContent {
                PlatformTheme {
                    DisplayCutoutProvider(
                        displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets)
                    ) {
                        SceneContainer(
                            viewModel = viewModel,
                            sceneByKey =
@@ -75,3 +92,47 @@ object ComposeFacade : BaseComposeFacade {
            }
        }
    }

    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private fun displayCutoutFromWindowInsets(
        scope: CoroutineScope,
        context: Context,
        windowInsets: StateFlow<WindowInsets?>,
    ): StateFlow<DisplayCutout> =
        windowInsets
            .map {
                val boundingRect = it?.displayCutout?.boundingRectTop
                val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0
                val left = boundingRect?.left?.toDp(context) ?: 0.dp
                val top = boundingRect?.top?.toDp(context) ?: 0.dp
                val right = boundingRect?.right?.toDp(context) ?: 0.dp
                val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp
                val location =
                    when {
                        width <= 0f -> CutoutLocation.NONE
                        left <= 0.dp -> CutoutLocation.LEFT
                        right >= getDisplayWidth(context) -> CutoutLocation.RIGHT
                        else -> CutoutLocation.CENTER
                    }
                DisplayCutout(
                    left,
                    top,
                    right,
                    bottom,
                    location,
                )
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout())

    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private fun getDisplayWidth(context: Context): Dp {
        val point = Point()
        checkNotNull(context.display).getRealSize(point)
        return point.x.dp
    }

    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private fun Int.toDp(context: Context): Dp {
        return (this.toFloat() / context.resources.displayMetrics.density).dp
    }
}
+39 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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.common.ui.compose.windowinsets

import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.abs

/** Represents the global position of the bounds for the display cutout for this display */
data class DisplayCutout(
    val left: Dp = 0.dp,
    val top: Dp = 0.dp,
    val right: Dp = 0.dp,
    val bottom: Dp = 0.dp,
    val location: CutoutLocation = CutoutLocation.NONE,
) {
    fun width() = abs(right.value - left.value).dp
}

enum class CutoutLocation {
    NONE,
    CENTER,
    LEFT,
    RIGHT,
}
+37 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.common.ui.compose.windowinsets

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
import kotlinx.coroutines.flow.StateFlow

/** The bounds and [CutoutLocation] of the current display. */
val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() }

@Composable
fun DisplayCutoutProvider(
    displayCutout: StateFlow<DisplayCutout>,
    content: @Composable () -> Unit,
) {
    val cutout by displayCutout.collectAsState()

    CompositionLocalProvider(LocalDisplayCutout provides cutout) { content() }
}
+103 −34
Original line number Diff line number Diff line
@@ -39,8 +39,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
@@ -49,9 +51,11 @@ import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateSharedFloatAsState
import com.android.settingslib.Utils
import com.android.systemui.res.R
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.res.R
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -97,32 +101,45 @@ fun SceneScope.CollapsedShadeHeader(
    val useExpandedFormat by
        remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } }

    Row(
        modifier =
            modifier
                .element(ShadeHeader.Elements.CollapsedContent)
                .fillMaxWidth()
                .defaultMinSize(minHeight = ShadeHeader.Dimensions.CollapsedHeight),
    ) {
    val cutoutWidth = LocalDisplayCutout.current.width()
    val cutoutLocation = LocalDisplayCutout.current.location

    // This layout assumes it is globally positioned at (0, 0) and is the
    // same size as the screen.
    Layout(
        modifier = modifier.element(ShadeHeader.Elements.CollapsedContent),
        contents =
            listOf(
                {
                    Row {
                        AndroidView(
                            factory = { context ->
                Clock(ContextThemeWrapper(context, R.style.TextAppearance_QS_Status), null)
                                Clock(
                                    ContextThemeWrapper(context, R.style.TextAppearance_QS_Status),
                                    null
                                )
                            },
                            modifier = Modifier.align(Alignment.CenterVertically),
                        )
                        Spacer(modifier = Modifier.width(5.dp))
                        VariableDayDate(
                            viewModel = viewModel,
            modifier = Modifier.widthIn(max = 90.dp).align(Alignment.CenterVertically),
                            modifier = Modifier.align(Alignment.CenterVertically),
                        )
        Spacer(modifier = Modifier.weight(1f))
                    }
                },
                {
                    Row(horizontalArrangement = Arrangement.End) {
                        SystemIconContainer {
                            StatusIcons(
                                viewModel = viewModel,
                                createTintedIconManager = createTintedIconManager,
                                statusBarIconController = statusBarIconController,
                                useExpandedFormat = useExpandedFormat,
                modifier = Modifier.align(Alignment.CenterVertically).padding(end = 6.dp),
                                modifier =
                                    Modifier.align(Alignment.CenterVertically)
                                        .padding(end = 6.dp)
                                        .weight(1f, fill = false)
                            )
                            BatteryIcon(
                                createBatteryMeterViewController = createBatteryMeterViewController,
@@ -131,6 +148,55 @@ fun SceneScope.CollapsedShadeHeader(
                            )
                        }
                    }
                },
            ),
    ) { measurables, constraints ->
        check(constraints.hasBoundedWidth)
        check(measurables.size == 2)
        check(measurables[0].size == 1)
        check(measurables[1].size == 1)

        val screenWidth = constraints.maxWidth
        val cutoutWidthPx = cutoutWidth.roundToPx()
        val height = ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
        val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height)

        val startMeasurable = measurables[0][0]
        val endMeasurable = measurables[1][0]

        val startPlaceable = startMeasurable.measure(childConstraints)
        val endPlaceable = endMeasurable.measure(childConstraints)

        layout(screenWidth, height) {
            when (cutoutLocation) {
                CutoutLocation.NONE,
                CutoutLocation.RIGHT -> {
                    startPlaceable.placeRelative(x = 0, y = 0)
                    endPlaceable.placeRelative(
                        x = startPlaceable.width,
                        y = 0,
                    )
                }
                CutoutLocation.CENTER -> {
                    startPlaceable.placeRelative(x = 0, y = 0)
                    endPlaceable.placeRelative(
                        x = startPlaceable.width + cutoutWidthPx,
                        y = 0,
                    )
                }
                CutoutLocation.LEFT -> {
                    startPlaceable.placeRelative(
                        x = cutoutWidthPx,
                        y = 0,
                    )
                    endPlaceable.placeRelative(
                        x = startPlaceable.width + cutoutWidthPx,
                        y = 0,
                    )
                }
            }
        }
    }
}

@Composable
@@ -201,7 +267,10 @@ fun SceneScope.ExpandedShadeHeader(
                    createTintedIconManager = createTintedIconManager,
                    statusBarIconController = statusBarIconController,
                    useExpandedFormat = useExpandedFormat,
                    modifier = Modifier.align(Alignment.CenterVertically).padding(end = 6.dp),
                    modifier =
                        Modifier.align(Alignment.CenterVertically)
                            .padding(end = 6.dp)
                            .weight(1f, fill = false),
                )
                BatteryIcon(
                    useExpandedFormat = useExpandedFormat,
Loading