Loading packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt +74 −20 Original line number Diff line number Diff line Loading @@ -150,7 +150,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun addMediaControlPlayingThenRemote() = testScope.runTest { val sortedMedia by collectLastValue(underTest.sortedMedia) val currentMedia by collectLastValue(underTest.currentMedia) val playingInstanceId = InstanceId.fakeInstanceId(123) val remoteInstanceId = InstanceId.fakeInstanceId(321) val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId) Loading @@ -161,8 +161,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(remoteData) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId)) assertThat(sortedMedia?.size).isEqualTo(2) assertThat(sortedMedia?.values) assertThat(currentMedia?.size).isEqualTo(2) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId)) Loading @@ -173,7 +173,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun switchMediaControlsPlaying() = testScope.runTest { val sortedMedia by collectLastValue(underTest.sortedMedia) val currentMedia by collectLastValue(underTest.currentMedia) val playingInstanceId1 = InstanceId.fakeInstanceId(123) val playingInstanceId2 = InstanceId.fakeInstanceId(321) var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1) Loading @@ -184,8 +184,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(playingData2) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2)) assertThat(sortedMedia?.size).isEqualTo(2) assertThat(sortedMedia?.values) assertThat(currentMedia?.size).isEqualTo(2) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2)) Loading @@ -198,12 +198,28 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(playingData1) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1)) underTest.addSelectedUserMediaEntry(playingData2) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2)) underTest.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(playingInstanceId2, false) ) assertThat(currentMedia?.size).isEqualTo(2) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)), MediaCommonModel.MediaControl( MediaDataLoadingModel.Loaded(playingInstanceId2, false) ) ) .inOrder() assertThat(sortedMedia?.size).isEqualTo(2) assertThat(sortedMedia?.values) underTest.setOrderedMedia() assertThat(currentMedia?.size).isEqualTo(2) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2)), MediaCommonModel.MediaControl( MediaDataLoadingModel.Loaded(playingInstanceId2, false) ), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)) ) .inOrder() Loading @@ -212,7 +228,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun fullOrderTest() = testScope.runTest { val sortedMedia by collectLastValue(underTest.sortedMedia) val currentMedia by collectLastValue(underTest.currentMedia) val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) val instanceId3 = InstanceId.fakeInstanceId(321) Loading Loading @@ -252,8 +268,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) ) assertThat(sortedMedia?.size).isEqualTo(6) assertThat(sortedMedia?.values) assertThat(currentMedia?.size).isEqualTo(6) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId1)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId2)), Loading @@ -270,7 +286,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun loadMediaFromRec() = testScope.runTest { val isMediaFromRec by collectLastValue(underTest.isMediaFromRec) val currentMedia by collectLastValue(underTest.currentMedia) val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) val data = Loading @@ -278,22 +294,59 @@ class MediaFilterRepositoryTest : SysuiTestCase() { active = true, instanceId = instanceId1, packageName = PACKAGE_NAME, isPlaying = true isPlaying = true, notificationKey = KEY, ) val newData = MediaData( active = true, instanceId = instanceId2, isPlaying = true, notificationKey = KEY_2 ) val icon = Icon.createWithResource(context, R.drawable.ic_media_play) val mediaRecommendations = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, packageName = PACKAGE_NAME, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) val newData = MediaData(active = true, instanceId = instanceId2) assertThat(isMediaFromRec).isFalse() underTest.setMediaFromRecPackageName(PACKAGE_NAME) underTest.addSelectedUserMediaEntry(data) underTest.setRecommendation(mediaRecommendations) underTest.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) ) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1)) assertThat(isMediaFromRec).isTrue() assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl( MediaDataLoadingModel.Loaded(instanceId1), isMediaFromRec = true ), MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) ) ) .inOrder() underTest.addSelectedUserMediaEntry(newData) underTest.addSelectedUserMediaEntry(data.copy(isPlaying = false)) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2)) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1)) assertThat(isMediaFromRec).isFalse() assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId2)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId1)), MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) ) ) .inOrder() } private fun createMediaData( Loading @@ -316,6 +369,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { private const val LOCAL = MediaData.PLAYBACK_LOCAL private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL private const val KEY = "KEY" private const val KEY_2 = "KEY_2" private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" private const val PACKAGE_NAME = "com.android.example" } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt +35 −34 Original line number Diff line number Diff line Loading @@ -55,6 +55,14 @@ class MediaCarouselInteractorTest : SysuiTestCase() { private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository private val mediaRecommendationsInteractor: MediaRecommendationsInteractor = kosmos.mediaRecommendationsInteractor val icon = Icon.createWithResource(context, R.drawable.ic_media_play) private val mediaRecommendation = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, packageName = PACKAGE_NAME, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor Loading Loading @@ -122,26 +130,19 @@ class MediaCarouselInteractorTest : SysuiTestCase() { collectLastValue(underTest.hasActiveMediaOrRecommendation) val hasAnyMediaOrRecommendation by collectLastValue(underTest.hasAnyMediaOrRecommendation) val sortedMedia by collectLastValue(underTest.sortedMedia) val currentMedia by collectLastValue(underTest.currentMedia) kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) val icon = Icon.createWithResource(context, R.drawable.ic_media_play) val userMediaRecommendation = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) val userMedia = MediaData(active = false) val recsLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) val mediaLoadingModel = MediaDataLoadingModel.Loaded(userMedia.instanceId) mediaFilterRepository.setRecommendation(userMediaRecommendation) mediaFilterRepository.setRecommendation(mediaRecommendation) mediaFilterRepository.setRecommendationsLoadingState(recsLoadingModel) assertThat(hasActiveMediaOrRecommendation).isTrue() assertThat(hasAnyMediaOrRecommendation).isTrue() assertThat(sortedMedia) assertThat(currentMedia) .containsExactly(MediaCommonModel.MediaRecommendations(recsLoadingModel)) mediaFilterRepository.addSelectedUserMediaEntry(userMedia) Loading @@ -149,7 +150,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() { assertThat(hasActiveMediaOrRecommendation).isTrue() assertThat(hasAnyMediaOrRecommendation).isTrue() assertThat(sortedMedia) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaRecommendations(recsLoadingModel), MediaCommonModel.MediaControl(mediaLoadingModel, true) Loading @@ -166,14 +167,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() { collectLastValue(underTest.hasAnyMediaOrRecommendation) kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) val icon = Icon.createWithResource(context, R.drawable.ic_media_play) val mediaRecommendation = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) mediaFilterRepository.setRecommendation(mediaRecommendation) assertThat(hasActiveMediaOrRecommendation).isTrue() Loading @@ -194,14 +187,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() { collectLastValue(underTest.hasAnyMediaOrRecommendation) kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) val icon = Icon.createWithResource(context, R.drawable.ic_media_play) val mediaRecommendation = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) mediaFilterRepository.setRecommendation(mediaRecommendation) assertThat(hasActiveMediaOrRecommendation).isTrue() Loading Loading @@ -234,26 +219,42 @@ class MediaCarouselInteractorTest : SysuiTestCase() { @Test fun loadMediaFromRec() = testScope.runTest { val isMediaFromRec by collectLastValue(underTest.isMediaFromRec) val currentMedia by collectLastValue(underTest.currentMedia) val instanceId = InstanceId.fakeInstanceId(123) val data = MediaData(active = true, instanceId = instanceId, packageName = PACKAGE_NAME) assertThat(isMediaFromRec).isFalse() val data = MediaData( active = true, instanceId = instanceId, packageName = PACKAGE_NAME, notificationKey = KEY ) val smartspaceLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) val mediaLoadingModel = MediaDataLoadingModel.Loaded(instanceId) mediaFilterRepository.setRecommendation(mediaRecommendation) mediaFilterRepository.setRecommendationsLoadingState(smartspaceLoadingModel) mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME) mediaFilterRepository.addSelectedUserMediaEntry(data) mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel) assertThat(isMediaFromRec).isFalse() assertThat(currentMedia) .containsExactly(MediaCommonModel.MediaRecommendations(smartspaceLoadingModel)) .inOrder() mediaFilterRepository.addSelectedUserMediaEntry(data.copy(isPlaying = true)) mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) assertThat(isMediaFromRec).isTrue() assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(mediaLoadingModel, isMediaFromRec = true), MediaCommonModel.MediaRecommendations(smartspaceLoadingModel) ) .inOrder() } companion object { private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" private const val PACKAGE_NAME = "com.android.example" private const val KEY = "key" } } packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt +23 −6 Original line number Diff line number Diff line Loading @@ -92,13 +92,29 @@ class MediaCarouselViewModelTest : SysuiTestCase() { val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) loadMediaControl(KEY, instanceId1) loadMediaControl(KEY_2, instanceId2) loadMediaControl(KEY, instanceId1, isPlaying = true) loadMediaControl(KEY_2, instanceId2, isPlaying = true) loadMediaControl(KEY, instanceId1, isPlaying = false) val firstMediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl val secondMediaControl = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl assertThat(firstMediaControl.instanceId).isEqualTo(instanceId2) assertThat(secondMediaControl.instanceId).isEqualTo(instanceId1) var mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl var mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) loadMediaControl(KEY, instanceId1, isPlaying = true) loadMediaControl(KEY_2, instanceId2, isPlaying = false) mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) underTest.onAttached() mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) } @Test Loading Loading @@ -147,6 +163,7 @@ class MediaCarouselViewModelTest : SysuiTestCase() { val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl assertThat(sortedMedia).hasSize(2) assertThat(mediaControl.instanceId).isEqualTo(instanceId) assertThat(mediaControl.isMediaFromRec).isTrue() } private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) { Loading packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt +65 −20 Original line number Diff line number Diff line Loading @@ -107,14 +107,11 @@ constructor( .thenByDescending { it.updateTime } .thenByDescending { it.notificationKey } private val _sortedMedia: MutableStateFlow<TreeMap<MediaSortKeyModel, MediaCommonModel>> = MutableStateFlow(TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)) val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> = _sortedMedia.asStateFlow() private val _isMediaFromRec: MutableStateFlow<Boolean> = MutableStateFlow(false) val isMediaFromRec: StateFlow<Boolean> = _isMediaFromRec.asStateFlow() private val _currentMedia: MutableStateFlow<List<MediaCommonModel>> = MutableStateFlow(mutableListOf()) val currentMedia = _currentMedia.asStateFlow() private var sortedMedia = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) private var mediaFromRecPackageName: String? = null private var locale: Locale = applicationContext.resources.configuration.locales.get(0) Loading Loading @@ -186,7 +183,7 @@ constructor( fun addMediaDataLoadingState(mediaDataLoadingModel: MediaDataLoadingModel) { val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) sortedMap.putAll( _sortedMedia.value.filter { (_, commonModel) -> sortedMedia.filter { (_, commonModel) -> commonModel !is MediaCommonModel.MediaControl || commonModel.mediaLoadedModel.instanceId != mediaDataLoadingModel.instanceId } Loading @@ -207,18 +204,52 @@ constructor( ) if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) { val isMediaFromRec = isMediaFromRec(it) val newCommonModel = MediaCommonModel.MediaControl( mediaDataLoadingModel, canBeRemoved(it), isMediaFromRec(it) ) sortedMap[sortKey] = newCommonModel _isMediaFromRec.value = isMediaFromRec if (isMediaFromRec) { // On Addition or tapping on recommendations, we should show the new order of media. if (mediaFromRecPackageName == it.packageName) { if (it.isPlaying == true) { mediaFromRecPackageName = null _currentMedia.value = sortedMap.values.toList() } } else if (sortedMap.size > sortedMedia.size) { _currentMedia.value = sortedMap.values.toList() } else if (sortedMap.size == sortedMedia.size) { // When loading an update for an existing media control. val currentList = mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) } currentList.forEachIndexed { index, mediaCommonModel -> if ( mediaCommonModel is MediaCommonModel.MediaControl && mediaCommonModel.mediaLoadedModel.instanceId == mediaDataLoadingModel.instanceId && mediaCommonModel != newCommonModel ) { // Update media model if changed. currentList[index] = newCommonModel } } _currentMedia.value = currentList } sortedMap[sortKey] = MediaCommonModel.MediaControl(mediaDataLoadingModel, canBeRemoved(it)) } } _sortedMedia.value = sortedMap sortedMedia = sortedMap // On removal we want to keep the order being shown to user. if (mediaDataLoadingModel is MediaDataLoadingModel.Removed) { _currentMedia.value = _currentMedia.value.filter { commonModel -> commonModel !is MediaCommonModel.MediaControl || mediaDataLoadingModel.instanceId != commonModel.mediaLoadedModel.instanceId } } } fun setRecommendationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) { Loading @@ -229,7 +260,7 @@ constructor( } val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) sortedMap.putAll( _sortedMedia.value.filter { (_, commonModel) -> sortedMedia.filter { (_, commonModel) -> commonModel !is MediaCommonModel.MediaRecommendations } ) Loading @@ -240,11 +271,25 @@ constructor( isPlaying = false, active = _smartspaceMediaData.value.isActive, ) if (smartspaceMediaLoadingModel is SmartspaceMediaLoadingModel.Loaded) { sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) when (smartspaceMediaLoadingModel) { is SmartspaceMediaLoadingModel.Loaded -> sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) is SmartspaceMediaLoadingModel.Removed -> _currentMedia.value = _currentMedia.value.filter { commonModel -> commonModel !is MediaCommonModel.MediaRecommendations } } if (sortedMap.size > sortedMedia.size) { _currentMedia.value = sortedMap.values.toList() } sortedMedia = sortedMap } _sortedMedia.value = sortedMap fun setOrderedMedia() { _currentMedia.value = sortedMedia.values.toList() } fun setMediaFromRecPackageName(packageName: String) { Loading packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +7 −13 Original line number Diff line number Diff line Loading @@ -63,7 +63,7 @@ constructor( private val mediaDeviceManager: MediaDeviceManager, private val mediaDataCombineLatest: MediaDataCombineLatest, private val mediaDataFilter: MediaDataFilterImpl, mediaFilterRepository: MediaFilterRepository, private val mediaFilterRepository: MediaFilterRepository, private val mediaFlags: MediaFlags, ) : MediaDataManager, CoreStartable { Loading Loading @@ -123,18 +123,8 @@ constructor( initialValue = false, ) /** The most recent sorted set for user media instances */ val sortedMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.sortedMedia .mapLatest { it.values.toList() } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = emptyList(), ) /** Whether the current change in media was done by clicking on a recommendation */ val isMediaFromRec: StateFlow<Boolean> = mediaFilterRepository.isMediaFromRec /** The current list for user media instances */ val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia override fun start() { if (!mediaFlags.isMediaControlsRefactorEnabled()) { Loading Loading @@ -251,6 +241,10 @@ constructor( override fun isRecommendationActive() = mediaDataRepository.smartspaceMediaData.value.isActive fun reorderMedia() { mediaFilterRepository.setOrderedMedia() } /** Add a listener for internal events. */ private fun addInternalListener(listener: MediaDataManager.Listener) = mediaDataProcessor.addInternalListener(listener) Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt +74 −20 Original line number Diff line number Diff line Loading @@ -150,7 +150,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun addMediaControlPlayingThenRemote() = testScope.runTest { val sortedMedia by collectLastValue(underTest.sortedMedia) val currentMedia by collectLastValue(underTest.currentMedia) val playingInstanceId = InstanceId.fakeInstanceId(123) val remoteInstanceId = InstanceId.fakeInstanceId(321) val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId) Loading @@ -161,8 +161,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(remoteData) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId)) assertThat(sortedMedia?.size).isEqualTo(2) assertThat(sortedMedia?.values) assertThat(currentMedia?.size).isEqualTo(2) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId)) Loading @@ -173,7 +173,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun switchMediaControlsPlaying() = testScope.runTest { val sortedMedia by collectLastValue(underTest.sortedMedia) val currentMedia by collectLastValue(underTest.currentMedia) val playingInstanceId1 = InstanceId.fakeInstanceId(123) val playingInstanceId2 = InstanceId.fakeInstanceId(321) var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1) Loading @@ -184,8 +184,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(playingData2) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2)) assertThat(sortedMedia?.size).isEqualTo(2) assertThat(sortedMedia?.values) assertThat(currentMedia?.size).isEqualTo(2) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2)) Loading @@ -198,12 +198,28 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(playingData1) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1)) underTest.addSelectedUserMediaEntry(playingData2) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2)) underTest.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(playingInstanceId2, false) ) assertThat(currentMedia?.size).isEqualTo(2) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)), MediaCommonModel.MediaControl( MediaDataLoadingModel.Loaded(playingInstanceId2, false) ) ) .inOrder() assertThat(sortedMedia?.size).isEqualTo(2) assertThat(sortedMedia?.values) underTest.setOrderedMedia() assertThat(currentMedia?.size).isEqualTo(2) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2)), MediaCommonModel.MediaControl( MediaDataLoadingModel.Loaded(playingInstanceId2, false) ), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)) ) .inOrder() Loading @@ -212,7 +228,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun fullOrderTest() = testScope.runTest { val sortedMedia by collectLastValue(underTest.sortedMedia) val currentMedia by collectLastValue(underTest.currentMedia) val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) val instanceId3 = InstanceId.fakeInstanceId(321) Loading Loading @@ -252,8 +268,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) ) assertThat(sortedMedia?.size).isEqualTo(6) assertThat(sortedMedia?.values) assertThat(currentMedia?.size).isEqualTo(6) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId1)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId2)), Loading @@ -270,7 +286,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun loadMediaFromRec() = testScope.runTest { val isMediaFromRec by collectLastValue(underTest.isMediaFromRec) val currentMedia by collectLastValue(underTest.currentMedia) val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) val data = Loading @@ -278,22 +294,59 @@ class MediaFilterRepositoryTest : SysuiTestCase() { active = true, instanceId = instanceId1, packageName = PACKAGE_NAME, isPlaying = true isPlaying = true, notificationKey = KEY, ) val newData = MediaData( active = true, instanceId = instanceId2, isPlaying = true, notificationKey = KEY_2 ) val icon = Icon.createWithResource(context, R.drawable.ic_media_play) val mediaRecommendations = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, packageName = PACKAGE_NAME, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) val newData = MediaData(active = true, instanceId = instanceId2) assertThat(isMediaFromRec).isFalse() underTest.setMediaFromRecPackageName(PACKAGE_NAME) underTest.addSelectedUserMediaEntry(data) underTest.setRecommendation(mediaRecommendations) underTest.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) ) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1)) assertThat(isMediaFromRec).isTrue() assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl( MediaDataLoadingModel.Loaded(instanceId1), isMediaFromRec = true ), MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) ) ) .inOrder() underTest.addSelectedUserMediaEntry(newData) underTest.addSelectedUserMediaEntry(data.copy(isPlaying = false)) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2)) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1)) assertThat(isMediaFromRec).isFalse() assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId2)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId1)), MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) ) ) .inOrder() } private fun createMediaData( Loading @@ -316,6 +369,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { private const val LOCAL = MediaData.PLAYBACK_LOCAL private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL private const val KEY = "KEY" private const val KEY_2 = "KEY_2" private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" private const val PACKAGE_NAME = "com.android.example" } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt +35 −34 Original line number Diff line number Diff line Loading @@ -55,6 +55,14 @@ class MediaCarouselInteractorTest : SysuiTestCase() { private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository private val mediaRecommendationsInteractor: MediaRecommendationsInteractor = kosmos.mediaRecommendationsInteractor val icon = Icon.createWithResource(context, R.drawable.ic_media_play) private val mediaRecommendation = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, packageName = PACKAGE_NAME, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor Loading Loading @@ -122,26 +130,19 @@ class MediaCarouselInteractorTest : SysuiTestCase() { collectLastValue(underTest.hasActiveMediaOrRecommendation) val hasAnyMediaOrRecommendation by collectLastValue(underTest.hasAnyMediaOrRecommendation) val sortedMedia by collectLastValue(underTest.sortedMedia) val currentMedia by collectLastValue(underTest.currentMedia) kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) val icon = Icon.createWithResource(context, R.drawable.ic_media_play) val userMediaRecommendation = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) val userMedia = MediaData(active = false) val recsLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) val mediaLoadingModel = MediaDataLoadingModel.Loaded(userMedia.instanceId) mediaFilterRepository.setRecommendation(userMediaRecommendation) mediaFilterRepository.setRecommendation(mediaRecommendation) mediaFilterRepository.setRecommendationsLoadingState(recsLoadingModel) assertThat(hasActiveMediaOrRecommendation).isTrue() assertThat(hasAnyMediaOrRecommendation).isTrue() assertThat(sortedMedia) assertThat(currentMedia) .containsExactly(MediaCommonModel.MediaRecommendations(recsLoadingModel)) mediaFilterRepository.addSelectedUserMediaEntry(userMedia) Loading @@ -149,7 +150,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() { assertThat(hasActiveMediaOrRecommendation).isTrue() assertThat(hasAnyMediaOrRecommendation).isTrue() assertThat(sortedMedia) assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaRecommendations(recsLoadingModel), MediaCommonModel.MediaControl(mediaLoadingModel, true) Loading @@ -166,14 +167,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() { collectLastValue(underTest.hasAnyMediaOrRecommendation) kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) val icon = Icon.createWithResource(context, R.drawable.ic_media_play) val mediaRecommendation = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) mediaFilterRepository.setRecommendation(mediaRecommendation) assertThat(hasActiveMediaOrRecommendation).isTrue() Loading @@ -194,14 +187,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() { collectLastValue(underTest.hasAnyMediaOrRecommendation) kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) val icon = Icon.createWithResource(context, R.drawable.ic_media_play) val mediaRecommendation = SmartspaceMediaData( targetId = KEY_MEDIA_SMARTSPACE, isActive = true, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) mediaFilterRepository.setRecommendation(mediaRecommendation) assertThat(hasActiveMediaOrRecommendation).isTrue() Loading Loading @@ -234,26 +219,42 @@ class MediaCarouselInteractorTest : SysuiTestCase() { @Test fun loadMediaFromRec() = testScope.runTest { val isMediaFromRec by collectLastValue(underTest.isMediaFromRec) val currentMedia by collectLastValue(underTest.currentMedia) val instanceId = InstanceId.fakeInstanceId(123) val data = MediaData(active = true, instanceId = instanceId, packageName = PACKAGE_NAME) assertThat(isMediaFromRec).isFalse() val data = MediaData( active = true, instanceId = instanceId, packageName = PACKAGE_NAME, notificationKey = KEY ) val smartspaceLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) val mediaLoadingModel = MediaDataLoadingModel.Loaded(instanceId) mediaFilterRepository.setRecommendation(mediaRecommendation) mediaFilterRepository.setRecommendationsLoadingState(smartspaceLoadingModel) mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME) mediaFilterRepository.addSelectedUserMediaEntry(data) mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel) assertThat(isMediaFromRec).isFalse() assertThat(currentMedia) .containsExactly(MediaCommonModel.MediaRecommendations(smartspaceLoadingModel)) .inOrder() mediaFilterRepository.addSelectedUserMediaEntry(data.copy(isPlaying = true)) mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) assertThat(isMediaFromRec).isTrue() assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(mediaLoadingModel, isMediaFromRec = true), MediaCommonModel.MediaRecommendations(smartspaceLoadingModel) ) .inOrder() } companion object { private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" private const val PACKAGE_NAME = "com.android.example" private const val KEY = "key" } }
packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt +23 −6 Original line number Diff line number Diff line Loading @@ -92,13 +92,29 @@ class MediaCarouselViewModelTest : SysuiTestCase() { val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) loadMediaControl(KEY, instanceId1) loadMediaControl(KEY_2, instanceId2) loadMediaControl(KEY, instanceId1, isPlaying = true) loadMediaControl(KEY_2, instanceId2, isPlaying = true) loadMediaControl(KEY, instanceId1, isPlaying = false) val firstMediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl val secondMediaControl = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl assertThat(firstMediaControl.instanceId).isEqualTo(instanceId2) assertThat(secondMediaControl.instanceId).isEqualTo(instanceId1) var mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl var mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) loadMediaControl(KEY, instanceId1, isPlaying = true) loadMediaControl(KEY_2, instanceId2, isPlaying = false) mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) underTest.onAttached() mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) } @Test Loading Loading @@ -147,6 +163,7 @@ class MediaCarouselViewModelTest : SysuiTestCase() { val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl assertThat(sortedMedia).hasSize(2) assertThat(mediaControl.instanceId).isEqualTo(instanceId) assertThat(mediaControl.isMediaFromRec).isTrue() } private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) { Loading
packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt +65 −20 Original line number Diff line number Diff line Loading @@ -107,14 +107,11 @@ constructor( .thenByDescending { it.updateTime } .thenByDescending { it.notificationKey } private val _sortedMedia: MutableStateFlow<TreeMap<MediaSortKeyModel, MediaCommonModel>> = MutableStateFlow(TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)) val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> = _sortedMedia.asStateFlow() private val _isMediaFromRec: MutableStateFlow<Boolean> = MutableStateFlow(false) val isMediaFromRec: StateFlow<Boolean> = _isMediaFromRec.asStateFlow() private val _currentMedia: MutableStateFlow<List<MediaCommonModel>> = MutableStateFlow(mutableListOf()) val currentMedia = _currentMedia.asStateFlow() private var sortedMedia = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) private var mediaFromRecPackageName: String? = null private var locale: Locale = applicationContext.resources.configuration.locales.get(0) Loading Loading @@ -186,7 +183,7 @@ constructor( fun addMediaDataLoadingState(mediaDataLoadingModel: MediaDataLoadingModel) { val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) sortedMap.putAll( _sortedMedia.value.filter { (_, commonModel) -> sortedMedia.filter { (_, commonModel) -> commonModel !is MediaCommonModel.MediaControl || commonModel.mediaLoadedModel.instanceId != mediaDataLoadingModel.instanceId } Loading @@ -207,18 +204,52 @@ constructor( ) if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) { val isMediaFromRec = isMediaFromRec(it) val newCommonModel = MediaCommonModel.MediaControl( mediaDataLoadingModel, canBeRemoved(it), isMediaFromRec(it) ) sortedMap[sortKey] = newCommonModel _isMediaFromRec.value = isMediaFromRec if (isMediaFromRec) { // On Addition or tapping on recommendations, we should show the new order of media. if (mediaFromRecPackageName == it.packageName) { if (it.isPlaying == true) { mediaFromRecPackageName = null _currentMedia.value = sortedMap.values.toList() } } else if (sortedMap.size > sortedMedia.size) { _currentMedia.value = sortedMap.values.toList() } else if (sortedMap.size == sortedMedia.size) { // When loading an update for an existing media control. val currentList = mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) } currentList.forEachIndexed { index, mediaCommonModel -> if ( mediaCommonModel is MediaCommonModel.MediaControl && mediaCommonModel.mediaLoadedModel.instanceId == mediaDataLoadingModel.instanceId && mediaCommonModel != newCommonModel ) { // Update media model if changed. currentList[index] = newCommonModel } } _currentMedia.value = currentList } sortedMap[sortKey] = MediaCommonModel.MediaControl(mediaDataLoadingModel, canBeRemoved(it)) } } _sortedMedia.value = sortedMap sortedMedia = sortedMap // On removal we want to keep the order being shown to user. if (mediaDataLoadingModel is MediaDataLoadingModel.Removed) { _currentMedia.value = _currentMedia.value.filter { commonModel -> commonModel !is MediaCommonModel.MediaControl || mediaDataLoadingModel.instanceId != commonModel.mediaLoadedModel.instanceId } } } fun setRecommendationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) { Loading @@ -229,7 +260,7 @@ constructor( } val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) sortedMap.putAll( _sortedMedia.value.filter { (_, commonModel) -> sortedMedia.filter { (_, commonModel) -> commonModel !is MediaCommonModel.MediaRecommendations } ) Loading @@ -240,11 +271,25 @@ constructor( isPlaying = false, active = _smartspaceMediaData.value.isActive, ) if (smartspaceMediaLoadingModel is SmartspaceMediaLoadingModel.Loaded) { sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) when (smartspaceMediaLoadingModel) { is SmartspaceMediaLoadingModel.Loaded -> sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) is SmartspaceMediaLoadingModel.Removed -> _currentMedia.value = _currentMedia.value.filter { commonModel -> commonModel !is MediaCommonModel.MediaRecommendations } } if (sortedMap.size > sortedMedia.size) { _currentMedia.value = sortedMap.values.toList() } sortedMedia = sortedMap } _sortedMedia.value = sortedMap fun setOrderedMedia() { _currentMedia.value = sortedMedia.values.toList() } fun setMediaFromRecPackageName(packageName: String) { Loading
packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +7 −13 Original line number Diff line number Diff line Loading @@ -63,7 +63,7 @@ constructor( private val mediaDeviceManager: MediaDeviceManager, private val mediaDataCombineLatest: MediaDataCombineLatest, private val mediaDataFilter: MediaDataFilterImpl, mediaFilterRepository: MediaFilterRepository, private val mediaFilterRepository: MediaFilterRepository, private val mediaFlags: MediaFlags, ) : MediaDataManager, CoreStartable { Loading Loading @@ -123,18 +123,8 @@ constructor( initialValue = false, ) /** The most recent sorted set for user media instances */ val sortedMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.sortedMedia .mapLatest { it.values.toList() } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = emptyList(), ) /** Whether the current change in media was done by clicking on a recommendation */ val isMediaFromRec: StateFlow<Boolean> = mediaFilterRepository.isMediaFromRec /** The current list for user media instances */ val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia override fun start() { if (!mediaFlags.isMediaControlsRefactorEnabled()) { Loading Loading @@ -251,6 +241,10 @@ constructor( override fun isRecommendationActive() = mediaDataRepository.smartspaceMediaData.value.isActive fun reorderMedia() { mediaFilterRepository.setOrderedMedia() } /** Add a listener for internal events. */ private fun addInternalListener(listener: MediaDataManager.Listener) = mediaDataProcessor.addInternalListener(listener) Loading