diff options
| author | 2024-04-11 03:31:53 +0000 | |
|---|---|---|
| committer | 2024-04-11 03:31:53 +0000 | |
| commit | 328fca2caf65a8520f8a3ab596fbce47802fe0a4 (patch) | |
| tree | d324024bdda94e77f395aaa92c449d2909054f8a | |
| parent | 89dd306e77582e3e2d3d7c49283f36b4380bea5d (diff) | |
| parent | cdb6ccb8929e7172da68958fc116e19af9e6ebbe (diff) | |
Merge "Add loading states for media changes." into main
8 files changed, 554 insertions, 289 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt index 956ef661d467..33eb90acdcb3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt @@ -26,7 +26,9 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.MediaTestHelper import com.android.systemui.media.controls.shared.model.MediaData +import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel import com.android.systemui.media.controls.shared.model.SmartspaceMediaData +import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -144,6 +146,37 @@ class MediaFilterRepositoryTest : SysuiTestCase() { assertThat(smartspaceMediaData?.isActive).isFalse() } + @Test + fun addMediaDataLoadingState() = + testScope.runTest { + val mediaDataLoadedStates by collectLastValue(underTest.mediaDataLoadedStates) + val instanceId = InstanceId.fakeInstanceId(123) + val mediaLoadedStates = mutableListOf(MediaDataLoadingModel.Loaded(instanceId)) + + underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates) + + mediaLoadedStates.remove(MediaDataLoadingModel.Loaded(instanceId)) + + underTest.addMediaDataLoadingState(MediaDataLoadingModel.Removed(instanceId)) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates) + } + + @Test + fun setRecommendationsLoadingState() = + testScope.runTest { + val recommendationsLoadingState by + collectLastValue(underTest.recommendationsLoadingState) + val recommendationsLoadingModel = + SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) + + underTest.setRecommedationsLoadingState(recommendationsLoadingModel) + + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) + } + companion object { private const val KEY = "KEY" private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt index d9d84f2d2aac..a0a1eb3a6ca6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt @@ -20,6 +20,7 @@ import android.R import android.graphics.drawable.Icon 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.flags.Flags @@ -28,13 +29,20 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.MediaTestHelper import com.android.systemui.media.controls.data.repository.MediaFilterRepository import com.android.systemui.media.controls.data.repository.mediaFilterRepository +import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor +import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter import com.android.systemui.media.controls.shared.model.MediaData +import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel import com.android.systemui.media.controls.shared.model.SmartspaceMediaData +import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel +import com.android.systemui.statusbar.notificationLockscreenUserManager import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -45,9 +53,17 @@ class MediaCarouselInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope + private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter + private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository + private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor + @Before + fun setUp() { + underTest.start() + } + @Test fun addUserMediaEntry_activeThenInactivate() = testScope.runTest { @@ -56,7 +72,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() { val hasActiveMedia by collectLastValue(underTest.hasActiveMedia) val hasAnyMedia by collectLastValue(underTest.hasAnyMedia) - val userMedia = MediaData().copy(active = true) + val userMedia = MediaData(active = true) mediaFilterRepository.addSelectedUserMediaEntry(userMedia) @@ -79,7 +95,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() { val hasActiveMedia by collectLastValue(underTest.hasActiveMedia) val hasAnyMedia by collectLastValue(underTest.hasAnyMedia) - val userMedia = MediaData().copy(active = false) + val userMedia = MediaData(active = false) val instanceId = userMedia.instanceId mediaFilterRepository.addSelectedUserMediaEntry(userMedia) @@ -112,7 +128,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() { isActive = true, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) - val userMedia = MediaData().copy(active = false) + val userMedia = MediaData(active = false) mediaFilterRepository.setRecommendation(userMediaRecommendation) @@ -199,7 +215,80 @@ class MediaCarouselInteractorTest : SysuiTestCase() { fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() = testScope.runTest { assertThat(underTest.hasActiveMediaOrRecommendation.value).isFalse() } + @Test + fun onMediaDataUpdated_updatesLoadingState() = + testScope.runTest { + whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true) + whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true) + val mediaDataLoadedStates by collectLastValue(underTest.mediaDataLoadedStates) + val instanceId = InstanceId.fakeInstanceId(123) + val mediaLoadedStates: MutableList<MediaDataLoadingModel> = mutableListOf() + + mediaLoadedStates.add(MediaDataLoadingModel.Loaded(instanceId)) + mediaDataFilter.onMediaDataLoaded( + KEY, + KEY, + MediaData(userId = USER_ID, instanceId = instanceId) + ) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates) + + val newInstanceId = InstanceId.fakeInstanceId(321) + + mediaLoadedStates.add(MediaDataLoadingModel.Loaded(newInstanceId)) + mediaDataFilter.onMediaDataLoaded( + KEY_2, + KEY_2, + MediaData(userId = USER_ID, instanceId = newInstanceId) + ) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates) + + mediaLoadedStates.remove(MediaDataLoadingModel.Loaded(instanceId)) + + mediaDataFilter.onMediaDataRemoved(KEY) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates) + + mediaLoadedStates.remove(MediaDataLoadingModel.Loaded(newInstanceId)) + + mediaDataFilter.onMediaDataRemoved(KEY_2) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates) + } + + @Test + fun onMediaRecommendationsUpdated_updatesLoadingState() = + testScope.runTest { + whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true) + whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true) + val recommendationsLoadingState by + collectLastValue(underTest.recommendationsLoadingState) + val icon = Icon.createWithResource(context, R.drawable.ic_media_play) + val mediaRecommendations = + SmartspaceMediaData( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + recommendations = MediaTestHelper.getValidRecommendationList(icon), + ) + var recommendationsLoadingModel: SmartspaceMediaLoadingModel = + SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, isPrioritized = true) + + mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, mediaRecommendations) + + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) + + recommendationsLoadingModel = SmartspaceMediaLoadingModel.Removed(KEY_MEDIA_SMARTSPACE) + + mediaDataFilter.onSmartspaceMediaDataRemoved(KEY_MEDIA_SMARTSPACE) + + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) + } + companion object { + private const val KEY = "key" + private const val KEY_2 = "key2" + private const val USER_ID = 0 private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt index df34169c9a50..9dc5900296c2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt @@ -19,7 +19,9 @@ package com.android.systemui.media.controls.data.repository import com.android.internal.logging.InstanceId import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.shared.model.MediaData +import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel import com.android.systemui.media.controls.shared.model.SmartspaceMediaData +import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -46,6 +48,16 @@ class MediaFilterRepository @Inject constructor() { MutableStateFlow(LinkedHashMap()) val allUserEntries: StateFlow<Map<String, MediaData>> = _allUserEntries.asStateFlow() + private val _mediaDataLoadedStates: MutableStateFlow<List<MediaDataLoadingModel>> = + MutableStateFlow(mutableListOf()) + val mediaDataLoadedStates: StateFlow<List<MediaDataLoadingModel>> = + _mediaDataLoadedStates.asStateFlow() + + private val _recommendationsLoadingState: MutableStateFlow<SmartspaceMediaLoadingModel> = + MutableStateFlow(SmartspaceMediaLoadingModel.Unknown) + val recommendationsLoadingState: StateFlow<SmartspaceMediaLoadingModel> = + _recommendationsLoadingState.asStateFlow() + fun addMediaEntry(key: String, data: MediaData) { val entries = LinkedHashMap<String, MediaData>(_allUserEntries.value) entries[key] = data @@ -110,4 +122,25 @@ class MediaFilterRepository @Inject constructor() { fun setReactivatedId(instanceId: InstanceId?) { _reactivatedId.value = instanceId } + + fun addMediaDataLoadingState(mediaDataLoadingModel: MediaDataLoadingModel) { + // Filter out previous loading state that has same [InstanceId]. + val loadedStates = + _mediaDataLoadedStates.value.filter { loadedModel -> + loadedModel !is MediaDataLoadingModel.Loaded || + !loadedModel.equalInstanceIds(mediaDataLoadingModel) + } + + _mediaDataLoadedStates.value = + loadedStates + + if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) { + listOf(mediaDataLoadingModel) + } else { + emptyList() + } + } + + fun setRecommedationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) { + _recommendationsLoadingState.value = smartspaceMediaLoadingModel + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt index d40069c4b3da..a30e5826529a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt @@ -28,7 +28,9 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.data.repository.MediaFilterRepository import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.shared.model.MediaData +import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel import com.android.systemui.media.controls.shared.model.SmartspaceMediaData +import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.settings.UserTracker @@ -67,9 +69,6 @@ constructor( private val mediaFlags: MediaFlags, private val mediaFilterRepository: MediaFilterRepository, ) : MediaDataManager.Listener { - private val _listeners: MutableSet<Listener> = mutableSetOf() - val listeners: Set<Listener> - get() = _listeners.toSet() lateinit var mediaDataManager: MediaDataManager // Ensure the field (and associated reference) isn't removed during optimization. @@ -111,8 +110,9 @@ constructor( mediaFilterRepository.addSelectedUserMediaEntry(data) - // Notify listeners - listeners.forEach { it.onMediaDataLoaded(data.instanceId) } + mediaFilterRepository.addMediaDataLoadingState( + MediaDataLoadingModel.Loaded(data.instanceId) + ) } override fun onSmartspaceMediaDataLoaded( @@ -159,7 +159,7 @@ constructor( // reactivate. if (shouldReactivate) { val lastActiveId = sorted.lastKey() // most recently active id - // Notify listeners to consider this media active + // Update loading state to consider this media active Log.d(TAG, "reactivating $lastActiveId instead of smartspace") mediaFilterRepository.setReactivatedId(lastActiveId) val mediaData = sorted[lastActiveId]!!.copy(active = true) @@ -168,15 +168,9 @@ constructor( mediaData.packageName, mediaData.instanceId ) - listeners.forEach { - it.onMediaDataLoaded( - lastActiveId, - receivedSmartspaceCardLatency = - (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) - .toInt(), - isSsReactivated = true - ) - } + mediaFilterRepository.addMediaDataLoadingState( + MediaDataLoadingModel.Loaded(lastActiveId) + ) } } else if (data.isActive) { // Mark to prioritize Smartspace card if no recent media. @@ -192,15 +186,18 @@ constructor( smartspaceMediaData.packageName, smartspaceMediaData.instanceId ) - listeners.forEach { it.onSmartspaceMediaDataLoaded(key, shouldPrioritizeMutable) } + mediaFilterRepository.setRecommedationsLoadingState( + SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable) + ) } override fun onMediaDataRemoved(key: String) { mediaFilterRepository.removeMediaEntry(key)?.let { mediaData -> val instanceId = mediaData.instanceId mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let { - // Only notify listeners if something actually changed - listeners.forEach { it.onMediaDataRemoved(instanceId) } + mediaFilterRepository.addMediaDataLoadingState( + MediaDataLoadingModel.Removed(instanceId) + ) } } } @@ -210,11 +207,11 @@ constructor( mediaFilterRepository.reactivatedId.value?.let { lastActiveId -> mediaFilterRepository.setReactivatedId(null) Log.d(TAG, "expiring reactivated key $lastActiveId") - // Notify listeners to update with actual active value + // Update loading state with actual active value mediaFilterRepository.selectedUserEntries.value[lastActiveId]?.let { - listeners.forEach { listener -> - listener.onMediaDataLoaded(lastActiveId, immediately) - } + mediaFilterRepository.addMediaDataLoadingState( + MediaDataLoadingModel.Loaded(lastActiveId, immediately) + ) } } @@ -227,7 +224,9 @@ constructor( ) ) } - listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) } + mediaFilterRepository.setRecommedationsLoadingState( + SmartspaceMediaLoadingModel.Removed(key, immediately) + ) } @VisibleForTesting @@ -238,29 +237,37 @@ constructor( // Only remove media when the profile is unavailable. if (DEBUG) Log.d(TAG, "Removing $key after profile change") mediaFilterRepository.removeSelectedUserMediaEntry(data.instanceId, data) - listeners.forEach { listener -> listener.onMediaDataRemoved(data.instanceId) } + mediaFilterRepository.addMediaDataLoadingState( + MediaDataLoadingModel.Removed(data.instanceId) + ) } } } @VisibleForTesting internal fun handleUserSwitched() { - // If the user changes, remove all current MediaData objects and inform listeners - val listenersCopy = listeners + // If the user changes, remove all current MediaData objects. val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList() - // Clear the list first, to make sure callbacks from listeners if we have any entries - // are up to date + // Clear the list first and update loading state to remove media from UI. mediaFilterRepository.clearSelectedUserMedia() keyCopy.forEach { instanceId -> if (DEBUG) Log.d(TAG, "Removing $instanceId after user change") - listenersCopy.forEach { listener -> listener.onMediaDataRemoved(instanceId) } + mediaFilterRepository.addMediaDataLoadingState( + MediaDataLoadingModel.Removed(instanceId) + ) } mediaFilterRepository.allUserEntries.value.forEach { (key, data) -> if (lockscreenUserManager.isCurrentProfile(data.userId)) { - if (DEBUG) Log.d(TAG, "Re-adding $key after user change") + if (DEBUG) + Log.d( + TAG, + "Re-adding $key with instanceId=${data.instanceId} after user change" + ) mediaFilterRepository.addSelectedUserMediaEntry(data) - listenersCopy.forEach { listener -> listener.onMediaDataLoaded(data.instanceId) } + mediaFilterRepository.addMediaDataLoadingState( + MediaDataLoadingModel.Loaded(data.instanceId) + ) } } } @@ -310,12 +317,6 @@ constructor( } } - /** Add a listener for filtered [MediaData] changes */ - fun addListener(listener: Listener) = _listeners.add(listener) - - /** Remove a listener that was registered with addListener */ - fun removeListener(listener: Listener) = _listeners.remove(listener) - /** * Return the time since last active for the most-recent media. * @@ -335,48 +336,6 @@ constructor( return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE } - interface Listener { - /** - * Called whenever there's new MediaData Loaded for the consumption in views. - * - * @param immediately indicates should apply the UI changes immediately, otherwise wait - * until the next refresh-round before UI becomes visible. True by default to take in - * place immediately. - * @param receivedSmartspaceCardLatency is the latency between headphone connects and sysUI - * displays Smartspace media targets. Will be 0 if the data is not activated by Smartspace - * signal. - * @param isSsReactivated indicates resume media card is reactivated by Smartspace - * recommendation signal - */ - fun onMediaDataLoaded( - instanceId: InstanceId, - immediately: Boolean = true, - receivedSmartspaceCardLatency: Int = 0, - isSsReactivated: Boolean = false, - ) - - /** - * Called whenever there's new Smartspace media data loaded. - * - * @param shouldPrioritize indicates the sorting priority of the Smartspace card. If true, - * it will be prioritized as the first card. Otherwise, it will show up as the last card - * as default. - */ - fun onSmartspaceMediaDataLoaded(key: String, shouldPrioritize: Boolean = false) - - /** Called whenever a previously existing Media notification was removed. */ - fun onMediaDataRemoved(instanceId: InstanceId) - - /** - * Called whenever a previously existing Smartspace media data was removed. - * - * @param immediately indicates should apply the UI changes immediately, otherwise wait - * until the next refresh-round before UI becomes visible. True by default to take in - * place immediately. - */ - fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean = true) - } - companion object { /** * Maximum age of a media control to re-activate on smartspace signal. If there is no media diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt index 7dbca0ae4cda..cdcf3636e148 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt @@ -34,11 +34,14 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDeviceManager import com.android.systemui.media.controls.domain.pipeline.MediaSessionBasedFilter import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener import com.android.systemui.media.controls.domain.resume.MediaResumeListener +import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel +import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel import com.android.systemui.media.controls.util.MediaFlags import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -109,6 +112,14 @@ constructor( .distinctUntilChanged() .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false) + /** The most recent list of loaded media controls. */ + val mediaDataLoadedStates: Flow<List<MediaDataLoadingModel>> = + mediaFilterRepository.mediaDataLoadedStates + + /** The most recent change to loaded media recommendations. */ + val recommendationsLoadingState: Flow<SmartspaceMediaLoadingModel> = + mediaFilterRepository.recommendationsLoadingState + override fun start() { if (!mediaFlags.isMediaControlsRefactorEnabled()) { return diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt new file mode 100644 index 000000000000..bd42a4df7262 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt @@ -0,0 +1,46 @@ +/* + * 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 { + /** The initial loading state when no media data has yet loaded. */ + data object Unknown : MediaDataLoadingModel() + + /** Media data has been loaded. */ + data class Loaded( + val instanceId: InstanceId, + val immediatelyUpdateUi: Boolean = true, + ) : MediaDataLoadingModel() { + + /** Returns true if [other] has the same instance id, false otherwise. */ + fun equalInstanceIds(other: MediaDataLoadingModel): Boolean { + return when (other) { + is Loaded -> other.instanceId == instanceId + is Removed -> other.instanceId == instanceId + Unknown -> false + } + } + } + + /** Media data has been removed. */ + data class Removed( + val instanceId: InstanceId, + ) : MediaDataLoadingModel() +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt new file mode 100644 index 000000000000..6c1e536f8c02 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt @@ -0,0 +1,35 @@ +/* + * 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 smartspace media loading state. */ +sealed class SmartspaceMediaLoadingModel { + /** The initial loading state when no smartspace media has yet loaded. */ + data object Unknown : SmartspaceMediaLoadingModel() + + /** Smartspace media has been loaded. */ + data class Loaded( + val key: String, + val isPrioritized: Boolean = false, + ) : SmartspaceMediaLoadingModel() + + /** Smartspace media has been removed. */ + data class Removed( + val key: String, + val immediatelyUpdateUi: Boolean = true, + ) : SmartspaceMediaLoadingModel() +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt index b70cc30eb3e1..fe8fdc042ae4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt @@ -29,7 +29,9 @@ import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.data.repository.MediaFilterRepository import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.shared.model.MediaData +import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel import com.android.systemui.media.controls.shared.model.SmartspaceMediaData +import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel import com.android.systemui.media.controls.ui.controller.MediaPlayerData import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger @@ -48,12 +50,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.never -import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -76,7 +76,6 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! @TestableLooper.RunWithLooper class MediaDataFilterImplTest : SysuiTestCase() { - @Mock private lateinit var listener: MediaDataFilterImpl.Listener @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var broadcastSender: BroadcastSender @Mock private lateinit var mediaDataManager: MediaDataManager @@ -89,7 +88,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Mock private lateinit var cardAction: SmartspaceAction private lateinit var mediaDataFilter: MediaDataFilterImpl - private lateinit var mediaFilterRepository: MediaFilterRepository + private lateinit var repository: MediaFilterRepository private lateinit var testScope: TestScope private lateinit var dataMain: MediaData private lateinit var dataGuest: MediaData @@ -102,7 +101,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { MediaPlayerData.clear() whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false) testScope = TestScope() - mediaFilterRepository = MediaFilterRepository() + repository = MediaFilterRepository() mediaDataFilter = MediaDataFilterImpl( context, @@ -113,10 +112,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { clock, logger, mediaFlags, - mediaFilterRepository, + repository, ) mediaDataFilter.mediaDataManager = mediaDataManager - mediaDataFilter.addListener(listener) // Start all tests as main user setUser(USER_MAIN) @@ -162,91 +160,114 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnDataLoadedForCurrentUser_callsListener() { - // GIVEN a media for main user - mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) + fun onDataLoadedForCurrentUser_updatesLoadedStates() = + testScope.runTest { + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) + val mediaDataLoadingModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) - // THEN we should tell the listener - verify(listener).onMediaDataLoaded(eq(dataMain.instanceId), eq(true), eq(0), eq(false)) - } + mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) + } @Test - fun testOnDataLoadedForGuest_doesNotCallListener() { - // GIVEN a media for guest user - mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) + fun onDataLoadedForGuest_doesNotUpdateLoadedStates() = + testScope.runTest { + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) + val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) - // THEN we should NOT tell the listener - verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean()) - } + mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) + + assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel) + } @Test - fun testOnRemovedForCurrent_callsListener() { - // GIVEN a media was removed for main user - mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) - mediaDataFilter.onMediaDataRemoved(KEY) + fun onRemovedForCurrent_updatesLoadedStates() = + testScope.runTest { + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) + val mediaLoadedStatesModel = + mutableListOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) - // THEN we should tell the listener - verify(listener).onMediaDataRemoved(eq(dataMain.instanceId)) - } + // GIVEN a media was removed for main user + mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + + mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId)) + mediaDataFilter.onMediaDataRemoved(KEY) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + } @Test - fun testOnRemovedForGuest_doesNotCallListener() { - // GIVEN a media was removed for guest user - mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) - mediaDataFilter.onMediaDataRemoved(KEY) + fun onRemovedForGuest_doesNotUpdateLoadedStates() = + testScope.runTest { + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) - // THEN we should NOT tell the listener - verify(listener, never()).onMediaDataRemoved(eq(dataGuest.instanceId)) - } + // GIVEN a media was removed for guest user + mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) + mediaDataFilter.onMediaDataRemoved(KEY) + + assertThat(mediaDataLoadedStates).isEmpty() + } @Test - fun testOnUserSwitched_removesOldUserControls() { - // GIVEN that we have a media loaded for main user - mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) + fun onUserSwitched_removesOldUserControls() = + testScope.runTest { + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) + val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) - // and we switch to guest user - setUser(USER_GUEST) + // GIVEN that we have a media loaded for main user + mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) - // THEN we should remove the main user's media - verify(listener).onMediaDataRemoved(eq(dataMain.instanceId)) - } + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + + // and we switch to guest user + setUser(USER_GUEST) + + // THEN we should remove the main user's media + assertThat(mediaDataLoadedStates).isEmpty() + } @Test - fun testOnUserSwitched_addsNewUserControls() { - // GIVEN that we had some media for both users - mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) - mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest) - reset(listener) + fun onUserSwitched_addsNewUserControls() = + testScope.runTest { + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) + val guestLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataGuest.instanceId)) + val mainLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) - // and we switch to guest user - setUser(USER_GUEST) + // GIVEN that we had some media for both users + mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) + mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest) - // THEN we should add back the guest user media - verify(listener).onMediaDataLoaded(eq(dataGuest.instanceId), eq(true), eq(0), eq(false)) + // and we switch to guest user + setUser(USER_GUEST) - // but not the main user's - verify(listener, never()) - .onMediaDataLoaded(eq(dataMain.instanceId), anyBoolean(), anyInt(), anyBoolean()) - } + assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel) + assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel) + } @Test - fun testOnProfileChanged_profileUnavailable_loadControls() { - // GIVEN that we had some media for both profiles - mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) - mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataPrivateProfile) - reset(listener) + fun onProfileChanged_profileUnavailable_updateStates() = + testScope.runTest { + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) - // and we change profile status - setPrivateProfileUnavailable() + // GIVEN that we had some media for both profiles + mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) + mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataPrivateProfile) - // THEN we should add the private profile media - verify(listener).onMediaDataRemoved(eq(dataPrivateProfile.instanceId)) - } + // and we change profile status + setPrivateProfileUnavailable() + + val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) + // THEN we should remove the private profile media + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + } @Test fun hasAnyMedia_mediaSet_returnsTrue() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) assertThat(hasAnyMedia(selectedUserEntries)).isTrue() @@ -255,7 +276,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasAnyMedia_recommendationSet_returnsFalse() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) assertThat(hasAnyMedia(selectedUserEntries)).isFalse() @@ -264,8 +285,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasAnyMediaOrRecommendation_mediaSet_returnsTrue() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData)) @@ -275,8 +296,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasAnyMediaOrRecommendation_recommendationSet_returnsTrue() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData)) @@ -286,7 +307,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasActiveMedia_inactiveMediaSet_returnsFalse() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val data = dataMain.copy(active = false) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) @@ -297,7 +318,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasActiveMedia_activeMediaSet_returnsTrue() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val data = dataMain.copy(active = true) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) @@ -307,9 +328,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasActiveMediaOrRecommendation_inactiveMediaSet_returnsFalse() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) val data = dataMain.copy(active = false) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) @@ -326,9 +347,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasActiveMediaOrRecommendation_activeMediaSet_returnsTrue() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) val data = dataMain.copy(active = true) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data) @@ -345,9 +366,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalse() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) whenever(smartspaceData.isActive).thenReturn(false) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) @@ -364,9 +385,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalse() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) whenever(smartspaceData.isValid()).thenReturn(false) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) @@ -383,9 +404,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTrue() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) whenever(smartspaceData.isActive).thenReturn(true) whenever(smartspaceData.isValid()).thenReturn(true) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) @@ -401,10 +422,10 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testHasAnyMediaOrRecommendation_onlyCurrentUser() = + fun hasAnyMediaOrRecommendation_onlyCurrentUser() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData)) .isFalse() @@ -415,11 +436,11 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testHasActiveMediaOrRecommendation_onlyCurrentUser() = + fun hasActiveMediaOrRecommendation_onlyCurrentUser() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -443,10 +464,10 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnNotificationRemoved_doesNotHaveMedia() = + fun onNotificationRemoved_doesNotHaveMedia() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) mediaDataFilter.onMediaDataRemoved(KEY) @@ -456,7 +477,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSwipeToDismiss_setsTimedOut() { + fun onSwipeToDismiss_setsTimedOut() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) mediaDataFilter.onSwipeToDismiss() @@ -464,15 +485,19 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspace() = + fun onSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspace() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val recommendationsLoadingModel = + SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(true)) + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -487,18 +512,19 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() = + fun onSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) whenever(smartspaceData.isActive).thenReturn(false) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean()) - verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean()) + assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -513,17 +539,21 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSmartspaceMediaDataLoaded_noRecentMedia_activeValidRec_prioritizesSmartspace() = + fun onSmartspaceMediaDataLoaded_noRecentMedia_activeValidRec_prioritizesSmartspace() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val recommendationsLoadingModel = + SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true) val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld) clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(true)) + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -538,11 +568,13 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() = + fun onSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) whenever(smartspaceData.isActive).thenReturn(false) val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) @@ -550,7 +582,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean()) + assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -565,27 +597,29 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() = + fun onSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) whenever(smartspaceData.isActive).thenReturn(false) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) + val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener) - .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false)) - reset(listener) + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - // THEN we should tell listeners to treat the media as not active instead - verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean()) - verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean()) + // THEN we should treat the media as not active instead + assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -600,27 +634,28 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() = + fun onSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) whenever(smartspaceData.isValid()).thenReturn(false) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) + val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener) - .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false)) + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) // AND we get a smartspace signal runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - // THEN we should tell listeners to treat the media as active instead - val dataCurrentAndActive = dataCurrent.copy(active = true) - verify(listener) - .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true)) + // THEN we should treat the media as active instead + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -630,31 +665,35 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() // Smartspace update shouldn't be propagated for the empty rec list. - verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean()) + assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } @Test - fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeValidRec_usesBoth() = + fun onSmartspaceMediaDataLoaded_hasRecentMedia_activeValidRec_usesBoth() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) + val mediaDataLoadingModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) + val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) + mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener) - .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false)) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) // AND we get a smartspace signal runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - // THEN we should tell listeners to treat the media as active instead - val dataCurrentAndActive = dataCurrent.copy(active = true) - verify(listener) - .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true)) + // THEN we should treat the media as active instead + assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -664,22 +703,25 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() // Smartspace update should also be propagated but not prioritized. - verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false)) + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } @Test - fun testOnSmartspaceMediaDataRemoved_usedSmartspace_clearsSmartspace() = + fun onSmartspaceMediaDataRemoved_usedSmartspace_clearsSmartspace() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Removed(SMARTSPACE_KEY) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) - verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -692,26 +734,28 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() = + fun onSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Removed(SMARTSPACE_KEY) + val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener) - .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false)) + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - val dataCurrentAndActive = dataCurrent.copy(active = true) - verify(listener) - .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true)) + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) - verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -724,17 +768,20 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSmartspaceLoaded_persistentEnabled_isInactive_notifiesListeners() = + fun onSmartspaceLoaded_persistentEnabled_isInactive() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true) whenever(smartspaceData.isActive).thenReturn(false) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false)) + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -748,11 +795,16 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() = + fun onSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) + val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true) whenever(smartspaceData.isActive).thenReturn(false) @@ -760,16 +812,14 @@ class MediaDataFilterImplTest : SysuiTestCase() { // If there is media that was recently played but inactive val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener) - .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false)) - reset(listener) + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + // And an inactive recommendation is loaded mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // Smartspace is loaded but the media stays inactive - verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false)) - verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean()) + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -783,7 +833,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testOnSwipeToDismiss_persistentEnabled_recommendationSetInactive() { + fun onSwipeToDismiss_persistentEnabled_recommendationSetInactive() { whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true) val data = @@ -802,16 +852,21 @@ class MediaDataFilterImplTest : SysuiTestCase() { } @Test - fun testSmartspaceLoaded_shouldTriggerResume_doesTrigger() = + fun smartspaceLoaded_shouldTriggerResume_doesTrigger() = testScope.runTest { - val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries) - val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData) - val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId) + val selectedUserEntries by collectLastValue(repository.selectedUserEntries) + val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) + val reactivatedKey by collectLastValue(repository.reactivatedId) + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) + val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener) - .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false)) + + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) // AND we get a smartspace signal with extra to trigger resume runCurrent() @@ -819,10 +874,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { whenever(cardAction.extras).thenReturn(extras) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - // THEN we should tell listeners to treat the media as active instead - val dataCurrentAndActive = dataCurrent.copy(active = true) - verify(listener) - .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true)) + // THEN we should treat the media as active instead + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -831,27 +884,33 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) ) .isTrue() - // And send the smartspace data, but not prioritized - verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false)) + // And update the smartspace data state, but not prioritized + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) } @Test - fun testSmartspaceLoaded_notShouldTriggerResume_doesNotTrigger() { - // WHEN we have media that was recently played, but not currently active - val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) - mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener).onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false)) + fun smartspaceLoaded_notShouldTriggerResume_doesNotTrigger() = + testScope.runTest { + val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates) + val recommendationsLoadingState by + collectLastValue(repository.recommendationsLoadingState) + val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) + val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) - // AND we get a smartspace signal with extra to not trigger resume - val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) } - whenever(cardAction.extras).thenReturn(extras) - mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + // WHEN we have media that was recently played, but not currently active + val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) + mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - // THEN listeners are not updated to show media - verify(listener, never()).onMediaDataLoaded(any(), eq(true), eq(100), eq(true)) - // But the smartspace update is still propagated - verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false)) - } + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + + // AND we get a smartspace signal with extra to not trigger resume + val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) } + whenever(cardAction.extras).thenReturn(extras) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + // But the smartspace update is still propagated + assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) + } private fun hasActiveMediaOrRecommendation( entries: Map<InstanceId, MediaData>?, |