diff options
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) }  |