Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 90817b7b authored by Michael Mikhail's avatar Michael Mikhail
Browse files

Add color scheme logic

Flag: com.android.systemui.media_controls_in_compose
Bug: 397989775
Test: atest SystemUiRoboTests:MediaRepositoryTest
Change-Id: I30f1e5750fce6f8d97480ae9e5aabf42260ca84e
parent c0e86554
Loading
Loading
Loading
Loading
+58 −14
Original line number Diff line number Diff line
@@ -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
@@ -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()
        }
@@ -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()

@@ -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()

@@ -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()
        }
@@ -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()
        }
@@ -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,
@@ -280,6 +323,7 @@ class MediaRepositoryTest : SysuiTestCase() {
            background = null,
            title = song.toString(),
            subtitle = artist.toString(),
            colorScheme = colorScheme,
            notificationActions = actions,
            playbackStateActions = semanticActions,
            outputDevice = device,
+2 −0
Original line number Diff line number Diff line
@@ -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(
@@ -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>,
    /**
+112 −32
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
@@ -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,
@@ -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,
@@ -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) {
@@ -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"
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ interface MediaSessionModel {

    val background: ImageBitmap?

    val colorScheme: MediaColorScheme
    val colorScheme: MediaColorScheme?

    val title: String

Loading