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

Commit 4c5947ed authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Some fixes for performance

* Add @Immutable to some classes
* Use a QSTile.Icon instead of a Supplier in TileUiState to properly
  have equality. As this object is close to the UI, it makes no sense to
  have a supplier and complicate equality. However, this needs to be
  paired with proper QSTile.Icon (ResourceIcon and DrawableIconWithRes)
  to facilitate equality.
* Use produceState to obtain the TileUiState. Because `QSTile.State`
  doesn't have a good equality, and we only care about the uiState, use
  this to avoid recompositions when the legacy state changes but the ui
  state doesn't.
* Add key around Tile composables tied to the spec (identifies the
  tile).

Test: manual, perfetto trace. No recompositions of the tiles
Test: atest com.android.systemui.qs
Flag: com.android.systemui.qs_ui_refactor_compose_fragment
Bug: 383085298
Change-Id: I9a634cabbd1917ead1ced7f0b9fb968225194d24
parent 977e0679
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -89,7 +89,7 @@ class TileRequestDialogViewModelTest : SysuiTestCase() {
                expect.that(state).isEqualTo(expectedState.state)
                expect.that(handlesLongClick).isFalse()
                expect.that(handlesSecondaryClick).isFalse()
                expect.that(icon.get()).isEqualTo(defaultIcon)
                expect.that(icon).isEqualTo(defaultIcon)
                expect.that(sideDrawable).isNull()
                expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
            }
@@ -112,7 +112,7 @@ class TileRequestDialogViewModelTest : SysuiTestCase() {
                expect.that(state).isEqualTo(expectedState.state)
                expect.that(handlesLongClick).isFalse()
                expect.that(handlesSecondaryClick).isFalse()
                expect.that(icon.get()).isEqualTo(QSTileImpl.DrawableIcon(loadedDrawable))
                expect.that(icon).isEqualTo(QSTileImpl.DrawableIcon(loadedDrawable))
                expect.that(sideDrawable).isNull()
                expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
            }
@@ -135,7 +135,7 @@ class TileRequestDialogViewModelTest : SysuiTestCase() {
            underTest.activateIn(testScope)
            runCurrent()

            assertThat(underTest.uiState.icon.get()).isEqualTo(defaultIcon)
            assertThat(underTest.uiState.icon).isEqualTo(defaultIcon)
        }

    companion object {
+43 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.qs.panels.ui.viewmodel

import android.content.res.Resources
import android.content.res.mainResources
import android.graphics.drawable.TestStubDrawable
import android.service.quicksettings.Tile
import android.widget.Button
import android.widget.Switch
@@ -26,9 +27,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import java.util.function.Supplier
import org.junit.Test
import org.junit.runner.RunWith

@@ -263,6 +267,45 @@ class TileUiStateTest : SysuiTestCase() {
            .contains(resources.getString(R.string.tile_unavailable))
    }

    @Test
    fun iconAndSupplier_prefersIcon() {
        val state =
            QSTile.State().apply {
                icon = ResourceIcon.get(R.drawable.android)
                iconSupplier = Supplier { QSTileImpl.DrawableIcon(TestStubDrawable()) }
            }
        val uiState = state.toUiState()

        assertThat(uiState.icon).isEqualTo(state.icon)
    }

    @Test
    fun iconOnly_hasIcon() {
        val state = QSTile.State().apply { icon = ResourceIcon.get(R.drawable.android) }
        val uiState = state.toUiState()

        assertThat(uiState.icon).isEqualTo(state.icon)
    }

    @Test
    fun supplierOnly_hasIcon() {
        val state =
            QSTile.State().apply {
                iconSupplier = Supplier { ResourceIcon.get(R.drawable.android) }
            }
        val uiState = state.toUiState()

        assertThat(uiState.icon).isEqualTo(state.iconSupplier.get())
    }

    @Test
    fun noIconOrSupplier_iconNull() {
        val state = QSTile.State()
        val uiState = state.toUiState()

        assertThat(uiState.icon).isNull()
    }

    private fun QSTile.State.toUiState() = toUiState(resources)
}

+2 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.qs.panels.ui.compose

import android.processor.immutability.Immutable
import com.android.compose.animation.Bounceable
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.ui.model.GridCell
@@ -23,6 +24,7 @@ import com.android.systemui.qs.panels.ui.model.TileGridCell
import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel

@Immutable
data class BounceableInfo(
    val bounceable: BounceableTileViewModel,
    val previousTile: Bounceable?,
+15 −11
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
@@ -67,6 +68,7 @@ fun SceneScope.QuickQuickSettings(
            val it = sizedTiles[spanIndex]
            val column = cellIndex % columns
            cellIndex += it.width
            key(it.tile.spec) {
                Tile(
                    tile = it.tile,
                    iconOnly = it.isIcon,
@@ -74,10 +76,12 @@ fun SceneScope.QuickQuickSettings(
                    squishiness = { squishiness },
                    coroutineScope = scope,
                    bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
                tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider,
                    tileHapticsViewModelFactoryProvider =
                        viewModel.tileHapticsViewModelFactoryProvider,
                    // There should be no QuickQuickSettings when the details view is enabled.
                    detailsViewModel = null,
                )
            }
        }
    }
}
+6 −4
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.qs.panels.ui.compose.infinitegrid

import android.content.Context
import android.graphics.drawable.Animatable
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
@@ -83,7 +84,7 @@ private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target"
fun LargeTileContent(
    label: String,
    secondaryLabel: String?,
    icon: Icon,
    iconProvider: Context.() -> Icon,
    sideDrawable: Drawable?,
    colors: TileColors,
    squishiness: () -> Float,
@@ -129,7 +130,7 @@ fun LargeTileContent(
                }
        ) {
            SmallTileContent(
                icon = icon,
                iconProvider = iconProvider,
                color = colors.icon,
                size = { CommonTileDefaults.LargeTileIconSize },
                modifier = Modifier.align(Alignment.Center),
@@ -194,14 +195,15 @@ fun LargeTileLabels(
@Composable
fun SmallTileContent(
    modifier: Modifier = Modifier,
    icon: Icon,
    iconProvider: Context.() -> Icon,
    color: Color,
    size: () -> Dp = { CommonTileDefaults.IconSize },
    animateToEnd: Boolean = false,
) {
    val context = LocalContext.current
    val icon = iconProvider(context)
    val animatedColor by animateColorAsState(color, label = "QSTileIconColor")
    val iconModifier = modifier.size({ size().roundToPx() }, { size().roundToPx() })
    val context = LocalContext.current
    val loadedDrawable =
        remember(icon, context) {
            when (icon) {
Loading