Loading packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +52 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,10 @@ constructor( private val mediaFlags: MediaFlags, private val mediaFilterRepository: MediaFilterRepository, ) : MediaDataManager.Listener { /** Non-UI listeners to media changes. */ private val _listeners: MutableSet<MediaDataProcessor.Listener> = mutableSetOf() val listeners: Set<MediaDataProcessor.Listener> get() = _listeners.toSet() lateinit var mediaDataProcessor: MediaDataProcessor // Ensure the field (and associated reference) isn't removed during optimization. Loading Loading @@ -113,6 +117,9 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(data.instanceId) ) // Notify listeners listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) } } override fun onSmartspaceMediaDataLoaded( Loading Loading @@ -171,6 +178,20 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(lastActiveId) ) listeners.forEach { listener -> getKey(lastActiveId)?.let { lastActiveKey -> listener.onMediaDataLoaded( lastActiveKey, lastActiveKey, mediaData, receivedSmartspaceCardLatency = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) .toInt(), isSsReactivated = true ) } } } } else if (data.isActive) { // Mark to prioritize Smartspace card if no recent media. Loading @@ -189,6 +210,7 @@ constructor( mediaFilterRepository.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable) ) listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } override fun onMediaDataRemoved(key: String) { Loading @@ -198,6 +220,8 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(instanceId) ) // Only notify listeners if something actually changed listeners.forEach { it.onMediaDataRemoved(key) } } } } Loading @@ -212,6 +236,11 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(lastActiveId, immediately) ) listeners.forEach { listener -> getKey(lastActiveId)?.let { lastActiveKey -> listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, it, immediately) } } } } Loading @@ -227,6 +256,7 @@ constructor( mediaFilterRepository.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Removed(key, immediately) ) listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) } } @VisibleForTesting Loading @@ -240,6 +270,7 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(data.instanceId) ) listeners.forEach { listener -> listener.onMediaDataRemoved(key) } } } } Loading @@ -247,6 +278,7 @@ constructor( @VisibleForTesting internal fun handleUserSwitched() { // If the user changes, remove all current MediaData objects. val listenersCopy = listeners val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList() // Clear the list first and update loading state to remove media from UI. mediaFilterRepository.clearSelectedUserMedia() Loading @@ -255,6 +287,9 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(instanceId) ) getKey(instanceId)?.let { listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } } } mediaFilterRepository.allUserEntries.value.forEach { (key, data) -> Loading @@ -268,6 +303,7 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(data.instanceId) ) listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) } } } } Loading Loading @@ -317,6 +353,12 @@ constructor( } } /** Add a listener for filtered [MediaData] changes */ fun addListener(listener: MediaDataProcessor.Listener) = _listeners.add(listener) /** Remove a listener that was registered with addListener */ fun removeListener(listener: MediaDataProcessor.Listener) = _listeners.remove(listener) /** * Return the time since last active for the most-recent media. * Loading @@ -336,6 +378,16 @@ constructor( return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE } private fun getKey(instanceId: InstanceId): String? { val allEntries = mediaFilterRepository.allUserEntries.value val filteredEntries = allEntries.filter { (_, data) -> data.instanceId == instanceId } return if (filteredEntries.isNotEmpty()) { filteredEntries.keys.first() } else { null } } companion object { /** * Maximum age of a media control to re-activate on smartspace signal. If there is no media Loading packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +8 −0 Original line number Diff line number Diff line Loading @@ -160,6 +160,14 @@ constructor( mediaDataFilter.mediaDataProcessor = mediaDataProcessor } override fun addListener(listener: MediaDataManager.Listener) { mediaDataFilter.addListener(listener) } override fun removeListener(listener: MediaDataManager.Listener) { mediaDataFilter.removeListener(listener) } override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) = unsupported override fun onNotificationAdded(key: String, sbn: StatusBarNotification) { Loading packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +109 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ 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 Loading @@ -76,6 +77,7 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! @TestableLooper.RunWithLooper class MediaDataFilterImplTest : SysuiTestCase() { @Mock private lateinit var listener: MediaDataProcessor.Listener @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var broadcastSender: BroadcastSender @Mock private lateinit var mediaDataProcessor: MediaDataProcessor Loading Loading @@ -115,6 +117,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { repository, ) mediaDataFilter.mediaDataProcessor = mediaDataProcessor mediaDataFilter.addListener(listener) // Start all tests as main user setUser(USER_MAIN) Loading Loading @@ -167,6 +170,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) } Loading @@ -178,6 +183,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) verify(listener, never()) .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel) } Loading @@ -196,6 +203,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId)) mediaDataFilter.onMediaDataRemoved(KEY) verify(listener).onMediaDataRemoved(eq(KEY)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) } Loading @@ -208,6 +216,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) mediaDataFilter.onMediaDataRemoved(KEY) verify(listener, never()).onMediaDataRemoved(eq(KEY)) assertThat(mediaDataLoadedStates).isEmpty() } Loading @@ -226,6 +235,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { setUser(USER_GUEST) // THEN we should remove the main user's media verify(listener).onMediaDataRemoved(eq(KEY)) assertThat(mediaDataLoadedStates).isEmpty() } Loading @@ -243,6 +253,20 @@ class MediaDataFilterImplTest : SysuiTestCase() { // and we switch to guest user setUser(USER_GUEST) // THEN we should add back the guest user media verify(listener) .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false)) // but not the main user's verify(listener, never()) .onMediaDataLoaded( eq(KEY), any(), eq(dataMain), anyBoolean(), anyInt(), anyBoolean() ) assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel) assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel) } Loading @@ -261,6 +285,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) // THEN we should remove the private profile media verify(listener).onMediaDataRemoved(eq(KEY_ALT)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) } Loading Loading @@ -507,6 +532,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading Loading @@ -534,6 +561,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isFalse() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener, never()) .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading Loading @@ -563,6 +593,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading Loading @@ -592,6 +624,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isFalse() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading @@ -614,6 +647,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) Loading @@ -629,6 +664,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isFalse() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener, never()) .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean()) verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading @@ -649,12 +687,15 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) assertThat( hasActiveMediaOrRecommendation( Loading @@ -664,8 +705,18 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) ) .isTrue() verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), eq(100), eq(true) ) // Smartspace update shouldn't be propagated for the empty rec list. assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown) verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } Loading @@ -687,12 +738,24 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), eq(100), eq(true) ) assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) assertThat( hasActiveMediaOrRecommendation( Loading @@ -704,6 +767,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { .isTrue() // Smartspace update should also be propagated but not prioritized. assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } Loading @@ -721,6 +786,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( Loading @@ -746,15 +812,29 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), 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( Loading @@ -781,6 +861,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( Loading Loading @@ -813,12 +895,18 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) 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(smartspaceData), eq(false)) verify(listener, never()) .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( Loading Loading @@ -866,6 +954,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) // AND we get a smartspace signal with extra to trigger resume Loading @@ -875,6 +965,16 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), eq(100), eq(true) ) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) assertThat( hasActiveMediaOrRecommendation( Loading @@ -886,6 +986,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { .isTrue() // And update the smartspace data state, but not prioritized assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) } @Test Loading @@ -901,6 +1003,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) // AND we get a smartspace signal with extra to not trigger resume Loading @@ -908,7 +1012,12 @@ class MediaDataFilterImplTest : SysuiTestCase() { whenever(cardAction.extras).thenReturn(extras) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN listeners are not updated to show media verify(listener, never()) .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true)) // But the smartspace update is still propagated verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) } Loading Loading
packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +52 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,10 @@ constructor( private val mediaFlags: MediaFlags, private val mediaFilterRepository: MediaFilterRepository, ) : MediaDataManager.Listener { /** Non-UI listeners to media changes. */ private val _listeners: MutableSet<MediaDataProcessor.Listener> = mutableSetOf() val listeners: Set<MediaDataProcessor.Listener> get() = _listeners.toSet() lateinit var mediaDataProcessor: MediaDataProcessor // Ensure the field (and associated reference) isn't removed during optimization. Loading Loading @@ -113,6 +117,9 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(data.instanceId) ) // Notify listeners listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) } } override fun onSmartspaceMediaDataLoaded( Loading Loading @@ -171,6 +178,20 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(lastActiveId) ) listeners.forEach { listener -> getKey(lastActiveId)?.let { lastActiveKey -> listener.onMediaDataLoaded( lastActiveKey, lastActiveKey, mediaData, receivedSmartspaceCardLatency = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) .toInt(), isSsReactivated = true ) } } } } else if (data.isActive) { // Mark to prioritize Smartspace card if no recent media. Loading @@ -189,6 +210,7 @@ constructor( mediaFilterRepository.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable) ) listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } override fun onMediaDataRemoved(key: String) { Loading @@ -198,6 +220,8 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(instanceId) ) // Only notify listeners if something actually changed listeners.forEach { it.onMediaDataRemoved(key) } } } } Loading @@ -212,6 +236,11 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(lastActiveId, immediately) ) listeners.forEach { listener -> getKey(lastActiveId)?.let { lastActiveKey -> listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, it, immediately) } } } } Loading @@ -227,6 +256,7 @@ constructor( mediaFilterRepository.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Removed(key, immediately) ) listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) } } @VisibleForTesting Loading @@ -240,6 +270,7 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(data.instanceId) ) listeners.forEach { listener -> listener.onMediaDataRemoved(key) } } } } Loading @@ -247,6 +278,7 @@ constructor( @VisibleForTesting internal fun handleUserSwitched() { // If the user changes, remove all current MediaData objects. val listenersCopy = listeners val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList() // Clear the list first and update loading state to remove media from UI. mediaFilterRepository.clearSelectedUserMedia() Loading @@ -255,6 +287,9 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(instanceId) ) getKey(instanceId)?.let { listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } } } mediaFilterRepository.allUserEntries.value.forEach { (key, data) -> Loading @@ -268,6 +303,7 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(data.instanceId) ) listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) } } } } Loading Loading @@ -317,6 +353,12 @@ constructor( } } /** Add a listener for filtered [MediaData] changes */ fun addListener(listener: MediaDataProcessor.Listener) = _listeners.add(listener) /** Remove a listener that was registered with addListener */ fun removeListener(listener: MediaDataProcessor.Listener) = _listeners.remove(listener) /** * Return the time since last active for the most-recent media. * Loading @@ -336,6 +378,16 @@ constructor( return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE } private fun getKey(instanceId: InstanceId): String? { val allEntries = mediaFilterRepository.allUserEntries.value val filteredEntries = allEntries.filter { (_, data) -> data.instanceId == instanceId } return if (filteredEntries.isNotEmpty()) { filteredEntries.keys.first() } else { null } } companion object { /** * Maximum age of a media control to re-activate on smartspace signal. If there is no media Loading
packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +8 −0 Original line number Diff line number Diff line Loading @@ -160,6 +160,14 @@ constructor( mediaDataFilter.mediaDataProcessor = mediaDataProcessor } override fun addListener(listener: MediaDataManager.Listener) { mediaDataFilter.addListener(listener) } override fun removeListener(listener: MediaDataManager.Listener) { mediaDataFilter.removeListener(listener) } override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) = unsupported override fun onNotificationAdded(key: String, sbn: StatusBarNotification) { Loading
packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +109 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ 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 Loading @@ -76,6 +77,7 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! @TestableLooper.RunWithLooper class MediaDataFilterImplTest : SysuiTestCase() { @Mock private lateinit var listener: MediaDataProcessor.Listener @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var broadcastSender: BroadcastSender @Mock private lateinit var mediaDataProcessor: MediaDataProcessor Loading Loading @@ -115,6 +117,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { repository, ) mediaDataFilter.mediaDataProcessor = mediaDataProcessor mediaDataFilter.addListener(listener) // Start all tests as main user setUser(USER_MAIN) Loading Loading @@ -167,6 +170,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) } Loading @@ -178,6 +183,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) verify(listener, never()) .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel) } Loading @@ -196,6 +203,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId)) mediaDataFilter.onMediaDataRemoved(KEY) verify(listener).onMediaDataRemoved(eq(KEY)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) } Loading @@ -208,6 +216,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) mediaDataFilter.onMediaDataRemoved(KEY) verify(listener, never()).onMediaDataRemoved(eq(KEY)) assertThat(mediaDataLoadedStates).isEmpty() } Loading @@ -226,6 +235,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { setUser(USER_GUEST) // THEN we should remove the main user's media verify(listener).onMediaDataRemoved(eq(KEY)) assertThat(mediaDataLoadedStates).isEmpty() } Loading @@ -243,6 +253,20 @@ class MediaDataFilterImplTest : SysuiTestCase() { // and we switch to guest user setUser(USER_GUEST) // THEN we should add back the guest user media verify(listener) .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false)) // but not the main user's verify(listener, never()) .onMediaDataLoaded( eq(KEY), any(), eq(dataMain), anyBoolean(), anyInt(), anyBoolean() ) assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel) assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel) } Loading @@ -261,6 +285,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) // THEN we should remove the private profile media verify(listener).onMediaDataRemoved(eq(KEY_ALT)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) } Loading Loading @@ -507,6 +532,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading Loading @@ -534,6 +561,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isFalse() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener, never()) .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading Loading @@ -563,6 +593,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading Loading @@ -592,6 +624,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isFalse() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading @@ -614,6 +647,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) Loading @@ -629,6 +664,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isFalse() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() verify(listener, never()) .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean()) verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } Loading @@ -649,12 +687,15 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) assertThat( hasActiveMediaOrRecommendation( Loading @@ -664,8 +705,18 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) ) .isTrue() verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), eq(100), eq(true) ) // Smartspace update shouldn't be propagated for the empty rec list. assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown) verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } Loading @@ -687,12 +738,24 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), eq(100), eq(true) ) assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) assertThat( hasActiveMediaOrRecommendation( Loading @@ -704,6 +767,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { .isTrue() // Smartspace update should also be propagated but not prioritized. assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } Loading @@ -721,6 +786,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( Loading @@ -746,15 +812,29 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), 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( Loading @@ -781,6 +861,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( Loading Loading @@ -813,12 +895,18 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) 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(smartspaceData), eq(false)) verify(listener, never()) .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( Loading Loading @@ -866,6 +954,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) // AND we get a smartspace signal with extra to trigger resume Loading @@ -875,6 +965,16 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), eq(100), eq(true) ) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) assertThat( hasActiveMediaOrRecommendation( Loading @@ -886,6 +986,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { .isTrue() // And update the smartspace data state, but not prioritized assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) } @Test Loading @@ -901,6 +1003,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) // AND we get a smartspace signal with extra to not trigger resume Loading @@ -908,7 +1012,12 @@ class MediaDataFilterImplTest : SysuiTestCase() { whenever(cardAction.extras).thenReturn(extras) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN listeners are not updated to show media verify(listener, never()) .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true)) // But the smartspace update is still propagated verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) } Loading