Loading packages/SystemUI/multivalentTests/src/com/android/systemui/media/remedia/data/repository/MediaRepositoryTest.kt +58 −14 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ 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.media.remedia.shared.model.MediaColorScheme import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat Loading Loading @@ -157,8 +158,14 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) .containsExactly( playingData.toDataModel(underTest.currentMedia[0].controller), remoteData.toDataModel(underTest.currentMedia[1].controller), playingData.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), remoteData.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), ) .inOrder() } Loading @@ -177,8 +184,14 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) .containsExactly( playingData1.toDataModel(underTest.currentMedia[0].controller), playingData2.toDataModel(underTest.currentMedia[1].controller), playingData1.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), playingData2.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), ) .inOrder() Loading @@ -191,8 +204,14 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) .containsExactly( playingData1.toDataModel(underTest.currentMedia[0].controller), playingData2.toDataModel(underTest.currentMedia[1].controller), playingData1.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), playingData2.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), ) .inOrder() Loading @@ -202,8 +221,14 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) .containsExactly( playingData2.toDataModel(underTest.currentMedia[0].controller), playingData1.toDataModel(underTest.currentMedia[1].controller), playingData2.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), playingData1.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), ) .inOrder() } Loading Loading @@ -238,11 +263,26 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(5) assertThat(underTest.currentMedia) .containsExactly( playingAndLocalData.toDataModel(underTest.currentMedia[0].controller), playingAndRemoteData.toDataModel(underTest.currentMedia[1].controller), stoppedAndRemoteData.toDataModel(underTest.currentMedia[2].controller), stoppedAndLocalData.toDataModel(underTest.currentMedia[3].controller), canResumeData.toDataModel(underTest.currentMedia[4].controller), playingAndLocalData.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), playingAndRemoteData.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), stoppedAndRemoteData.toDataModel( underTest.currentMedia[2].controller, underTest.currentMedia[2].colorScheme, ), stoppedAndLocalData.toDataModel( underTest.currentMedia[3].controller, underTest.currentMedia[3].colorScheme, ), canResumeData.toDataModel( underTest.currentMedia[4].controller, underTest.currentMedia[4].colorScheme, ), ) .inOrder() } Loading Loading @@ -270,7 +310,10 @@ class MediaRepositoryTest : SysuiTestCase() { ) } private fun MediaData.toDataModel(mediaController: MediaController): MediaDataModel { private fun MediaData.toDataModel( mediaController: MediaController, colorScheme: MediaColorScheme?, ): MediaDataModel { return MediaDataModel( instanceId = instanceId, appUid = appUid, Loading @@ -280,6 +323,7 @@ class MediaRepositoryTest : SysuiTestCase() { background = null, title = song.toString(), subtitle = artist.toString(), colorScheme = colorScheme, notificationActions = actions, playbackStateActions = semanticActions, outputDevice = device, Loading packages/SystemUI/src/com/android/systemui/media/remedia/data/model/MediaDataModel.kt +2 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.media.controls.shared.model.MediaButton import com.android.systemui.media.controls.shared.model.MediaDeviceData import com.android.systemui.media.controls.shared.model.MediaNotificationAction import com.android.systemui.media.remedia.shared.model.MediaColorScheme /** Data model representing a media data. */ data class MediaDataModel( Loading @@ -37,6 +38,7 @@ data class MediaDataModel( val background: Icon?, val title: String, val subtitle: String, val colorScheme: MediaColorScheme?, /** List of generic action buttons for the media player, based on notification actions */ val notificationActions: List<MediaNotificationAction>, /** Loading packages/SystemUI/src/com/android/systemui/media/remedia/data/repository/MediaRepository.kt +112 −32 Original line number Diff line number Diff line Loading @@ -16,10 +16,14 @@ package com.android.systemui.media.remedia.data.repository import android.app.WallpaperColors import android.content.Context import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.media.session.MediaController import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import com.android.internal.logging.InstanceId import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon Loading @@ -31,6 +35,9 @@ 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.media.remedia.shared.model.MediaColorScheme import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.res.R import com.android.systemui.util.time.SystemClock import java.util.TreeMap Loading Loading @@ -130,15 +137,9 @@ constructor( systemClock.currentTimeMillis(), instanceId, ) val controller = if (currentModel != null && currentModel.controller.sessionToken == token) { currentModel.controller } else { MediaController(applicationContext, token!!) } applicationScope.launch { val mediaModel = toDataModel(controller) val mediaModel = toDataModel(currentModel) sortedMap[sortKey] = mediaModel var isNewToCurrentMedia = true Loading Loading @@ -173,10 +174,17 @@ constructor( TreeMap(sortedMedia.filter { (keyModel, _) -> keyModel.instanceId != data.instanceId }) } private suspend fun MediaData.toDataModel(controller: MediaController): MediaDataModel { private suspend fun MediaData.toDataModel(currentModel: MediaDataModel?): MediaDataModel { return withContext(backgroundDispatcher) { val controller = if (currentModel != null && currentModel.controller.sessionToken == token) { currentModel.controller } else { MediaController(applicationContext, token!!) } val icon = appIcon?.loadDrawable(applicationContext) val background = artwork?.loadDrawable(applicationContext) return MediaDataModel( MediaDataModel( instanceId = instanceId, appUid = appUid, packageName = packageName, Loading @@ -187,6 +195,7 @@ constructor( background = background?.let { Icon.Loaded(background, null) }, title = song.toString(), subtitle = artist.toString(), colorScheme = getScheme(artwork, packageName), notificationActions = actions, playbackStateActions = semanticActions, outputDevice = device, Loading @@ -199,6 +208,30 @@ constructor( isExplicit = isExplicit, ) } } private suspend fun getScheme( artwork: android.graphics.drawable.Icon?, packageName: String, ): MediaColorScheme? { val wallpaperColors = getWallpaperColor(applicationContext, backgroundDispatcher, artwork) val colorScheme = wallpaperColors?.let { ColorScheme(it, false, Style.CONTENT) } ?: let { val launcherIcon = getAltIcon(packageName) if (launcherIcon is Icon.Loaded) { getColorScheme(launcherIcon.drawable) } else { null } } return colorScheme?.run { MediaColorScheme( Color(colorScheme.materialScheme.getPrimaryFixed()), Color(colorScheme.materialScheme.getOnPrimaryFixed()), ) } } private suspend fun getAltIcon(packageName: String): Icon { return withContext(backgroundDispatcher) { Loading @@ -210,4 +243,51 @@ constructor( } } } /** * This method should be called from a background thread. WallpaperColors.fromBitmap is a * blocking call. */ private suspend fun getWallpaperColor( applicationContext: Context, backgroundDispatcher: CoroutineDispatcher, artworkIcon: android.graphics.drawable.Icon?, ): WallpaperColors? { return withContext(backgroundDispatcher) { artworkIcon?.let { if ( it.type == android.graphics.drawable.Icon.TYPE_BITMAP || it.type == android.graphics.drawable.Icon.TYPE_ADAPTIVE_BITMAP ) { // Avoids extra processing if this is already a valid bitmap it.bitmap.let { artworkBitmap -> if (artworkBitmap.isRecycled) { Log.d(TAG, "Cannot load wallpaper color from a recycled bitmap") null } else { WallpaperColors.fromBitmap(artworkBitmap) } } } else { it.loadDrawable(applicationContext)?.let { artworkDrawable -> WallpaperColors.fromDrawable(artworkDrawable) } } } } } /** Returns [ColorScheme] of media app given its [icon]. */ private fun getColorScheme(icon: Drawable): ColorScheme? { return try { ColorScheme(WallpaperColors.fromDrawable(icon), false, Style.CONTENT) } catch (e: PackageManager.NameNotFoundException) { Log.w(TAG, "Fail to get media app info", e) null } } companion object { private const val TAG = "MediaRepository" } } packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt +2 −2 Original line number Diff line number Diff line Loading @@ -91,8 +91,8 @@ constructor(@Application val applicationContext: Context, val repository: MediaR (it as Icon.Loaded).drawable.toBitmap()?.asImageBitmap() } override val colorScheme: MediaColorScheme get() = TODO("Not yet implemented") override val colorScheme: MediaColorScheme? get() = dataModel.colorScheme override val title: String get() = dataModel.title Loading packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaSessionModel.kt +1 −1 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ interface MediaSessionModel { val background: ImageBitmap? val colorScheme: MediaColorScheme val colorScheme: MediaColorScheme? val title: String Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/media/remedia/data/repository/MediaRepositoryTest.kt +58 −14 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ 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.media.remedia.shared.model.MediaColorScheme import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat Loading Loading @@ -157,8 +158,14 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) .containsExactly( playingData.toDataModel(underTest.currentMedia[0].controller), remoteData.toDataModel(underTest.currentMedia[1].controller), playingData.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), remoteData.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), ) .inOrder() } Loading @@ -177,8 +184,14 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) .containsExactly( playingData1.toDataModel(underTest.currentMedia[0].controller), playingData2.toDataModel(underTest.currentMedia[1].controller), playingData1.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), playingData2.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), ) .inOrder() Loading @@ -191,8 +204,14 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) .containsExactly( playingData1.toDataModel(underTest.currentMedia[0].controller), playingData2.toDataModel(underTest.currentMedia[1].controller), playingData1.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), playingData2.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), ) .inOrder() Loading @@ -202,8 +221,14 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(2) assertThat(underTest.currentMedia) .containsExactly( playingData2.toDataModel(underTest.currentMedia[0].controller), playingData1.toDataModel(underTest.currentMedia[1].controller), playingData2.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), playingData1.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), ) .inOrder() } Loading Loading @@ -238,11 +263,26 @@ class MediaRepositoryTest : SysuiTestCase() { assertThat(underTest.currentMedia.size).isEqualTo(5) assertThat(underTest.currentMedia) .containsExactly( playingAndLocalData.toDataModel(underTest.currentMedia[0].controller), playingAndRemoteData.toDataModel(underTest.currentMedia[1].controller), stoppedAndRemoteData.toDataModel(underTest.currentMedia[2].controller), stoppedAndLocalData.toDataModel(underTest.currentMedia[3].controller), canResumeData.toDataModel(underTest.currentMedia[4].controller), playingAndLocalData.toDataModel( underTest.currentMedia[0].controller, underTest.currentMedia[0].colorScheme, ), playingAndRemoteData.toDataModel( underTest.currentMedia[1].controller, underTest.currentMedia[1].colorScheme, ), stoppedAndRemoteData.toDataModel( underTest.currentMedia[2].controller, underTest.currentMedia[2].colorScheme, ), stoppedAndLocalData.toDataModel( underTest.currentMedia[3].controller, underTest.currentMedia[3].colorScheme, ), canResumeData.toDataModel( underTest.currentMedia[4].controller, underTest.currentMedia[4].colorScheme, ), ) .inOrder() } Loading Loading @@ -270,7 +310,10 @@ class MediaRepositoryTest : SysuiTestCase() { ) } private fun MediaData.toDataModel(mediaController: MediaController): MediaDataModel { private fun MediaData.toDataModel( mediaController: MediaController, colorScheme: MediaColorScheme?, ): MediaDataModel { return MediaDataModel( instanceId = instanceId, appUid = appUid, Loading @@ -280,6 +323,7 @@ class MediaRepositoryTest : SysuiTestCase() { background = null, title = song.toString(), subtitle = artist.toString(), colorScheme = colorScheme, notificationActions = actions, playbackStateActions = semanticActions, outputDevice = device, Loading
packages/SystemUI/src/com/android/systemui/media/remedia/data/model/MediaDataModel.kt +2 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.media.controls.shared.model.MediaButton import com.android.systemui.media.controls.shared.model.MediaDeviceData import com.android.systemui.media.controls.shared.model.MediaNotificationAction import com.android.systemui.media.remedia.shared.model.MediaColorScheme /** Data model representing a media data. */ data class MediaDataModel( Loading @@ -37,6 +38,7 @@ data class MediaDataModel( val background: Icon?, val title: String, val subtitle: String, val colorScheme: MediaColorScheme?, /** List of generic action buttons for the media player, based on notification actions */ val notificationActions: List<MediaNotificationAction>, /** Loading
packages/SystemUI/src/com/android/systemui/media/remedia/data/repository/MediaRepository.kt +112 −32 Original line number Diff line number Diff line Loading @@ -16,10 +16,14 @@ package com.android.systemui.media.remedia.data.repository import android.app.WallpaperColors import android.content.Context import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.media.session.MediaController import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import com.android.internal.logging.InstanceId import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon Loading @@ -31,6 +35,9 @@ 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.media.remedia.shared.model.MediaColorScheme import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.res.R import com.android.systemui.util.time.SystemClock import java.util.TreeMap Loading Loading @@ -130,15 +137,9 @@ constructor( systemClock.currentTimeMillis(), instanceId, ) val controller = if (currentModel != null && currentModel.controller.sessionToken == token) { currentModel.controller } else { MediaController(applicationContext, token!!) } applicationScope.launch { val mediaModel = toDataModel(controller) val mediaModel = toDataModel(currentModel) sortedMap[sortKey] = mediaModel var isNewToCurrentMedia = true Loading Loading @@ -173,10 +174,17 @@ constructor( TreeMap(sortedMedia.filter { (keyModel, _) -> keyModel.instanceId != data.instanceId }) } private suspend fun MediaData.toDataModel(controller: MediaController): MediaDataModel { private suspend fun MediaData.toDataModel(currentModel: MediaDataModel?): MediaDataModel { return withContext(backgroundDispatcher) { val controller = if (currentModel != null && currentModel.controller.sessionToken == token) { currentModel.controller } else { MediaController(applicationContext, token!!) } val icon = appIcon?.loadDrawable(applicationContext) val background = artwork?.loadDrawable(applicationContext) return MediaDataModel( MediaDataModel( instanceId = instanceId, appUid = appUid, packageName = packageName, Loading @@ -187,6 +195,7 @@ constructor( background = background?.let { Icon.Loaded(background, null) }, title = song.toString(), subtitle = artist.toString(), colorScheme = getScheme(artwork, packageName), notificationActions = actions, playbackStateActions = semanticActions, outputDevice = device, Loading @@ -199,6 +208,30 @@ constructor( isExplicit = isExplicit, ) } } private suspend fun getScheme( artwork: android.graphics.drawable.Icon?, packageName: String, ): MediaColorScheme? { val wallpaperColors = getWallpaperColor(applicationContext, backgroundDispatcher, artwork) val colorScheme = wallpaperColors?.let { ColorScheme(it, false, Style.CONTENT) } ?: let { val launcherIcon = getAltIcon(packageName) if (launcherIcon is Icon.Loaded) { getColorScheme(launcherIcon.drawable) } else { null } } return colorScheme?.run { MediaColorScheme( Color(colorScheme.materialScheme.getPrimaryFixed()), Color(colorScheme.materialScheme.getOnPrimaryFixed()), ) } } private suspend fun getAltIcon(packageName: String): Icon { return withContext(backgroundDispatcher) { Loading @@ -210,4 +243,51 @@ constructor( } } } /** * This method should be called from a background thread. WallpaperColors.fromBitmap is a * blocking call. */ private suspend fun getWallpaperColor( applicationContext: Context, backgroundDispatcher: CoroutineDispatcher, artworkIcon: android.graphics.drawable.Icon?, ): WallpaperColors? { return withContext(backgroundDispatcher) { artworkIcon?.let { if ( it.type == android.graphics.drawable.Icon.TYPE_BITMAP || it.type == android.graphics.drawable.Icon.TYPE_ADAPTIVE_BITMAP ) { // Avoids extra processing if this is already a valid bitmap it.bitmap.let { artworkBitmap -> if (artworkBitmap.isRecycled) { Log.d(TAG, "Cannot load wallpaper color from a recycled bitmap") null } else { WallpaperColors.fromBitmap(artworkBitmap) } } } else { it.loadDrawable(applicationContext)?.let { artworkDrawable -> WallpaperColors.fromDrawable(artworkDrawable) } } } } } /** Returns [ColorScheme] of media app given its [icon]. */ private fun getColorScheme(icon: Drawable): ColorScheme? { return try { ColorScheme(WallpaperColors.fromDrawable(icon), false, Style.CONTENT) } catch (e: PackageManager.NameNotFoundException) { Log.w(TAG, "Fail to get media app info", e) null } } companion object { private const val TAG = "MediaRepository" } }
packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt +2 −2 Original line number Diff line number Diff line Loading @@ -91,8 +91,8 @@ constructor(@Application val applicationContext: Context, val repository: MediaR (it as Icon.Loaded).drawable.toBitmap()?.asImageBitmap() } override val colorScheme: MediaColorScheme get() = TODO("Not yet implemented") override val colorScheme: MediaColorScheme? get() = dataModel.colorScheme override val title: String get() = dataModel.title Loading
packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaSessionModel.kt +1 −1 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ interface MediaSessionModel { val background: ImageBitmap? val colorScheme: MediaColorScheme val colorScheme: MediaColorScheme? val title: String Loading