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

Commit 60d986c9 authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Add edit mode events and QQS/QS open events

Test: atest TileSpecTest EditModeViewModelTest
Test: dump atoms
Bug: 339262836
Bug: 331600035
Flag: com.android.systemui.qs_ui_refactor_compose_fragment
Change-Id: Id3e756823ee314dc1b008d678cd0cbfaa80c36fd
parent a424fedc
Loading
Loading
Loading
Loading
+159 −5
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.ComponentName
import android.graphics.drawable.TestStubDrawable
import android.graphics.drawable.TestStubDrawable
import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.Flags
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription
@@ -29,9 +30,12 @@ import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.ui.compose.toAnnotatedString
import com.android.systemui.common.ui.compose.toAnnotatedString
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.FakeQSFactory
import com.android.systemui.qs.FakeQSFactory
import com.android.systemui.qs.FakeQSTile
import com.android.systemui.qs.FakeQSTile
import com.android.systemui.qs.QSEditEvent
import com.android.systemui.qs.panels.data.repository.stockTilesRepository
import com.android.systemui.qs.panels.data.repository.stockTilesRepository
import com.android.systemui.qs.panels.domain.interactor.FakeTileAvailabilityInteractor
import com.android.systemui.qs.panels.domain.interactor.FakeTileAvailabilityInteractor
import com.android.systemui.qs.panels.domain.interactor.tileAvailabilityInteractorsMap
import com.android.systemui.qs.panels.domain.interactor.tileAvailabilityInteractorsMap
@@ -42,8 +46,10 @@ import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesReposi
import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository
import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.metricSpec
import com.android.systemui.qs.qsTileFactory
import com.android.systemui.qs.qsTileFactory
import com.android.systemui.qs.shared.model.TileCategory
import com.android.systemui.qs.shared.model.TileCategory
import com.android.systemui.qs.tiles.impl.airplane.qsAirplaneModeTileConfig
import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig
import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig
import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
@@ -86,6 +92,7 @@ class EditModeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                qsFlashlightTileConfig,
                qsFlashlightTileConfig,
                qsBatterySaverTileConfig,
                qsBatterySaverTileConfig,
                qsAlarmTileConfig,
                qsAlarmTileConfig,
                qsAirplaneModeTileConfig,
                qsCameraSensorPrivacyToggleTileConfig,
                qsCameraSensorPrivacyToggleTileConfig,
                qsMicrophoneSensorPrivacyToggleTileConfig,
                qsMicrophoneSensorPrivacyToggleTileConfig,
            )
            )
@@ -116,7 +123,7 @@ class EditModeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {


            fakeInstalledTilesRepository.setInstalledServicesForUser(
            fakeInstalledTilesRepository.setInstalledServicesForUser(
                userTracker.userId,
                userTracker.userId,
                listOf(serviceInfo1, serviceInfo2)
                listOf(serviceInfo1, serviceInfo2),
            )
            )


            with(fakeQSTileConfigProvider) { configs.forEach { putConfig(it.tileSpec, it) } }
            with(fakeQSTileConfigProvider) { configs.forEach { putConfig(it.tileSpec, it) } }
@@ -424,10 +431,7 @@ class EditModeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
            testScope.runTest {
            testScope.runTest {
                val tiles by collectLastValue(underTest.tiles)
                val tiles by collectLastValue(underTest.tiles)
                val currentTiles =
                val currentTiles =
                    mutableListOf(
                    mutableListOf(TileSpec.create("flashlight"), TileSpec.create("airplane"))
                        TileSpec.create("flashlight"),
                        TileSpec.create("airplane"),
                    )
                currentTilesInteractor.setTiles(currentTiles)
                currentTilesInteractor.setTiles(currentTiles)
                assertThat(currentTiles.size).isLessThan(minNumberOfTiles)
                assertThat(currentTiles.size).isLessThan(minNumberOfTiles)


@@ -549,6 +553,156 @@ class EditModeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
            }
            }
        }
        }


    // UI EVENT TESTS

    @Test
    fun startEditing_onlyOneEvent() =
        kosmos.runTest {
            underTest.startEditing()
            underTest.startEditing()

            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)

            assertThat(uiEventLoggerFake[0].eventId).isEqualTo(QSEditEvent.QS_EDIT_OPEN.id)
        }

    @Test
    fun stopEditing_notEditing_noEvent() =
        kosmos.runTest {
            underTest.stopEditing()

            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
        }

    @Test
    fun stopEditing_whenEditing_correctEvent() =
        kosmos.runTest {
            underTest.startEditing()
            underTest.stopEditing()

            assertThat(uiEventLoggerFake[1].eventId).isEqualTo(QSEditEvent.QS_EDIT_CLOSED.id)
        }

    @Test
    fun addTile_correctPackageAndPosition() =
        kosmos.runTest {
            val flashlightTile = TileSpec.create("flashlight")
            val airplaneTile = TileSpec.create("airplane")
            val internetTile = TileSpec.create("internet")
            val customTile = TileSpec.create(component2)
            currentTilesInteractor.setTiles(listOf(flashlightTile))
            runCurrent()

            underTest.addTile(airplaneTile)
            underTest.addTile(internetTile, position = 0)
            underTest.addTile(customTile, position = 1)

            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(3)

            with(uiEventLoggerFake[0]) {
                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_ADD.id)
                assertThat(packageName).isEqualTo(airplaneTile.metricSpec)
                assertThat(position).isEqualTo(-1)
            }
            with(uiEventLoggerFake[1]) {
                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_ADD.id)
                assertThat(packageName).isEqualTo(internetTile.metricSpec)
                assertThat(position).isEqualTo(0)
            }
            with(uiEventLoggerFake[2]) {
                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_ADD.id)
                assertThat(packageName).isEqualTo(customTile.metricSpec)
                assertThat(position).isEqualTo(1)
            }
        }

    @Test
    fun addTile_alreadyThere_usesMoveEvent() =
        kosmos.runTest {
            val flashlightTile = TileSpec.create("flashlight")
            val airplaneTile = TileSpec.create("airplane")
            val internetTile = TileSpec.create("internet")
            currentTilesInteractor.setTiles(listOf(flashlightTile, airplaneTile, internetTile))
            runCurrent()

            underTest.addTile(flashlightTile) // adding at the end, should use correct position
            underTest.addTile(internetTile, 0)

            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)

            with(uiEventLoggerFake[0]) {
                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_MOVE.id)
                assertThat(packageName).isEqualTo(flashlightTile.metricSpec)
                // adding at the end, should use correct position
                assertThat(position).isEqualTo(2)
            }
            with(uiEventLoggerFake[1]) {
                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_MOVE.id)
                assertThat(packageName).isEqualTo(internetTile.metricSpec)
                assertThat(position).isEqualTo(0)
            }
        }

    @Test
    fun removeTileEvent() =
        kosmos.runTest {
            val flashlightTile = TileSpec.create("flashlight")
            val airplaneTile = TileSpec.create("airplane")
            val internetTile = TileSpec.create("internet")
            currentTilesInteractor.setTiles(listOf(flashlightTile, airplaneTile, internetTile))
            runCurrent()

            underTest.removeTile(airplaneTile)

            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)

            with(uiEventLoggerFake[0]) {
                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_REMOVE.id)
                assertThat(packageName).isEqualTo(airplaneTile.metricSpec)
            }
        }

    @Test
    fun setTiles_emitsCorrectOperation_individualOperations() =
        kosmos.runTest {
            val flashlightTile = TileSpec.create("flashlight")
            val airplaneTile = TileSpec.create("airplane")
            val internetTile = TileSpec.create("internet")
            val alarmTile = TileSpec.create("alarm")

            currentTilesInteractor.setTiles(listOf(flashlightTile, airplaneTile, internetTile))
            runCurrent()

            // 0. Move flashlightTile to position 2
            underTest.setTiles(listOf(airplaneTile, internetTile, flashlightTile))
            runCurrent()

            // 1. Add alarm tile at position 1
            underTest.setTiles(listOf(airplaneTile, alarmTile, internetTile, flashlightTile))
            runCurrent()

            // 2. Remove internetTile
            underTest.setTiles(listOf(airplaneTile, alarmTile, flashlightTile))
            runCurrent()

            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(3)

            with(uiEventLoggerFake[0]) {
                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_MOVE.id)
                assertThat(packageName).isEqualTo(flashlightTile.metricSpec)
                assertThat(position).isEqualTo(2)
            }
            with(uiEventLoggerFake[1]) {
                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_ADD.id)
                assertThat(packageName).isEqualTo(alarmTile.metricSpec)
                assertThat(position).isEqualTo(1)
            }
            with(uiEventLoggerFake[2]) {
                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_REMOVE.id)
                assertThat(packageName).isEqualTo(internetTile.metricSpec)
            }
        }

    companion object {
    companion object {
        private val drawable1 = TestStubDrawable("drawable1")
        private val drawable1 = TestStubDrawable("drawable1")
        private val appName1 = "App1"
        private val appName1 = "App1"
+18 −0
Original line number Original line Diff line number Diff line
@@ -85,6 +85,24 @@ class TileSpecTest : SysuiTestCase() {
        assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid)
        assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid)
    }
    }


    @Test
    fun metricSpec_invalid() {
        assertThat(TileSpec.Invalid.metricSpec).isEmpty()
    }

    @Test
    fun metricSpec_platform_specName() {
        val tile = "spec"
        assertThat(TileSpec.create(tile).metricSpec).isEqualTo(tile)
    }

    @Test
    fun metricSpec_custom_packageName() {
        val componentName = ComponentName("test_pkg", "test_cls")

        assertThat(TileSpec.create(componentName).metricSpec).isEqualTo(componentName.packageName)
    }

    companion object {
    companion object {
        private const val CUSTOM_TILE_PREFIX = "custom("
        private const val CUSTOM_TILE_PREFIX = "custom("
    }
    }
+8 −2
Original line number Original line Diff line number Diff line
@@ -325,9 +325,15 @@ constructor(
        }
        }


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


            scene(QuickQuickSettings) { QuickQuickSettingsElement() }
            scene(QuickQuickSettings) {
                LaunchedEffect(Unit) { viewModel.onQQSOpen() }
                QuickQuickSettingsElement()
            }
        }
        }
    }
    }


+11 −0
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleCoroutineScope
import com.android.app.animation.Interpolators
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.BouncerPanelExpansionCalculator
import com.android.keyguard.BouncerPanelExpansionCalculator
import com.android.systemui.Dumpable
import com.android.systemui.Dumpable
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.animation.ShadeInterpolation
@@ -51,6 +52,7 @@ import com.android.systemui.media.dagger.MediaModule.QS_PANEL
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.QSEvent
import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeLog
import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeLog
import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule
import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -113,6 +115,7 @@ constructor(
    @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost,
    @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost,
    @Named(QS_PANEL) val qsMediaHost: MediaHost,
    @Named(QS_PANEL) val qsMediaHost: MediaHost,
    @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean,
    @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean,
    private val uiEventLogger: UiEventLogger,
    @Assisted private val lifecycleScope: LifecycleCoroutineScope,
    @Assisted private val lifecycleScope: LifecycleCoroutineScope,
) : Dumpable, ExclusiveActivatable() {
) : Dumpable, ExclusiveActivatable() {


@@ -455,6 +458,14 @@ constructor(
        falsingInteractor.isFalseTouch(Classifier.QS_SWIPE_NESTED)
        falsingInteractor.isFalseTouch(Classifier.QS_SWIPE_NESTED)
    }
    }


    fun onQQSOpen() {
        uiEventLogger.log(QSEvent.QQS_PANEL_EXPANDED)
    }

    fun onQSOpen() {
        uiEventLogger.log(QSEvent.QS_PANEL_EXPANDED)
    }

    override suspend fun onActivated(): Nothing {
    override suspend fun onActivated(): Nothing {
        initMediaHosts() // init regardless of using media (same as current QS).
        initMediaHosts() // init regardless of using media (same as current QS).
        coroutineScope {
        coroutineScope {
+94 −1
Original line number Original line Diff line number Diff line
@@ -18,9 +18,14 @@ package com.android.systemui.qs.panels.ui.viewmodel


import android.content.Context
import android.content.Context
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMap
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import com.android.internal.logging.UiEventLogger
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.QSEditEvent
import com.android.systemui.qs.panels.domain.interactor.EditTilesListInteractor
import com.android.systemui.qs.panels.domain.interactor.EditTilesListInteractor
import com.android.systemui.qs.panels.domain.interactor.GridLayoutTypeInteractor
import com.android.systemui.qs.panels.domain.interactor.GridLayoutTypeInteractor
import com.android.systemui.qs.panels.domain.interactor.TilesAvailabilityInteractor
import com.android.systemui.qs.panels.domain.interactor.TilesAvailabilityInteractor
@@ -30,10 +35,12 @@ import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.metricSpec
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.kotlin.emitOnStart
import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Named
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -45,6 +52,7 @@ import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch


@SysUISingleton
@SysUISingleton
@OptIn(ExperimentalCoroutinesApi::class)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -55,10 +63,12 @@ constructor(
    private val currentTilesInteractor: CurrentTilesInteractor,
    private val currentTilesInteractor: CurrentTilesInteractor,
    private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
    private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
    private val minTilesInteractor: MinimumTilesInteractor,
    private val minTilesInteractor: MinimumTilesInteractor,
    private val uiEventLogger: UiEventLogger,
    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
    @ShadeDisplayAware private val context: Context,
    @ShadeDisplayAware private val context: Context,
    @Named("Default") private val defaultGridLayout: GridLayout,
    @Named("Default") private val defaultGridLayout: GridLayout,
    @Application private val applicationScope: CoroutineScope,
    @Application private val applicationScope: CoroutineScope,
    @Background private val bgDispatcher: CoroutineDispatcher,
    gridLayoutTypeInteractor: GridLayoutTypeInteractor,
    gridLayoutTypeInteractor: GridLayoutTypeInteractor,
    gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
    gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
) {
) {
@@ -149,11 +159,17 @@ constructor(


    /** @see isEditing */
    /** @see isEditing */
    fun startEditing() {
    fun startEditing() {
        if (!isEditing.value) {
            uiEventLogger.log(QSEditEvent.QS_EDIT_OPEN)
        }
        _isEditing.value = true
        _isEditing.value = true
    }
    }


    /** @see isEditing */
    /** @see isEditing */
    fun stopEditing() {
    fun stopEditing() {
        if (isEditing.value) {
            uiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED)
        }
        _isEditing.value = false
        _isEditing.value = false
    }
    }


@@ -164,6 +180,7 @@ constructor(
    fun addTile(tileSpec: TileSpec, position: Int = POSITION_AT_END) {
    fun addTile(tileSpec: TileSpec, position: Int = POSITION_AT_END) {
        val specs = currentTilesInteractor.currentTilesSpecs.toMutableList()
        val specs = currentTilesInteractor.currentTilesSpecs.toMutableList()
        val currentPosition = specs.indexOf(tileSpec)
        val currentPosition = specs.indexOf(tileSpec)
        val moved = currentPosition != -1


        if (currentPosition != -1) {
        if (currentPosition != -1) {
            // No operation needed if the element is already in the list at the right position
            // No operation needed if the element is already in the list at the right position
@@ -179,6 +196,12 @@ constructor(
        } else {
        } else {
            specs.add(tileSpec)
            specs.add(tileSpec)
        }
        }
        uiEventLogger.logWithPosition(
            if (moved) QSEditEvent.QS_EDIT_MOVE else QSEditEvent.QS_EDIT_ADD,
            /* uid= */ 0,
            /* packageName= */ tileSpec.metricSpec,
            if (moved && position == POSITION_AT_END) specs.size - 1 else position,
        )


        // Setting the new tiles as one operation to avoid UI jank with tiles disappearing and
        // Setting the new tiles as one operation to avoid UI jank with tiles disappearing and
        // reappearing
        // reappearing
@@ -187,10 +210,80 @@ constructor(


    /** Immediately removes [tileSpec] from the current tiles. */
    /** Immediately removes [tileSpec] from the current tiles. */
    fun removeTile(tileSpec: TileSpec) {
    fun removeTile(tileSpec: TileSpec) {
        uiEventLogger.log(
            QSEditEvent.QS_EDIT_REMOVE,
            /* uid= */ 0,
            /* packageName= */ tileSpec.metricSpec,
        )
        currentTilesInteractor.removeTiles(listOf(tileSpec))
        currentTilesInteractor.removeTiles(listOf(tileSpec))
    }
    }


    fun setTiles(tileSpecs: List<TileSpec>) {
    fun setTiles(tileSpecs: List<TileSpec>) {
        val currentTiles = currentTilesInteractor.currentTilesSpecs
        currentTilesInteractor.setTiles(tileSpecs)
        currentTilesInteractor.setTiles(tileSpecs)
        applicationScope.launch(bgDispatcher) {
            calculateDiffsAndEmitUiEvents(currentTiles, tileSpecs)
        }
    }

    private fun calculateDiffsAndEmitUiEvents(
        currentTiles: List<TileSpec>,
        newTiles: List<TileSpec>,
    ) {
        val listDiff = DiffUtil.calculateDiff(DiffCallback(currentTiles, newTiles))
        listDiff.dispatchUpdatesTo(
            object : ListUpdateCallback {
                override fun onInserted(position: Int, count: Int) {
                    newTiles.getOrNull(position)?.let {
                        uiEventLogger.logWithPosition(
                            QSEditEvent.QS_EDIT_ADD,
                            /* uid= */ 0,
                            /* packageName= */ it.metricSpec,
                            position,
                        )
                    }
                }

                override fun onRemoved(position: Int, count: Int) {
                    currentTiles.getOrNull(position)?.let {
                        uiEventLogger.log(QSEditEvent.QS_EDIT_REMOVE, 0, it.metricSpec)
                    }
                }

                override fun onMoved(fromPosition: Int, toPosition: Int) {
                    currentTiles.getOrNull(fromPosition)?.let {
                        uiEventLogger.logWithPosition(
                            QSEditEvent.QS_EDIT_MOVE,
                            /* uid= */ 0,
                            /* packageName= */ it.metricSpec,
                            toPosition,
                        )
                    }
                }

                override fun onChanged(position: Int, count: Int, payload: Any?) {}
            }
        )
    }
}

private class DiffCallback(
    private val currentList: List<TileSpec>,
    private val newList: List<TileSpec>,
) : DiffUtil.Callback() {
    override fun getOldListSize(): Int {
        return currentList.size
    }

    override fun getNewListSize(): Int {
        return newList.size
    }

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return currentList[oldItemPosition] == newList[newItemPosition]
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return areItemsTheSame(oldItemPosition, newItemPosition)
    }
    }
}
}
Loading