summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/MediaTestHelper.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaDataRepositoryTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt93
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt118
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaControlModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaRecommendationsModel.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaInstanceIdKosmos.kt22
14 files changed, 566 insertions, 18 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/MediaTestHelper.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/MediaTestHelper.kt
index 8e44932fb38e..720bcb52d95d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/MediaTestHelper.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/MediaTestHelper.kt
@@ -16,9 +16,7 @@
package com.android.systemui.media.controls
-import android.R
import android.app.smartspace.SmartspaceAction
-import android.content.Context
import android.graphics.drawable.Icon
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -26,17 +24,9 @@ import com.android.systemui.util.mockito.whenever
class MediaTestHelper {
companion object {
/** Returns a list of three mocked recommendations */
- fun getValidRecommendationList(context: Context): List<SmartspaceAction> {
+ fun getValidRecommendationList(mediaIcon: Icon): List<SmartspaceAction> {
val mediaRecommendationItem =
- mock<SmartspaceAction> {
- whenever(icon)
- .thenReturn(
- Icon.createWithResource(
- context,
- R.drawable.ic_media_play,
- )
- )
- }
+ mock<SmartspaceAction> { whenever(icon).thenReturn(mediaIcon) }
return listOf(mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaDataRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaDataRepositoryTest.kt
index 6c41bc3c1000..2864f041e451 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaDataRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaDataRepositoryTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.media.controls.data.repository
+import android.R
+import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -81,11 +83,12 @@ class MediaDataRepositoryTest : SysuiTestCase() {
testScope.runTest {
kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, true)
val smartspaceData by collectLastValue(underTest.smartspaceMediaData)
+ val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
val recommendation =
SmartspaceMediaData(
targetId = KEY_MEDIA_SMARTSPACE,
isActive = true,
- recommendations = MediaTestHelper.getValidRecommendationList(context),
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
)
underTest.setRecommendation(recommendation)
@@ -102,11 +105,12 @@ class MediaDataRepositoryTest : SysuiTestCase() {
fun dismissRecommendation() =
testScope.runTest {
val smartspaceData by collectLastValue(underTest.smartspaceMediaData)
+ val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
val recommendation =
SmartspaceMediaData(
targetId = KEY_MEDIA_SMARTSPACE,
isActive = true,
- recommendations = MediaTestHelper.getValidRecommendationList(context),
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
)
underTest.setRecommendation(recommendation)
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 f6381c655649..956ef661d467 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
@@ -16,6 +16,8 @@
package com.android.systemui.media.controls.data.repository
+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
@@ -124,11 +126,12 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
testScope.runTest {
val smartspaceMediaData by collectLastValue(underTest.smartspaceMediaData)
+ val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
val mediaRecommendation =
SmartspaceMediaData(
targetId = KEY_MEDIA_SMARTSPACE,
isActive = true,
- recommendations = MediaTestHelper.getValidRecommendationList(context),
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
)
underTest.setRecommendation(mediaRecommendation)
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 2d1115534de9..d9d84f2d2aac 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
@@ -16,6 +16,8 @@
package com.android.systemui.media.controls.domain.interactor
+import android.R
+import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -103,11 +105,12 @@ 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 userMediaRecommendation =
SmartspaceMediaData(
targetId = KEY_MEDIA_SMARTSPACE,
isActive = true,
- recommendations = MediaTestHelper.getValidRecommendationList(context),
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
)
val userMedia = MediaData().copy(active = false)
@@ -131,11 +134,12 @@ 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(context),
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
)
mediaFilterRepository.setRecommendation(mediaRecommendation)
@@ -158,11 +162,12 @@ 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(context),
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
)
mediaFilterRepository.setRecommendation(mediaRecommendation)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
new file mode 100644
index 000000000000..a1cee8aaac7c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor
+import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.util.mediaInstanceId
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MediaControlInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
+ private val instanceId: InstanceId = kosmos.mediaInstanceId
+ private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
+
+ private val underTest: MediaControlInteractor = kosmos.mediaControlInteractor
+
+ @Test
+ fun onMediaDataUpdated() =
+ testScope.runTest {
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val controlModel by collectLastValue(underTest.mediaControl)
+ var mediaData =
+ MediaData(userId = USER_ID, instanceId = instanceId, artist = SESSION_ARTIST)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(controlModel?.instanceId).isEqualTo(instanceId)
+ assertThat(controlModel?.artistName).isEqualTo(SESSION_ARTIST)
+
+ mediaData =
+ MediaData(userId = USER_ID, instanceId = instanceId, artist = SESSION_ARTIST_2)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(controlModel?.instanceId).isEqualTo(instanceId)
+ assertThat(controlModel?.artistName).isEqualTo(SESSION_ARTIST_2)
+
+ mediaData =
+ MediaData(
+ userId = USER_ID,
+ instanceId = InstanceId.fakeInstanceId(2),
+ artist = SESSION_ARTIST
+ )
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(controlModel?.instanceId).isNotEqualTo(mediaData.instanceId)
+ assertThat(controlModel?.artistName).isEqualTo(SESSION_ARTIST_2)
+ }
+
+ companion object {
+ private const val USER_ID = 0
+ private const val KEY = "key"
+ private const val SESSION_ARTIST = "artist"
+ private const val SESSION_ARTIST_2 = "artist2"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt
new file mode 100644
index 000000000000..28995e1feb0e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.interactor
+
+import android.R
+import android.graphics.drawable.Icon
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+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.MediaRecommendationsInteractor
+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.MediaRecModel
+import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MediaRecommendationsInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
+ private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+ private val smartspaceMediaData: SmartspaceMediaData =
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ packageName = PACKAGE_NAME,
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
+ )
+
+ private val underTest: MediaRecommendationsInteractor = kosmos.mediaRecommendationsInteractor
+
+ @Test
+ fun addRecommendation_smartspaceMediaDataUpdate() =
+ testScope.runTest {
+ val recommendations by collectLastValue(underTest.recommendations)
+
+ val model =
+ MediaRecommendationsModel(
+ key = KEY_MEDIA_SMARTSPACE,
+ packageName = PACKAGE_NAME,
+ areRecommendationsValid = true,
+ mediaRecs =
+ listOf(
+ MediaRecModel(icon = icon),
+ MediaRecModel(icon = icon),
+ MediaRecModel(icon = icon)
+ )
+ )
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
+
+ assertThat(recommendations).isEqualTo(model)
+ }
+
+ @Test
+ fun setRecommendationInactive_isActiveUpdate() =
+ testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, true)
+ val isActive by collectLastValue(underTest.isActive)
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
+ assertThat(isActive).isTrue()
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(
+ KEY_MEDIA_SMARTSPACE,
+ smartspaceMediaData.copy(isActive = false)
+ )
+ assertThat(isActive).isFalse()
+ }
+
+ @Test
+ fun addInvalidRecommendation() =
+ testScope.runTest {
+ val recommendations by collectLastValue(underTest.recommendations)
+ val inValidData = smartspaceMediaData.copy(recommendations = listOf())
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
+ assertThat(recommendations?.areRecommendationsValid).isTrue()
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, inValidData)
+ assertThat(recommendations?.areRecommendationsValid).isFalse()
+ assertThat(recommendations?.mediaRecs?.isEmpty()).isTrue()
+ }
+
+ companion object {
+ private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
+ private const val PACKAGE_NAME = "com.example.app"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
new file mode 100644
index 000000000000..5a0388de444e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline.interactor
+
+import com.android.internal.logging.InstanceId
+import com.android.systemui.media.controls.data.repository.MediaFilterRepository
+import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
+import com.android.systemui.media.controls.shared.model.MediaControlModel
+import com.android.systemui.media.controls.shared.model.MediaData
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** Encapsulates business logic for single media control. */
+class MediaControlInteractor(
+ instanceId: InstanceId,
+ repository: MediaFilterRepository,
+ private val mediaDataProcessor: MediaDataProcessor,
+) {
+
+ val mediaControl: Flow<MediaControlModel?> =
+ repository.selectedUserEntries
+ .map { entries -> entries[instanceId]?.let { toMediaControlModel(it) } }
+ .distinctUntilChanged()
+
+ fun removeMediaControl(key: String, delayMs: Long): Boolean {
+ return mediaDataProcessor.dismissMediaData(key, delayMs)
+ }
+
+ private fun toMediaControlModel(data: MediaData): MediaControlModel {
+ return with(data) {
+ MediaControlModel(
+ uid = appUid,
+ packageName = packageName,
+ instanceId = instanceId,
+ token = token,
+ appIcon = appIcon,
+ clickIntent = clickIntent,
+ appName = app,
+ songName = song,
+ artistName = artist,
+ artwork = artwork,
+ deviceData = device,
+ semanticActionButtons = semanticActions,
+ notificationActionButtons = actions,
+ actionsToShowInCollapsed = actionsToShowInCompact,
+ isResume = resumption,
+ resumeProgress = resumeProgress,
+ )
+ }
+ }
+}
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
new file mode 100644
index 000000000000..40a132a491a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline.interactor
+
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.controls.data.repository.MediaFilterRepository
+import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
+import com.android.systemui.media.controls.shared.model.MediaRecModel
+import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Encapsulates business logic for media recommendation */
+@SysUISingleton
+class MediaRecommendationsInteractor
+@Inject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ @Application private val applicationContext: Context,
+ repository: MediaFilterRepository,
+ private val mediaDataProcessor: MediaDataProcessor,
+) {
+
+ val recommendations: Flow<MediaRecommendationsModel> =
+ repository.smartspaceMediaData.map { toRecommendationsModel(it) }.distinctUntilChanged()
+
+ /** Indicates whether the recommendations card is active. */
+ val isActive: StateFlow<Boolean> =
+ repository.smartspaceMediaData
+ .map { it.isActive }
+ .distinctUntilChanged()
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+
+ fun removeMediaRecommendations(key: String, delayMs: Long) {
+ mediaDataProcessor.dismissSmartspaceRecommendation(key, delayMs)
+ }
+
+ private fun toRecommendationsModel(data: SmartspaceMediaData): MediaRecommendationsModel {
+ val mediaRecs = ArrayList<MediaRecModel>()
+ data.recommendations.forEach {
+ with(it) { mediaRecs.add(MediaRecModel(intent, title, subtitle, icon, extras)) }
+ }
+ return with(data) {
+ MediaRecommendationsModel(
+ key = targetId,
+ uid = getUid(applicationContext),
+ packageName = packageName,
+ instanceId = instanceId,
+ appName = getAppName(applicationContext),
+ dismissIntent = dismissIntent,
+ areRecommendationsValid = isValid(),
+ mediaRecs = mediaRecs,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaControlModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaControlModel.kt
new file mode 100644
index 000000000000..d4e34b5af260
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaControlModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.shared.model
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.graphics.drawable.Icon
+import android.media.session.MediaSession
+import android.os.Process
+import com.android.internal.logging.InstanceId
+
+data class MediaControlModel(
+ val uid: Int = Process.INVALID_UID,
+ val packageName: String,
+ val instanceId: InstanceId,
+ val token: MediaSession.Token?,
+ val appIcon: Icon?,
+ val clickIntent: PendingIntent?,
+ val appName: String?,
+ val songName: CharSequence?,
+ val artistName: CharSequence?,
+ val artwork: Icon?,
+ val deviceData: MediaDeviceData?,
+ /** [MediaButton] contains [MediaAction] objects which represent specific buttons in the UI */
+ val semanticActionButtons: MediaButton?,
+ val notificationActionButtons: List<MediaAction>,
+ /**
+ * List of [notificationActionButtons] indices shown on smaller version of media player. Check
+ * [Notification.MediaStyle.setShowActionsInCompactView].
+ */
+ val actionsToShowInCollapsed: List<Int>,
+ /** Whether player is in resumption state. */
+ val isResume: Boolean,
+ /** Track seek bar progress (0 - 1) when [isResume] is true. */
+ val resumeProgress: Double?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaRecommendationsModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaRecommendationsModel.kt
new file mode 100644
index 000000000000..43bd32d90c55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaRecommendationsModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.shared.model
+
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.Process
+import com.android.internal.logging.InstanceId
+
+data class MediaRecommendationsModel(
+ val key: String,
+ val uid: Int = Process.INVALID_UID,
+ val packageName: String,
+ val instanceId: InstanceId? = null,
+ val appName: CharSequence? = null,
+ val dismissIntent: Intent? = null,
+ /** Whether the model contains enough number of valid recommendations. */
+ val areRecommendationsValid: Boolean = false,
+ val mediaRecs: List<MediaRecModel>,
+)
+
+/** Represents smartspace media recommendation action */
+data class MediaRecModel(
+ val intent: Intent? = null,
+ val title: CharSequence? = null,
+ val subtitle: CharSequence? = null,
+ val icon: Icon? = null,
+ val extras: Bundle? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
index b44658502f48..9e15dbbb64b8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
@@ -20,6 +20,7 @@ import android.app.smartspace.SmartspaceAction
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.os.Process
import android.text.TextUtils
import android.util.Log
import androidx.annotation.VisibleForTesting
@@ -87,6 +88,15 @@ data class SmartspaceMediaData(
null
}
}
+
+ fun getUid(context: Context): Int {
+ return try {
+ context.packageManager.getApplicationInfo(packageName, 0 /* flags */).uid
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Fail to get media recommendation's app info", e)
+ Process.INVALID_UID
+ }
+ }
}
/** Key to indicate whether this card should be used to re-show recent media */
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt
new file mode 100644
index 000000000000..29c5bd5dd1d4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
+import com.android.systemui.media.controls.util.mediaInstanceId
+
+val Kosmos.mediaControlInteractor by
+ Kosmos.Fixture {
+ MediaControlInteractor(
+ instanceId = mediaInstanceId,
+ repository = mediaFilterRepository,
+ mediaDataProcessor = mediaDataProcessor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt
new file mode 100644
index 000000000000..372a1961159d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline.interactor
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
+
+val Kosmos.mediaRecommendationsInteractor by
+ Kosmos.Fixture {
+ MediaRecommendationsInteractor(
+ applicationScope = applicationCoroutineScope,
+ applicationContext = applicationContext,
+ repository = mediaFilterRepository,
+ mediaDataProcessor = mediaDataProcessor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaInstanceIdKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaInstanceIdKosmos.kt
new file mode 100644
index 000000000000..923eaa1f3953
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaInstanceIdKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import com.android.internal.logging.InstanceId
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.mediaInstanceId: InstanceId by Kosmos.Fixture { InstanceId.fakeInstanceId(123) }