Loading packages/SystemUI/multivalentTests/src/com/android/systemui/media/remedia/data/repository/MediaRepositoryTest.kt +34 −20 Original line number Diff line number Diff line Loading @@ -16,32 +16,43 @@ package com.android.systemui.media.remedia.data.repository import android.content.packageManager import android.media.session.MediaController import android.media.session.MediaSession 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.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.remedia.data.model.MediaDataModel import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyString import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class MediaRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val drawable = context.getDrawable(R.drawable.ic_music_note)!! private val kosmos = testKosmos().apply { whenever(packageManager.getApplicationIcon(anyString())).thenReturn(drawable) context.setMockPackageManager(packageManager) } private val testScope = kosmos.testScope private val session = MediaSession(context, "MediaRepositoryTestSession") Loading @@ -62,11 +73,11 @@ class MediaRepositoryTest : SysuiTestCase() { MediaData() .copy(token = session.sessionToken, active = true, instanceId = instanceId) underTest.addCurrentUserMediaEntry(userMedia) addCurrentUserMediaEntry(userMedia) assertThat(currentUserEntries?.get(instanceId)).isEqualTo(userMedia) underTest.addCurrentUserMediaEntry(userMedia.copy(active = false)) addCurrentUserMediaEntry(userMedia.copy(active = false)) assertThat(currentUserEntries?.get(instanceId)).isNotEqualTo(userMedia) assertThat(currentUserEntries?.get(instanceId)?.active).isFalse() Loading @@ -80,7 +91,7 @@ class MediaRepositoryTest : SysuiTestCase() { val instanceId = InstanceId.fakeInstanceId(123) val userMedia = MediaData().copy(token = session.sessionToken, instanceId = instanceId) underTest.addCurrentUserMediaEntry(userMedia) addCurrentUserMediaEntry(userMedia) assertThat(currentUserEntries?.get(instanceId)).isEqualTo(userMedia) assertThat(underTest.removeCurrentUserMediaEntry(instanceId, userMedia)).isTrue() Loading @@ -94,7 +105,7 @@ class MediaRepositoryTest : SysuiTestCase() { val instanceId = InstanceId.fakeInstanceId(123) val userMedia = MediaData().copy(token = session.sessionToken, instanceId = instanceId) underTest.addCurrentUserMediaEntry(userMedia) addCurrentUserMediaEntry(userMedia) assertThat(currentUserEntries?.get(instanceId)).isEqualTo(userMedia) Loading Loading @@ -140,9 +151,8 @@ class MediaRepositoryTest : SysuiTestCase() { val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId) val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId) underTest.addCurrentUserMediaEntry(playingData) underTest.addCurrentUserMediaEntry(remoteData) runCurrent() addCurrentUserMediaEntry(playingData) addCurrentUserMediaEntry(remoteData) assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) Loading @@ -161,9 +171,8 @@ class MediaRepositoryTest : SysuiTestCase() { var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1) var playingData2 = createMediaData("app2", false, LOCAL, false, playingInstanceId2) underTest.addCurrentUserMediaEntry(playingData1) underTest.addCurrentUserMediaEntry(playingData2) runCurrent() addCurrentUserMediaEntry(playingData1) addCurrentUserMediaEntry(playingData2) assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) Loading @@ -176,9 +185,8 @@ class MediaRepositoryTest : SysuiTestCase() { playingData1 = createMediaData("app1", false, LOCAL, false, playingInstanceId1) playingData2 = createMediaData("app2", true, LOCAL, false, playingInstanceId2) underTest.addCurrentUserMediaEntry(playingData1) underTest.addCurrentUserMediaEntry(playingData2) runCurrent() addCurrentUserMediaEntry(playingData1) addCurrentUserMediaEntry(playingData2) assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) Loading Loading @@ -214,15 +222,15 @@ class MediaRepositoryTest : SysuiTestCase() { val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4) val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5) underTest.addCurrentUserMediaEntry(stoppedAndLocalData) addCurrentUserMediaEntry(stoppedAndLocalData) underTest.addCurrentUserMediaEntry(stoppedAndRemoteData) addCurrentUserMediaEntry(stoppedAndRemoteData) underTest.addCurrentUserMediaEntry(canResumeData) addCurrentUserMediaEntry(canResumeData) underTest.addCurrentUserMediaEntry(playingAndLocalData) addCurrentUserMediaEntry(playingAndLocalData) underTest.addCurrentUserMediaEntry(playingAndRemoteData) addCurrentUserMediaEntry(playingAndRemoteData) underTest.reorderMedia() runCurrent() Loading @@ -239,6 +247,11 @@ class MediaRepositoryTest : SysuiTestCase() { .inOrder() } private fun TestScope.addCurrentUserMediaEntry(data: MediaData) { underTest.addCurrentUserMediaEntry(data) runCurrent() } private fun createMediaData( app: String, playing: Boolean, Loading @@ -248,6 +261,7 @@ class MediaRepositoryTest : SysuiTestCase() { ): MediaData { return MediaData( token = session.sessionToken, packageName = "packageName", playbackLocation = playbackLocation, resumption = isResume, notificationKey = "key: $app", Loading @@ -262,7 +276,7 @@ class MediaRepositoryTest : SysuiTestCase() { appUid = appUid, packageName = packageName, appName = app.toString(), appIcon = null, appIcon = Icon.Loaded(drawable, null), background = null, title = song.toString(), subtitle = artist.toString(), Loading packages/SystemUI/src/com/android/systemui/media/remedia/data/model/MediaDataModel.kt +1 −1 Original line number Diff line number Diff line Loading @@ -33,7 +33,7 @@ data class MediaDataModel( /** Package name of the app that's posting the media, used for logging. */ val packageName: String, val appName: String, val appIcon: Icon?, val appIcon: Icon, val background: Icon?, val title: String, val subtitle: String, Loading packages/SystemUI/src/com/android/systemui/media/remedia/data/repository/MediaRepository.kt +54 −27 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.media.remedia.data.repository import android.content.Context import android.content.pm.PackageManager import android.media.session.MediaController import androidx.compose.runtime.getValue import com.android.internal.logging.InstanceId Loading @@ -24,15 +25,21 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.lifecycle.Activatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.data.model.MediaSortKeyModel import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.remedia.data.model.MediaDataModel import com.android.systemui.res.R import com.android.systemui.util.time.SystemClock import java.util.TreeMap import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** A repository that holds the state of current media on the device. */ interface MediaRepository : Activatable { Loading @@ -49,8 +56,12 @@ interface MediaRepository : Activatable { @SysUISingleton class MediaRepositoryImpl @Inject constructor(@Application private val context: Context, private val systemClock: SystemClock) : MediaRepository, MediaPipelineRepository() { constructor( @Application private val applicationContext: Context, @Application private val applicationScope: CoroutineScope, @Background val backgroundDispatcher: CoroutineDispatcher, private val systemClock: SystemClock, ) : MediaRepository, MediaPipelineRepository() { private val hydrator = Hydrator(traceName = "MediaRepository.hydrator") private val mutableCurrentMedia: MutableStateFlow<List<MediaDataModel>> = Loading Loading @@ -123,8 +134,10 @@ constructor(@Application private val context: Context, private val systemClock: if (currentModel != null && currentModel.controller.sessionToken == token) { currentModel.controller } else { MediaController(context, token!!) MediaController(applicationContext, token!!) } applicationScope.launch { val mediaModel = toDataModel(controller) sortedMap[sortKey] = mediaModel Loading @@ -151,6 +164,7 @@ constructor(@Application private val context: Context, private val systemClock: } } } } private fun removeFromSortedMedia(data: MediaData) { mutableCurrentMedia.value = Loading @@ -159,15 +173,17 @@ constructor(@Application private val context: Context, private val systemClock: TreeMap(sortedMedia.filter { (keyModel, _) -> keyModel.instanceId != data.instanceId }) } private fun MediaData.toDataModel(controller: MediaController): MediaDataModel { val icon = appIcon?.loadDrawable(context) val background = artwork?.loadDrawable(context) private suspend fun MediaData.toDataModel(controller: MediaController): MediaDataModel { val icon = appIcon?.loadDrawable(applicationContext) val background = artwork?.loadDrawable(applicationContext) return MediaDataModel( instanceId = instanceId, appUid = appUid, packageName = packageName, appName = app.toString(), appIcon = icon?.let { Icon.Loaded(it, ContentDescription.Loaded(app)) }, appIcon = icon?.let { Icon.Loaded(it, ContentDescription.Loaded(app)) } ?: getAltIcon(packageName), background = background?.let { Icon.Loaded(background, null) }, title = song.toString(), subtitle = artist.toString(), Loading @@ -183,4 +199,15 @@ constructor(@Application private val context: Context, private val systemClock: isExplicit = isExplicit, ) } private suspend fun getAltIcon(packageName: String): Icon { return withContext(backgroundDispatcher) { try { val icon = applicationContext.packageManager.getApplicationIcon(packageName) Icon.Loaded(icon, null) } catch (exception: PackageManager.NameNotFoundException) { Icon.Resource(R.drawable.ic_music_note, null) } } } } packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt +109 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,23 @@ package com.android.systemui.media.remedia.domain.interactor import android.content.Context import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import com.android.systemui.biometrics.Utils.toBitmap import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.media.remedia.data.model.MediaDataModel import com.android.systemui.media.remedia.data.repository.MediaRepository import com.android.systemui.media.remedia.domain.model.MediaActionModel import com.android.systemui.media.remedia.domain.model.MediaOutputDeviceModel import com.android.systemui.media.remedia.domain.model.MediaSessionModel import com.android.systemui.media.remedia.shared.model.MediaCardActionButtonLayout import com.android.systemui.media.remedia.shared.model.MediaColorScheme import com.android.systemui.media.remedia.shared.model.MediaSessionState import javax.inject.Inject /** * Defines interface for classes that can provide business logic in the domain of the media controls Loading @@ -36,3 +52,96 @@ interface MediaInteractor { /** Open media settings. */ fun openMediaSettings() } @SysUISingleton class MediaInteractorImpl @Inject constructor(@Application val applicationContext: Context, val repository: MediaRepository) : MediaInteractor, ExclusiveActivatable() { override val sessions: List<MediaSessionModel> get() = repository.currentMedia.map { toMediaSessionModel(it) } override fun seek(sessionKey: Any, to: Long) { TODO("Not yet implemented") } override fun hide(sessionKey: Any) { TODO("Not yet implemented") } override fun openMediaSettings() { TODO("Not yet implemented") } private fun toMediaSessionModel(dataModel: MediaDataModel): MediaSessionModel { return object : MediaSessionModel { override val key get() = dataModel.instanceId override val appName get() = dataModel.appName override val appIcon: Icon get() = dataModel.appIcon override val background: ImageBitmap? get() = dataModel.background?.let { (it as Icon.Loaded).drawable.toBitmap()?.asImageBitmap() } override val colorScheme: MediaColorScheme get() = TODO("Not yet implemented") override val title: String get() = dataModel.title override val subtitle: String get() = dataModel.subtitle override val onClick: () -> Unit get() = TODO("Not yet implemented") override val isActive: Boolean get() = dataModel.isActive override val canBeHidden: Boolean get() = dataModel.canBeDismissed override val canBeScrubbed: Boolean get() = TODO("Not yet implemented") override val state: MediaSessionState get() = TODO("Not yet implemented") override val positionMs: Long get() = TODO("Not yet implemented") override val durationMs: Long get() = TODO("Not yet implemented") override val outputDevice: MediaOutputDeviceModel get() = TODO("Not yet implemented") override val actionButtonLayout: MediaCardActionButtonLayout get() = TODO("Not yet implemented") override val playPauseAction: MediaActionModel get() = TODO("Not yet implemented") override val leftAction: MediaActionModel get() = TODO("Not yet implemented") override val rightAction: MediaActionModel get() = TODO("Not yet implemented") override val additionalActions: List<MediaActionModel.Action> get() = TODO("Not yet implemented") } } override suspend fun onActivated(): Nothing { repository.activate() } } packages/SystemUI/tests/utils/src/com/android/systemui/media/remedia/data/repository/MediaRepositoryKosmos.kt +10 −1 Original line number Diff line number Diff line Loading @@ -18,7 +18,16 @@ package com.android.systemui.media.remedia.data.repository import android.content.applicationContext import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.util.time.systemClock val Kosmos.mediaRepository by Kosmos.Fixture { MediaRepositoryImpl(context = applicationContext, systemClock = systemClock) } Kosmos.Fixture { MediaRepositoryImpl( applicationContext = applicationContext, applicationScope = applicationCoroutineScope, backgroundDispatcher = testDispatcher, systemClock = systemClock, ) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/media/remedia/data/repository/MediaRepositoryTest.kt +34 −20 Original line number Diff line number Diff line Loading @@ -16,32 +16,43 @@ package com.android.systemui.media.remedia.data.repository import android.content.packageManager import android.media.session.MediaController import android.media.session.MediaSession 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.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.remedia.data.model.MediaDataModel import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyString import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class MediaRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val drawable = context.getDrawable(R.drawable.ic_music_note)!! private val kosmos = testKosmos().apply { whenever(packageManager.getApplicationIcon(anyString())).thenReturn(drawable) context.setMockPackageManager(packageManager) } private val testScope = kosmos.testScope private val session = MediaSession(context, "MediaRepositoryTestSession") Loading @@ -62,11 +73,11 @@ class MediaRepositoryTest : SysuiTestCase() { MediaData() .copy(token = session.sessionToken, active = true, instanceId = instanceId) underTest.addCurrentUserMediaEntry(userMedia) addCurrentUserMediaEntry(userMedia) assertThat(currentUserEntries?.get(instanceId)).isEqualTo(userMedia) underTest.addCurrentUserMediaEntry(userMedia.copy(active = false)) addCurrentUserMediaEntry(userMedia.copy(active = false)) assertThat(currentUserEntries?.get(instanceId)).isNotEqualTo(userMedia) assertThat(currentUserEntries?.get(instanceId)?.active).isFalse() Loading @@ -80,7 +91,7 @@ class MediaRepositoryTest : SysuiTestCase() { val instanceId = InstanceId.fakeInstanceId(123) val userMedia = MediaData().copy(token = session.sessionToken, instanceId = instanceId) underTest.addCurrentUserMediaEntry(userMedia) addCurrentUserMediaEntry(userMedia) assertThat(currentUserEntries?.get(instanceId)).isEqualTo(userMedia) assertThat(underTest.removeCurrentUserMediaEntry(instanceId, userMedia)).isTrue() Loading @@ -94,7 +105,7 @@ class MediaRepositoryTest : SysuiTestCase() { val instanceId = InstanceId.fakeInstanceId(123) val userMedia = MediaData().copy(token = session.sessionToken, instanceId = instanceId) underTest.addCurrentUserMediaEntry(userMedia) addCurrentUserMediaEntry(userMedia) assertThat(currentUserEntries?.get(instanceId)).isEqualTo(userMedia) Loading Loading @@ -140,9 +151,8 @@ class MediaRepositoryTest : SysuiTestCase() { val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId) val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId) underTest.addCurrentUserMediaEntry(playingData) underTest.addCurrentUserMediaEntry(remoteData) runCurrent() addCurrentUserMediaEntry(playingData) addCurrentUserMediaEntry(remoteData) assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) Loading @@ -161,9 +171,8 @@ class MediaRepositoryTest : SysuiTestCase() { var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1) var playingData2 = createMediaData("app2", false, LOCAL, false, playingInstanceId2) underTest.addCurrentUserMediaEntry(playingData1) underTest.addCurrentUserMediaEntry(playingData2) runCurrent() addCurrentUserMediaEntry(playingData1) addCurrentUserMediaEntry(playingData2) assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) Loading @@ -176,9 +185,8 @@ class MediaRepositoryTest : SysuiTestCase() { playingData1 = createMediaData("app1", false, LOCAL, false, playingInstanceId1) playingData2 = createMediaData("app2", true, LOCAL, false, playingInstanceId2) underTest.addCurrentUserMediaEntry(playingData1) underTest.addCurrentUserMediaEntry(playingData2) runCurrent() addCurrentUserMediaEntry(playingData1) addCurrentUserMediaEntry(playingData2) assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) Loading Loading @@ -214,15 +222,15 @@ class MediaRepositoryTest : SysuiTestCase() { val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4) val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5) underTest.addCurrentUserMediaEntry(stoppedAndLocalData) addCurrentUserMediaEntry(stoppedAndLocalData) underTest.addCurrentUserMediaEntry(stoppedAndRemoteData) addCurrentUserMediaEntry(stoppedAndRemoteData) underTest.addCurrentUserMediaEntry(canResumeData) addCurrentUserMediaEntry(canResumeData) underTest.addCurrentUserMediaEntry(playingAndLocalData) addCurrentUserMediaEntry(playingAndLocalData) underTest.addCurrentUserMediaEntry(playingAndRemoteData) addCurrentUserMediaEntry(playingAndRemoteData) underTest.reorderMedia() runCurrent() Loading @@ -239,6 +247,11 @@ class MediaRepositoryTest : SysuiTestCase() { .inOrder() } private fun TestScope.addCurrentUserMediaEntry(data: MediaData) { underTest.addCurrentUserMediaEntry(data) runCurrent() } private fun createMediaData( app: String, playing: Boolean, Loading @@ -248,6 +261,7 @@ class MediaRepositoryTest : SysuiTestCase() { ): MediaData { return MediaData( token = session.sessionToken, packageName = "packageName", playbackLocation = playbackLocation, resumption = isResume, notificationKey = "key: $app", Loading @@ -262,7 +276,7 @@ class MediaRepositoryTest : SysuiTestCase() { appUid = appUid, packageName = packageName, appName = app.toString(), appIcon = null, appIcon = Icon.Loaded(drawable, null), background = null, title = song.toString(), subtitle = artist.toString(), Loading
packages/SystemUI/src/com/android/systemui/media/remedia/data/model/MediaDataModel.kt +1 −1 Original line number Diff line number Diff line Loading @@ -33,7 +33,7 @@ data class MediaDataModel( /** Package name of the app that's posting the media, used for logging. */ val packageName: String, val appName: String, val appIcon: Icon?, val appIcon: Icon, val background: Icon?, val title: String, val subtitle: String, Loading
packages/SystemUI/src/com/android/systemui/media/remedia/data/repository/MediaRepository.kt +54 −27 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.media.remedia.data.repository import android.content.Context import android.content.pm.PackageManager import android.media.session.MediaController import androidx.compose.runtime.getValue import com.android.internal.logging.InstanceId Loading @@ -24,15 +25,21 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.lifecycle.Activatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.data.model.MediaSortKeyModel import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.remedia.data.model.MediaDataModel import com.android.systemui.res.R import com.android.systemui.util.time.SystemClock import java.util.TreeMap import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** A repository that holds the state of current media on the device. */ interface MediaRepository : Activatable { Loading @@ -49,8 +56,12 @@ interface MediaRepository : Activatable { @SysUISingleton class MediaRepositoryImpl @Inject constructor(@Application private val context: Context, private val systemClock: SystemClock) : MediaRepository, MediaPipelineRepository() { constructor( @Application private val applicationContext: Context, @Application private val applicationScope: CoroutineScope, @Background val backgroundDispatcher: CoroutineDispatcher, private val systemClock: SystemClock, ) : MediaRepository, MediaPipelineRepository() { private val hydrator = Hydrator(traceName = "MediaRepository.hydrator") private val mutableCurrentMedia: MutableStateFlow<List<MediaDataModel>> = Loading Loading @@ -123,8 +134,10 @@ constructor(@Application private val context: Context, private val systemClock: if (currentModel != null && currentModel.controller.sessionToken == token) { currentModel.controller } else { MediaController(context, token!!) MediaController(applicationContext, token!!) } applicationScope.launch { val mediaModel = toDataModel(controller) sortedMap[sortKey] = mediaModel Loading @@ -151,6 +164,7 @@ constructor(@Application private val context: Context, private val systemClock: } } } } private fun removeFromSortedMedia(data: MediaData) { mutableCurrentMedia.value = Loading @@ -159,15 +173,17 @@ constructor(@Application private val context: Context, private val systemClock: TreeMap(sortedMedia.filter { (keyModel, _) -> keyModel.instanceId != data.instanceId }) } private fun MediaData.toDataModel(controller: MediaController): MediaDataModel { val icon = appIcon?.loadDrawable(context) val background = artwork?.loadDrawable(context) private suspend fun MediaData.toDataModel(controller: MediaController): MediaDataModel { val icon = appIcon?.loadDrawable(applicationContext) val background = artwork?.loadDrawable(applicationContext) return MediaDataModel( instanceId = instanceId, appUid = appUid, packageName = packageName, appName = app.toString(), appIcon = icon?.let { Icon.Loaded(it, ContentDescription.Loaded(app)) }, appIcon = icon?.let { Icon.Loaded(it, ContentDescription.Loaded(app)) } ?: getAltIcon(packageName), background = background?.let { Icon.Loaded(background, null) }, title = song.toString(), subtitle = artist.toString(), Loading @@ -183,4 +199,15 @@ constructor(@Application private val context: Context, private val systemClock: isExplicit = isExplicit, ) } private suspend fun getAltIcon(packageName: String): Icon { return withContext(backgroundDispatcher) { try { val icon = applicationContext.packageManager.getApplicationIcon(packageName) Icon.Loaded(icon, null) } catch (exception: PackageManager.NameNotFoundException) { Icon.Resource(R.drawable.ic_music_note, null) } } } }
packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt +109 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,23 @@ package com.android.systemui.media.remedia.domain.interactor import android.content.Context import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import com.android.systemui.biometrics.Utils.toBitmap import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.media.remedia.data.model.MediaDataModel import com.android.systemui.media.remedia.data.repository.MediaRepository import com.android.systemui.media.remedia.domain.model.MediaActionModel import com.android.systemui.media.remedia.domain.model.MediaOutputDeviceModel import com.android.systemui.media.remedia.domain.model.MediaSessionModel import com.android.systemui.media.remedia.shared.model.MediaCardActionButtonLayout import com.android.systemui.media.remedia.shared.model.MediaColorScheme import com.android.systemui.media.remedia.shared.model.MediaSessionState import javax.inject.Inject /** * Defines interface for classes that can provide business logic in the domain of the media controls Loading @@ -36,3 +52,96 @@ interface MediaInteractor { /** Open media settings. */ fun openMediaSettings() } @SysUISingleton class MediaInteractorImpl @Inject constructor(@Application val applicationContext: Context, val repository: MediaRepository) : MediaInteractor, ExclusiveActivatable() { override val sessions: List<MediaSessionModel> get() = repository.currentMedia.map { toMediaSessionModel(it) } override fun seek(sessionKey: Any, to: Long) { TODO("Not yet implemented") } override fun hide(sessionKey: Any) { TODO("Not yet implemented") } override fun openMediaSettings() { TODO("Not yet implemented") } private fun toMediaSessionModel(dataModel: MediaDataModel): MediaSessionModel { return object : MediaSessionModel { override val key get() = dataModel.instanceId override val appName get() = dataModel.appName override val appIcon: Icon get() = dataModel.appIcon override val background: ImageBitmap? get() = dataModel.background?.let { (it as Icon.Loaded).drawable.toBitmap()?.asImageBitmap() } override val colorScheme: MediaColorScheme get() = TODO("Not yet implemented") override val title: String get() = dataModel.title override val subtitle: String get() = dataModel.subtitle override val onClick: () -> Unit get() = TODO("Not yet implemented") override val isActive: Boolean get() = dataModel.isActive override val canBeHidden: Boolean get() = dataModel.canBeDismissed override val canBeScrubbed: Boolean get() = TODO("Not yet implemented") override val state: MediaSessionState get() = TODO("Not yet implemented") override val positionMs: Long get() = TODO("Not yet implemented") override val durationMs: Long get() = TODO("Not yet implemented") override val outputDevice: MediaOutputDeviceModel get() = TODO("Not yet implemented") override val actionButtonLayout: MediaCardActionButtonLayout get() = TODO("Not yet implemented") override val playPauseAction: MediaActionModel get() = TODO("Not yet implemented") override val leftAction: MediaActionModel get() = TODO("Not yet implemented") override val rightAction: MediaActionModel get() = TODO("Not yet implemented") override val additionalActions: List<MediaActionModel.Action> get() = TODO("Not yet implemented") } } override suspend fun onActivated(): Nothing { repository.activate() } }
packages/SystemUI/tests/utils/src/com/android/systemui/media/remedia/data/repository/MediaRepositoryKosmos.kt +10 −1 Original line number Diff line number Diff line Loading @@ -18,7 +18,16 @@ package com.android.systemui.media.remedia.data.repository import android.content.applicationContext import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.util.time.systemClock val Kosmos.mediaRepository by Kosmos.Fixture { MediaRepositoryImpl(context = applicationContext, systemClock = systemClock) } Kosmos.Fixture { MediaRepositoryImpl( applicationContext = applicationContext, applicationScope = applicationCoroutineScope, backgroundDispatcher = testDispatcher, systemClock = systemClock, ) }