summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Michael Mikhail <michaelmikhil@google.com> 2024-04-19 21:09:17 +0000
committer Michael Mikhail <michaelmikhil@google.com> 2024-04-24 14:32:07 +0000
commitd74e08af1ba2dc1d26c9c205db9c94c5569df97c (patch)
tree0711ffb8920090e1781d85e1c44a420bd63b08a0
parent3f548c0033a3918c7d80377c2598b7fb34369aed (diff)
Handle reordering when recommendation clicked.
Flag: ACONFIG com.android.systemui.media_controls_refactor DISABLED Bug: 328207006 Test: atest SystemUiRoboTests:MediaCarouselViewModelTest Change-Id: Ic85447cdc112de33c237fe98ba06c4dd096f0856
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderKosmos.kt21
11 files changed, 163 insertions, 8 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 e39511fdfdab..1e5f31401e20 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
@@ -267,6 +267,35 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
.inOrder()
}
+ @Test
+ fun loadMediaFromRec() =
+ testScope.runTest {
+ val isMediaFromRec by collectLastValue(underTest.isMediaFromRec)
+ val instanceId1 = InstanceId.fakeInstanceId(123)
+ val instanceId2 = InstanceId.fakeInstanceId(456)
+ val data =
+ MediaData(
+ active = true,
+ instanceId = instanceId1,
+ packageName = PACKAGE_NAME,
+ isPlaying = true
+ )
+ val newData = MediaData(active = true, instanceId = instanceId2)
+
+ assertThat(isMediaFromRec).isFalse()
+
+ underTest.setMediaFromRecPackageName(PACKAGE_NAME)
+ underTest.addSelectedUserMediaEntry(data)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1))
+
+ assertThat(isMediaFromRec).isTrue()
+
+ underTest.addSelectedUserMediaEntry(newData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2))
+
+ assertThat(isMediaFromRec).isFalse()
+ }
+
private fun createMediaData(
app: String,
playing: Boolean,
@@ -288,5 +317,6 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
private const val KEY = "KEY"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
+ private const val PACKAGE_NAME = "com.android.example"
}
}
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 a2991fddc0ca..e44affc7262b 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
@@ -29,7 +30,9 @@ 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.interactor.MediaCarouselInteractor
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
@@ -50,6 +53,8 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository
+ private val mediaRecommendationsInteractor: MediaRecommendationsInteractor =
+ kosmos.mediaRecommendationsInteractor
private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor
@@ -226,7 +231,29 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() =
testScope.runTest { assertThat(underTest.hasActiveMediaOrRecommendation.value).isFalse() }
+ @Test
+ fun loadMediaFromRec() =
+ testScope.runTest {
+ val isMediaFromRec by collectLastValue(underTest.isMediaFromRec)
+ val instanceId = InstanceId.fakeInstanceId(123)
+ val data = MediaData(active = true, instanceId = instanceId, packageName = PACKAGE_NAME)
+
+ assertThat(isMediaFromRec).isFalse()
+
+ mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME)
+ mediaFilterRepository.addSelectedUserMediaEntry(data)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ assertThat(isMediaFromRec).isFalse()
+
+ mediaFilterRepository.addSelectedUserMediaEntry(data.copy(isPlaying = true))
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ assertThat(isMediaFromRec).isTrue()
+ }
+
companion object {
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
+ private const val PACKAGE_NAME = "com.android.example"
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index 4b5fecd9c68d..d1e475f590dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -31,9 +31,11 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
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.mediaRecommendationsInteractor
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.SmartspaceMediaData
+import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -119,7 +121,35 @@ class MediaCarouselViewModelTest : SysuiTestCase() {
assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
}
- private fun loadMediaControl(key: String, instanceId: InstanceId) {
+ @Test
+ fun recommendationClicked_switchToPlayer() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.mediaItems)
+ kosmos.visualStabilityProvider.isReorderingAllowed = false
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+ val instanceId = InstanceId.fakeInstanceId(123)
+
+ loadMediaRecommendations()
+ kosmos.mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME)
+
+ var recsCard = sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations
+ assertThat(sortedMedia).hasSize(1)
+ assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
+
+ loadMediaControl(KEY, instanceId, false)
+
+ recsCard = sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations
+ assertThat(sortedMedia).hasSize(1)
+ assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
+
+ loadMediaControl(KEY, instanceId, true)
+
+ val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
+ assertThat(sortedMedia).hasSize(2)
+ assertThat(mediaControl.instanceId).isEqualTo(instanceId)
+ }
+
+ private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) {
whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
val mediaData =
@@ -127,7 +157,8 @@ class MediaCarouselViewModelTest : SysuiTestCase() {
userId = USER_ID,
packageName = PACKAGE_NAME,
notificationKey = key,
- instanceId = instanceId
+ instanceId = instanceId,
+ isPlaying = isPlaying,
)
mediaDataFilter.onMediaDataLoaded(key, key, mediaData)
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 8ee3adcb46ef..df3a974634d7 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
@@ -72,6 +72,11 @@ class MediaFilterRepository @Inject constructor(private val systemClock: SystemC
val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> =
_sortedMedia.asStateFlow()
+ private val _isMediaFromRec: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ val isMediaFromRec: StateFlow<Boolean> = _isMediaFromRec.asStateFlow()
+
+ private var mediaFromRecPackageName: String? = null
+
fun addMediaEntry(key: String, data: MediaData) {
val entries = LinkedHashMap<String, MediaData>(_allUserEntries.value)
entries[key] = data
@@ -161,6 +166,12 @@ class MediaFilterRepository @Inject constructor(private val systemClock: SystemC
)
if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) {
+ val isMediaFromRec = isMediaFromRec(it)
+
+ _isMediaFromRec.value = isMediaFromRec
+ if (isMediaFromRec) {
+ mediaFromRecPackageName = null
+ }
sortedMap[sortKey] =
MediaCommonModel.MediaControl(mediaDataLoadingModel, canBeRemoved(it))
}
@@ -195,7 +206,15 @@ class MediaFilterRepository @Inject constructor(private val systemClock: SystemC
_sortedMedia.value = sortedMap
}
+ fun setMediaFromRecPackageName(packageName: String) {
+ mediaFromRecPackageName = packageName
+ }
+
private fun canBeRemoved(data: MediaData): Boolean {
return data.isPlaying?.let { !it } ?: data.isClearable && !data.active
}
+
+ private fun isMediaFromRec(data: MediaData): Boolean {
+ return data.isPlaying == true && mediaFromRecPackageName == data.packageName
+ }
}
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 dc2c6512deda..13f934ecda43 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
@@ -133,6 +133,9 @@ constructor(
initialValue = emptyList(),
)
+ /** Whether the current change in media was done by clicking on a recommendation */
+ val isMediaFromRec: StateFlow<Boolean> = mediaFilterRepository.isMediaFromRec
+
override fun start() {
if (!mediaFlags.isMediaControlsRefactorEnabled()) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
index d57b04938d2c..dea581083cb7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
@@ -49,7 +49,7 @@ class MediaRecommendationsInteractor
constructor(
@Application applicationScope: CoroutineScope,
@Application private val applicationContext: Context,
- repository: MediaFilterRepository,
+ private val repository: MediaFilterRepository,
private val mediaDataProcessor: MediaDataProcessor,
private val broadcastSender: BroadcastSender,
private val activityStarter: ActivityStarter,
@@ -133,6 +133,10 @@ constructor(
}
}
+ fun switchToMediaControl(packageName: String) {
+ repository.setMediaFromRecPackageName(packageName)
+ }
+
companion object {
private const val TAG = "MediaRecommendationsInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
index 562fe7a9ca67..23860bb9868c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
@@ -20,7 +20,7 @@ package com.android.systemui.media.controls.shared.model
sealed class MediaCommonModel {
data class MediaControl(
val mediaLoadedModel: MediaDataLoadingModel.Loaded,
- val canBeRemoved: Boolean = false
+ val canBeRemoved: Boolean = false,
) : MediaCommonModel()
data class MediaRecommendations(val recsLoadingModel: SmartspaceMediaLoadingModel) :
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index c92965e5d6ba..5188132b571e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -29,6 +29,7 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.util.Utils
+import com.android.systemui.util.kotlin.pairwiseBy
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -37,8 +38,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user inputs for media carousel */
@@ -66,8 +67,11 @@ constructor(
awaitClose { visualStabilityProvider.removeReorderingAllowedListener(listener) }
}
.flatMapLatest {
- interactor.sortedMedia.map { sortedItems ->
+ combine(interactor.isMediaFromRec, interactor.sortedMedia) {
+ isRecsToMedia,
+ sortedItems ->
buildList {
+ shouldReorder = isRecsToMedia
val reorderAllowed = isReorderingAllowed()
sortedItems.forEach { commonModel ->
if (!reorderAllowed || !modelsPendingRemoval.contains(commonModel)) {
@@ -85,6 +89,16 @@ constructor(
}
}
}
+ .pairwiseBy { old, new ->
+ // This condition can only happen when view is attached. So the old emit is of the
+ // most recent list updated.
+ // If the old list is empty, it is okay to emit the new ordered list.
+ if (isReorderingAllowed() || shouldReorder || old.isEmpty()) {
+ new
+ } else {
+ old
+ }
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
@@ -98,6 +112,8 @@ constructor(
private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf()
+ private var shouldReorder = true
+
fun onSwipeToDismiss() {
logger.logSwipeDismiss()
interactor.onSwipeToDismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
index a2307d407396..5cd980441b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
@@ -107,6 +107,10 @@ constructor(
logger.logRecommendationItemTap(packageName, instanceId, index)
}
// TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT).
+
+ // set the package name of the player added by recommendation once the media is loaded.
+ interactor.switchToMediaControl(packageName)
+
interactor.startClickIntent(expandable, intent)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
index 9a181cd4ab72..6eae28f7c772 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
@@ -24,7 +24,7 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.factory.me
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.media.controls.util.mediaUiEventLogger
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
+import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
val Kosmos.mediaCarouselViewModel by
Kosmos.Fixture {
@@ -32,7 +32,7 @@ val Kosmos.mediaCarouselViewModel by
applicationScope = applicationCoroutineScope,
applicationContext = applicationContext,
backgroundDispatcher = testDispatcher,
- visualStabilityProvider = VisualStabilityProvider(),
+ visualStabilityProvider = visualStabilityProvider,
interactor = mediaCarouselInteractor,
controlInteractorFactory = mediaControlInteractorFactory,
recommendationsViewModel = mediaRecommendationsViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderKosmos.kt
new file mode 100644
index 000000000000..9a5f126584e8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.statusbar.notification.collection.provider
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.visualStabilityProvider by Kosmos.Fixture { VisualStabilityProvider() }