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

Commit 6eb67e77 authored by Michael Mikhail's avatar Michael Mikhail
Browse files

[Flexiglass] remove unnecessary code of data layer in media.controls package

Flag: com.android.systemui.scene_container
Fixes: 436384980
Test: atest MediaDataFilterImplTest
Test: atest MediaDataProcessorTest
Test: atest SystemUiRoboTests:StatusBarPopupChipsViewModelTest
Change-Id: Idccc3ba622bc8fea9b04159fcb347a1751ffbdb9
parent cd66423b
Loading
Loading
Loading
Loading
+0 −265
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.media.controls.data.repository

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class MediaFilterRepositoryTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val underTest: MediaFilterRepository = with(kosmos) { mediaFilterRepository }

    @Test
    fun addCurrentUserMediaEntry_activeThenInactivate() =
        testScope.runTest {
            val currentUserEntries by collectLastValue(underTest.currentUserEntries)

            val instanceId = InstanceId.fakeInstanceId(123)
            val userMedia = MediaData().copy(active = true, instanceId = instanceId)

            underTest.addCurrentUserMediaEntry(userMedia)

            assertThat(currentUserEntries?.get(instanceId)).isEqualTo(userMedia)
            assertThat(underTest.hasActiveMedia()).isTrue()
            assertThat(underTest.hasAnyMedia()).isTrue()

            underTest.addCurrentUserMediaEntry(userMedia.copy(active = false))

            assertThat(currentUserEntries?.get(instanceId)).isNotEqualTo(userMedia)
            assertThat(currentUserEntries?.get(instanceId)?.active).isFalse()
            assertThat(underTest.hasActiveMedia()).isFalse()
            assertThat(underTest.hasAnyMedia()).isTrue()
        }

    @Test
    fun addCurrentUserMediaEntry_thenRemove_returnsBoolean() =
        testScope.runTest {
            val currentUserEntries by collectLastValue(underTest.currentUserEntries)

            val instanceId = InstanceId.fakeInstanceId(123)
            val userMedia = MediaData().copy(instanceId = instanceId)

            underTest.addCurrentUserMediaEntry(userMedia)

            assertThat(currentUserEntries?.get(instanceId)).isEqualTo(userMedia)
            assertThat(underTest.hasActiveMedia()).isTrue()
            assertThat(underTest.hasAnyMedia()).isTrue()

            assertThat(underTest.removeCurrentUserMediaEntry(instanceId, userMedia)).isTrue()
            assertThat(underTest.hasActiveMedia()).isFalse()
            assertThat(underTest.hasAnyMedia()).isFalse()
        }

    @Test
    fun addCurrentUserMediaEntry_thenRemove_returnsValue() =
        testScope.runTest {
            val currentUserEntries by collectLastValue(underTest.currentUserEntries)

            val instanceId = InstanceId.fakeInstanceId(123)
            val userMedia = MediaData().copy(instanceId = instanceId)

            underTest.addCurrentUserMediaEntry(userMedia)

            assertThat(currentUserEntries?.get(instanceId)).isEqualTo(userMedia)

            assertThat(underTest.removeCurrentUserMediaEntry(instanceId)).isEqualTo(userMedia)
        }

    @Test
    fun addMediaEntry_activeThenInactivate() =
        testScope.runTest {
            val allMediaEntries by collectLastValue(underTest.allMediaEntries)

            val userMedia = MediaData().copy(active = true)

            underTest.addMediaEntry(KEY, userMedia)

            assertThat(allMediaEntries?.get(KEY)).isEqualTo(userMedia)

            underTest.addMediaEntry(KEY, userMedia.copy(active = false))

            assertThat(allMediaEntries?.get(KEY)).isNotEqualTo(userMedia)
            assertThat(allMediaEntries?.get(KEY)?.active).isFalse()
        }

    @Test
    fun addMediaEntry_thenRemove_returnsValue() =
        testScope.runTest {
            val allMediaEntries by collectLastValue(underTest.allMediaEntries)

            val userMedia = MediaData()

            underTest.addMediaEntry(KEY, userMedia)

            assertThat(allMediaEntries?.get(KEY)).isEqualTo(userMedia)

            assertThat(underTest.removeMediaEntry(KEY)).isEqualTo(userMedia)
        }

    @Test
    fun addMediaControlPlayingThenRemote() =
        testScope.runTest {
            val currentMedia by collectLastValue(underTest.currentMedia)
            val playingInstanceId = InstanceId.fakeInstanceId(123)
            val remoteInstanceId = InstanceId.fakeInstanceId(321)
            val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId)
            val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId)

            underTest.addCurrentUserMediaEntry(playingData)

            underTest.addCurrentUserMediaEntry(remoteData)

            assertThat(currentMedia?.size).isEqualTo(2)
            assertThat(currentMedia)
                .containsExactly(
                    MediaCommonModel(MediaDataLoadingModel.Loaded(playingInstanceId)),
                    MediaCommonModel(MediaDataLoadingModel.Loaded(remoteInstanceId)),
                )
                .inOrder()
        }

    @Test
    fun switchMediaControlsPlaying() =
        testScope.runTest {
            val currentMedia by collectLastValue(underTest.currentMedia)
            val playingInstanceId1 = InstanceId.fakeInstanceId(123)
            val playingInstanceId2 = InstanceId.fakeInstanceId(321)
            var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1)
            var playingData2 = createMediaData("app2", false, LOCAL, false, playingInstanceId2)

            underTest.addCurrentUserMediaEntry(playingData1)
            underTest.addCurrentUserMediaEntry(playingData2)

            assertThat(currentMedia?.size).isEqualTo(2)
            assertThat(currentMedia)
                .containsExactly(
                    MediaCommonModel(MediaDataLoadingModel.Loaded(playingInstanceId1)),
                    MediaCommonModel(MediaDataLoadingModel.Loaded(playingInstanceId2)),
                )
                .inOrder()

            playingData1 = createMediaData("app1", false, LOCAL, false, playingInstanceId1)
            playingData2 = createMediaData("app2", true, LOCAL, false, playingInstanceId2)

            underTest.addCurrentUserMediaEntry(playingData1)
            underTest.addCurrentUserMediaEntry(playingData2)

            assertThat(currentMedia?.size).isEqualTo(2)
            assertThat(currentMedia)
                .containsExactly(
                    MediaCommonModel(MediaDataLoadingModel.Loaded(playingInstanceId1)),
                    MediaCommonModel(MediaDataLoadingModel.Loaded(playingInstanceId2)),
                )
                .inOrder()

            underTest.setOrderedMedia()

            assertThat(currentMedia?.size).isEqualTo(2)
            assertThat(currentMedia)
                .containsExactly(
                    MediaCommonModel(MediaDataLoadingModel.Loaded(playingInstanceId2)),
                    MediaCommonModel(MediaDataLoadingModel.Loaded(playingInstanceId1)),
                )
                .inOrder()
        }

    @Test
    fun fullOrderTest() =
        testScope.runTest {
            val currentMedia by collectLastValue(underTest.currentMedia)
            val instanceId1 = InstanceId.fakeInstanceId(123)
            val instanceId2 = InstanceId.fakeInstanceId(456)
            val instanceId3 = InstanceId.fakeInstanceId(321)
            val instanceId4 = InstanceId.fakeInstanceId(654)
            val instanceId5 = InstanceId.fakeInstanceId(124)
            val playingAndLocalData = createMediaData("app1", true, LOCAL, false, instanceId1)
            val playingAndRemoteData = createMediaData("app2", true, REMOTE, false, instanceId2)
            val stoppedAndLocalData = createMediaData("app3", false, LOCAL, false, instanceId3)
            val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4)
            val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5)

            underTest.addCurrentUserMediaEntry(stoppedAndLocalData)

            underTest.addCurrentUserMediaEntry(stoppedAndRemoteData)

            underTest.addCurrentUserMediaEntry(canResumeData)

            underTest.addCurrentUserMediaEntry(playingAndLocalData)

            underTest.addCurrentUserMediaEntry(playingAndRemoteData)

            underTest.setOrderedMedia()

            assertThat(currentMedia?.size).isEqualTo(5)
            assertThat(currentMedia)
                .containsExactly(
                    MediaCommonModel(MediaDataLoadingModel.Loaded(instanceId1)),
                    MediaCommonModel(MediaDataLoadingModel.Loaded(instanceId2)),
                    MediaCommonModel(MediaDataLoadingModel.Loaded(instanceId4)),
                    MediaCommonModel(MediaDataLoadingModel.Loaded(instanceId3)),
                    MediaCommonModel(MediaDataLoadingModel.Loaded(instanceId5)),
                )
                .inOrder()
        }

    @Test
    fun hasAnyMedia_noMediaSet_returnsFalse() =
        testScope.runTest { assertThat(underTest.hasAnyMedia()).isFalse() }

    @Test
    fun hasActiveMedia_noMediaSet_returnsFalse() =
        testScope.runTest { assertThat(underTest.hasActiveMedia()).isFalse() }

    private fun createMediaData(
        app: String,
        playing: Boolean,
        playbackLocation: Int,
        isResume: Boolean,
        instanceId: InstanceId,
    ): MediaData {
        return MediaData(
            playbackLocation = playbackLocation,
            resumption = isResume,
            notificationKey = "key: $app",
            isPlaying = playing,
            instanceId = instanceId,
        )
    }

    companion object {
        private const val LOCAL = MediaData.PLAYBACK_LOCAL
        private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
        private const val KEY = "KEY"
    }
}
+3 −3
Original line number Original line Diff line number Diff line
@@ -25,8 +25,8 @@ import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.remedia.data.repository.mediaPipelineRepository
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
import com.android.systemui.statusbar.featurepods.popups.ui.model.PopupChipId
import com.android.systemui.statusbar.featurepods.popups.ui.model.PopupChipId
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModelFactory
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModelFactory
@@ -61,7 +61,7 @@ class StatusBarPopupChipsViewModelTest : SysuiTestCase() {
            val shownPopupChips = underTest.shownPopupChips
            val shownPopupChips = underTest.shownPopupChips
            val userMedia = MediaData(active = true, song = "test")
            val userMedia = MediaData(active = true, song = "test")


            mediaFilterRepository.addCurrentUserMediaEntry(userMedia)
            mediaPipelineRepository.addCurrentUserMediaEntry(userMedia)


            Snapshot.takeSnapshot {
            Snapshot.takeSnapshot {
                assertThat(shownPopupChips).hasSize(1)
                assertThat(shownPopupChips).hasSize(1)
@@ -76,7 +76,7 @@ class StatusBarPopupChipsViewModelTest : SysuiTestCase() {


            val userMedia = MediaData(active = true, song = "test")
            val userMedia = MediaData(active = true, song = "test")


            mediaFilterRepository.addCurrentUserMediaEntry(userMedia)
            mediaPipelineRepository.addCurrentUserMediaEntry(userMedia)


            Snapshot.takeSnapshot {
            Snapshot.takeSnapshot {
                assertThat(shownPopupChips).hasSize(1)
                assertThat(shownPopupChips).hasSize(1)
+0 −166
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.media.controls.data.repository

import android.content.Context
import com.android.internal.logging.InstanceId
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.media.controls.data.model.MediaSortKeyModel
import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.remedia.data.model.UpdateArtInfoModel
import com.android.systemui.media.remedia.data.repository.MediaPipelineRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.SystemClock
import java.util.TreeMap
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

/** A repository that holds the state of filtered media data on the device. */
@SysUISingleton
class MediaFilterRepository
@Inject
constructor(
    @Application private val applicationContext: Context,
    @Application applicationScope: CoroutineScope,
    @Background backgroundDispatcher: CoroutineDispatcher,
    private val systemClock: SystemClock,
    secureSettings: SecureSettings,
) :
    MediaPipelineRepository(
        applicationContext,
        applicationScope,
        backgroundDispatcher,
        secureSettings,
    ) {

    private val _currentMedia: MutableStateFlow<List<MediaCommonModel>> =
        MutableStateFlow(mutableListOf())
    val currentMedia = _currentMedia.asStateFlow()

    private var sortedMedia = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)

    override fun addCurrentUserMediaEntry(data: MediaData): UpdateArtInfoModel? {
        return super.addCurrentUserMediaEntry(data).also { updateModel ->
            addMediaDataLoadingState(
                MediaDataLoadingModel.Loaded(data.instanceId),
                isUpdate = updateModel != null,
            )
        }
    }

    override fun removeCurrentUserMediaEntry(key: InstanceId): MediaData? {
        return super.removeCurrentUserMediaEntry(key)?.also {
            addMediaDataLoadingState(MediaDataLoadingModel.Removed(key))
        }
    }

    override fun removeCurrentUserMediaEntry(key: InstanceId, data: MediaData): Boolean {
        return super.removeCurrentUserMediaEntry(key, data).also {
            addMediaDataLoadingState(MediaDataLoadingModel.Removed(key))
        }
    }

    override fun clearCurrentUserMedia() {
        val userEntries = LinkedHashMap<InstanceId, MediaData>(mutableUserEntries.value)
        mutableUserEntries.value = LinkedHashMap()
        userEntries.forEach { addMediaDataLoadingState(MediaDataLoadingModel.Removed(it.key)) }
    }

    private fun addMediaDataLoadingState(
        mediaDataLoadingModel: MediaDataLoadingModel,
        isUpdate: Boolean = true,
    ) {
        val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
        sortedMap.putAll(
            sortedMedia.filter { (_, commonModel) ->
                commonModel.mediaLoadedModel.instanceId != mediaDataLoadingModel.instanceId
            }
        )

        mutableUserEntries.value[mediaDataLoadingModel.instanceId]?.let {
            val sortKey =
                MediaSortKeyModel(
                    it.isPlaying,
                    it.playbackLocation,
                    it.active,
                    it.resumption,
                    it.lastActive,
                    it.notificationKey,
                    systemClock.currentTimeMillis(),
                    it.instanceId,
                )

            if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) {
                val newCommonModel =
                    MediaCommonModel(
                        mediaDataLoadingModel,
                        canBeRemoved(it),
                        if (isUpdate) systemClock.currentTimeMillis() else 0,
                    )
                sortedMap[sortKey] = newCommonModel

                var isNewToCurrentMedia = true
                val currentList =
                    mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) }
                currentList.forEachIndexed { index, mediaCommonModel ->
                    if (
                        mediaCommonModel.mediaLoadedModel.instanceId ==
                            mediaDataLoadingModel.instanceId
                    ) {
                        // When loading an update for an existing media control.
                        isNewToCurrentMedia = false
                        if (mediaCommonModel != newCommonModel) {
                            // Update media model if changed.
                            currentList[index] = newCommonModel
                        }
                    }
                }
                if (isNewToCurrentMedia && it.active) {
                    _currentMedia.value = sortedMap.values.toList()
                } else {
                    _currentMedia.value = currentList
                }

                sortedMedia = sortedMap
            }
        }

        // On removal we want to keep the order being shown to user.
        if (mediaDataLoadingModel is MediaDataLoadingModel.Removed) {
            _currentMedia.value =
                _currentMedia.value.filter { commonModel ->
                    mediaDataLoadingModel.instanceId != commonModel.mediaLoadedModel.instanceId
                }
            sortedMedia = sortedMap
        }
    }

    fun setOrderedMedia() {
        _currentMedia.value = sortedMedia.values.toList()
    }

    private fun canBeRemoved(data: MediaData): Boolean {
        return data.isPlaying?.let { !it } ?: data.isClearable && !data.active
    }
}
+0 −24
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.media.controls.shared.model

/** Models any type of media. */
data class MediaCommonModel(
    val mediaLoadedModel: MediaDataLoadingModel.Loaded,
    val canBeRemoved: Boolean = false,
    val updateTime: Long = 0L,
)
+0 −31
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.media.controls.shared.model

import com.android.internal.logging.InstanceId

/** Models media data loading state. */
sealed class MediaDataLoadingModel {

    abstract val instanceId: InstanceId

    /** Media data has been loaded. */
    data class Loaded(override val instanceId: InstanceId) : MediaDataLoadingModel()

    /** Media data has been removed. */
    data class Removed(override val instanceId: InstanceId) : MediaDataLoadingModel()
}
Loading