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

Commit 7e95ede5 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Fabián Kozynski
Browse files

Always compose QS fragment

This makes it so opening the shade or expanding QS doesn't require
composing all components.

We limit the tiles to listen only when the content should be visible
(and for expanded QS, also when we are not fully collapsed). This
starting to listen causes a small recomposition, but only of the tiles,
not the full grid. Tiles not listening will prevent their UI from
updating.

Notice that other components are not gated by whether we are visible. In
particular:
* Brightness is always composed, which means its viewmodel is always
  collecting. But we don't expect a large issue here.
* Media is explicitly not gated. This guarantees that as soon as a media
  player should be visible, it's composed and measured.

Bug: 389985793
Bug: 399825091
Test: Manual, perfetto shows shorter recomposition on opening shade and
expanding
Test: Manual, dump QSLog and see that tiles are listening at the correct
points
Flag: com.android.systemui.qs_ui_refactor_compose_fragment
Flag: com.android.systemui.always_compose_qs_ui_fragment

Change-Id: I07022bbc0266bf3f1438efcf5759743c9e254f31
parent c474a3cc
Loading
Loading
Loading
Loading
+48 −5
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
@@ -250,10 +251,23 @@ constructor(
    private fun Content() {
        PlatformTheme(isDarkTheme = true) {
            ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) {
                if (viewModel.isQsVisibleAndAnyShadeExpanded) {
                // TODO(b/389985793): Make sure that there is no coroutine work or recompositions
                // happening when alwaysCompose is true but isQsVisibleAndAnyShadeExpanded is false.
                if (alwaysCompose || viewModel.isQsVisibleAndAnyShadeExpanded) {
                    Box(
                        modifier =
                            Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
                            Modifier.thenIf(alwaysCompose) {
                                    Modifier.layout { measurable, constraints ->
                                        measurable.measure(constraints).run {
                                            layout(width, height) {
                                                if (viewModel.isQsVisibleAndAnyShadeExpanded) {
                                                    place(0, 0)
                                                }
                                            }
                                        }
                                    }
                                }
                                .graphicsLayer { alpha = viewModel.viewAlpha }
                                .thenIf(notificationScrimClippingParams.isEnabled) {
                                    Modifier.notificationScrimClip {
                                        notificationScrimClippingParams.params
@@ -331,12 +345,12 @@ constructor(
        }

        SceneTransitionLayout(state = sceneState, modifier = Modifier.fillMaxSize()) {
            scene(QuickSettings) {
            scene(QuickSettings, alwaysCompose = alwaysCompose) {
                LaunchedEffect(Unit) { viewModel.onQSOpen() }
                Element(QuickSettings.rootElementKey, Modifier) { QuickSettingsElement() }
            }

            scene(QuickQuickSettings) {
            scene(QuickQuickSettings, alwaysCompose = alwaysCompose) {
                LaunchedEffect(Unit) { viewModel.onQQSOpen() }
                // Cannot pass the element modifier in because the top element has a `testTag`
                // and this would overwrite it.
@@ -626,7 +640,20 @@ constructor(
            ) {
                val Tiles =
                    @Composable {
                        QuickQuickSettings(viewModel = viewModel.quickQuickSettingsViewModel)
                        QuickQuickSettings(
                            viewModel = viewModel.quickQuickSettingsViewModel,
                            listening = {
                                /*
                                 *  When always compose is false, this will always be true, and we'll be
                                 *  listening whenever this is composed.
                                 *  When always compose is true, we listen if we are visible and not
                                 *  fully expanded
                                 */
                                !alwaysCompose ||
                                    (viewModel.isQsVisibleAndAnyShadeExpanded &&
                                        viewModel.expansionState.progress < 1f)
                            },
                        )
                    }
                val Media =
                    @Composable {
@@ -726,6 +753,18 @@ constructor(
                                    TileGrid(
                                        viewModel = containerViewModel.tileGridViewModel,
                                        modifier = Modifier.fillMaxWidth(),
                                        listening = {
                                            /*
                                             *  When always compose is false, this will always be true,
                                             *  and we'll be listening whenever this is composed.
                                             *  When always compose is true, we look a the second
                                             *  condition and we'll listen if QS is visible AND we are
                                             *  not fully collapsed.
                                             */
                                            !alwaysCompose ||
                                                (viewModel.isQsVisibleAndAnyShadeExpanded &&
                                                    viewModel.expansionState.progress > 0f)
                                        },
                                    )
                                }
                            }
@@ -830,6 +869,7 @@ constructor(
                println("qqsPositionOnScreen", rect)
            }
            println("QQS visible", qqsVisible.value)
            println("Always composed", alwaysCompose)
            if (::viewModel.isInitialized) {
                printSection("View Model") { viewModel.dump(this@run, args) }
            }
@@ -1177,3 +1217,6 @@ private fun interactionsConfig() =
        // we are OK using this as our content is clipped and all corner radius are larger than this
        surfaceCornerRadius = 28.dp,
    )

private inline val alwaysCompose
    get() = Flags.alwaysComposeQsUiFragment()
+11 −1
Original line number Diff line number Diff line
@@ -27,7 +27,17 @@ import com.android.systemui.qs.pipeline.shared.TileSpec

/** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */
interface GridLayout {
    @Composable fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier)

    /**
     * [listening] can be used to compose the grid but limit when tiles should be listening. It
     * should be a function tracking a snapshot state.
     */
    @Composable
    fun ContentScope.TileGrid(
        tiles: List<TileViewModel>,
        modifier: Modifier,
        listening: () -> Boolean,
    )

    @Composable
    fun EditTileGrid(
+6 −8
Original line number Diff line number Diff line
@@ -32,7 +32,6 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
@@ -65,17 +64,16 @@ constructor(
    @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout,
) : GridLayout by delegateGridLayout {
    @Composable
    override fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) {
    override fun ContentScope.TileGrid(
        tiles: List<TileViewModel>,
        modifier: Modifier,
        listening: () -> Boolean,
    ) {
        val viewModel =
            rememberViewModel(traceName = "PaginatedGridLayout-TileGrid") {
                viewModelFactory.create()
            }

        DisposableEffect(tiles) {
            val token = Any()
            tiles.forEach { it.startListening(token) }
            onDispose { tiles.forEach { it.stopListening(token) } }
        }
        val columns = viewModel.columns
        val rows = integerResource(R.integer.quick_settings_paginated_grid_num_rows)

@@ -122,7 +120,7 @@ constructor(
            ) {
                val page = pages[it]

                with(delegateGridLayout) { TileGrid(tiles = page, modifier = Modifier) }
                with(delegateGridLayout) { TileGrid(tiles = page, modifier = Modifier, listening) }
            }
            FooterBar(
                buildNumberViewModelFactory = viewModel.buildNumberViewModelFactory,
+3 −6
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.qs.panels.ui.compose

import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -41,6 +40,7 @@ import com.android.systemui.res.R
fun ContentScope.QuickQuickSettings(
    viewModel: QuickQuickSettingsViewModel,
    modifier: Modifier = Modifier,
    listening: () -> Boolean,
) {

    val sizedTiles = viewModel.tileViewModels
@@ -51,11 +51,6 @@ fun ContentScope.QuickQuickSettings(

    val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } }

    DisposableEffect(tiles) {
        val token = Any()
        tiles.forEach { it.startListening(token) }
        onDispose { tiles.forEach { it.stopListening(token) } }
    }
    val columns = viewModel.columns
    Box(modifier = modifier) {
        GridAnchor()
@@ -91,4 +86,6 @@ fun ContentScope.QuickQuickSettings(
            }
        }
    }

    TileListener(tiles, listening)
}
+6 −2
Original line number Diff line number Diff line
@@ -22,9 +22,13 @@ import com.android.compose.animation.scene.ContentScope
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel

@Composable
fun ContentScope.TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) {
fun ContentScope.TileGrid(
    viewModel: TileGridViewModel,
    modifier: Modifier = Modifier,
    listening: () -> Boolean = { true },
) {
    val gridLayout = viewModel.gridLayout
    val tiles = viewModel.tileViewModels

    with(gridLayout) { TileGrid(tiles, modifier) }
    with(gridLayout) { TileGrid(tiles, modifier, listening) }
}
Loading