Loading packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +27 −14 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewM import com.android.systemui.media.controls.ui.viewmodel.MediaPlayerViewModel import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.surfaceeffects.ripple.MultiRippleView Loading @@ -66,6 +67,8 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext private const val TAG = "MediaControlViewBinder" object MediaControlViewBinder { fun bind( Loading Loading @@ -234,9 +237,6 @@ object MediaControlViewBinder { } } setDismissible(model.isDismissEnabled) setTextPrimaryColor(model.textPrimaryColor) setAccentPrimaryColor(model.accentPrimaryColor) setSurfaceColor(model.surfaceColor) } } Loading Loading @@ -423,22 +423,37 @@ object MediaControlViewBinder { val width = viewController.widthInSceneContainerPx val height = viewController.heightInSceneContainerPx withContext(backgroundDispatcher) { val wallpaperColors = MediaArtworkHelper.getWallpaperColor( viewHolder.albumView.context, backgroundDispatcher, viewModel.backgroundCover, TAG, ) val isArtworkBound = wallpaperColors != null val scheme = wallpaperColors?.let { ColorScheme(it, true, Style.CONTENT) } ?: let { if (viewModel.launcherIcon is Icon.Loaded) { MediaArtworkHelper.getColorScheme(viewModel.launcherIcon.drawable, TAG) } else { null } } val artwork = if (viewModel.shouldAddGradient) { wallpaperColors?.let { addGradientToPlayerAlbum( viewHolder.albumView.context, viewModel.backgroundCover!!, viewModel.colorScheme, scheme!!, width, height, ) } else { ColorDrawable(Color.TRANSPARENT) } } ?: ColorDrawable(Color.TRANSPARENT) withContext(mainDispatcher) { // Transition Colors to current color scheme val colorSchemeChanged = viewController.colorSchemeTransition.updateColorScheme(viewModel.colorScheme) viewController.colorSchemeTransition.updateColorScheme(scheme) val albumView = viewHolder.albumView // Set up width of album view constraint. Loading @@ -449,7 +464,7 @@ object MediaControlViewBinder { if ( updateBackground || colorSchemeChanged || (!viewController.isArtworkBound && viewModel.shouldAddGradient) (!viewController.isArtworkBound && isArtworkBound) ) { viewController.prevArtwork?.let { // Since we throw away the last transition, this will pop if your Loading @@ -464,12 +479,10 @@ object MediaControlViewBinder { transitionDrawable.isCrossFadeEnabled = true albumView.setImageDrawable(transitionDrawable) transitionDrawable.startTransition( if (viewModel.shouldAddGradient) 333 else 80 ) transitionDrawable.startTransition(if (isArtworkBound) 333 else 80) } ?: albumView.setImageDrawable(artwork) } viewController.isArtworkBound = viewModel.shouldAddGradient viewController.isArtworkBound = isArtworkBound viewController.prevArtwork = artwork if (viewModel.useGrayColorFilter) { Loading packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt +197 −29 Original line number Diff line number Diff line Loading @@ -16,13 +16,24 @@ package com.android.systemui.media.controls.ui.binder import android.app.WallpaperColors import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Color import android.graphics.Matrix import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.Icon import android.graphics.drawable.LayerDrawable import android.os.Trace import android.util.TypedValue import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope Loading @@ -30,17 +41,29 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.animation.Expandable import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS import com.android.systemui.media.controls.ui.animation.surfaceFromScheme import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme import com.android.systemui.media.controls.ui.animation.textSecondaryFromScheme import com.android.systemui.media.controls.ui.controller.MediaViewController import com.android.systemui.media.controls.ui.util.MediaArtworkHelper import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.MediaRecViewModel import com.android.systemui.media.controls.ui.viewmodel.MediaRecommendationsViewModel import com.android.systemui.media.controls.ui.viewmodel.MediaRecsCardViewModel import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.util.animation.TransitionLayout import kotlin.math.min import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext private const val TAG = "MediaRecommendationsViewBinder" private const val MEDIA_REC_SCRIM_START_ALPHA = 0.15f private const val MEDIA_REC_SCRIM_END_ALPHA = 1.0f object MediaRecommendationsViewBinder { Loading @@ -50,6 +73,8 @@ object MediaRecommendationsViewBinder { viewModel: MediaRecommendationsViewModel, mediaViewController: MediaViewController, falsingManager: FalsingManager, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) { mediaViewController.recsConfigurationChangeListener = this::updateRecommendationsVisibility val cardView = viewHolder.recommendations Loading @@ -59,7 +84,14 @@ object MediaRecommendationsViewBinder { launch { viewModel.mediaRecsCard.collectLatest { viewModel -> viewModel?.let { bindRecsCard(viewHolder, it, mediaViewController, falsingManager) bindRecsCard( viewHolder, it, mediaViewController, falsingManager, backgroundDispatcher, mainDispatcher, ) } } } Loading @@ -68,19 +100,19 @@ object MediaRecommendationsViewBinder { } } fun bindRecsCard( suspend fun bindRecsCard( viewHolder: RecommendationViewHolder, viewModel: MediaRecsCardViewModel, viewController: MediaViewController, falsingManager: FalsingManager, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) { // Set up media control location and its listener. viewModel.onLocationChanged(viewController.currentEndLocation) viewController.locationChangeListener = viewModel.onLocationChanged // Bind main card. viewHolder.cardTitle.setTextColor(viewModel.cardTitleColor) viewHolder.recommendations.backgroundTintList = ColorStateList.valueOf(viewModel.cardColor) viewHolder.recommendations.contentDescription = viewModel.contentDescription.invoke(viewController.isGutsVisible) Loading @@ -100,8 +132,17 @@ object MediaRecommendationsViewBinder { return@setOnLongClickListener true } // Bind colors val appIcon = viewModel.mediaRecs.first().appIcon fetchAndUpdateColors(viewHolder, appIcon, backgroundDispatcher, mainDispatcher) // Bind all recommendations. bindRecommendationsList(viewHolder, viewModel.mediaRecs, falsingManager) bindRecommendationsList( viewHolder, viewModel.mediaRecs, falsingManager, backgroundDispatcher, mainDispatcher, ) updateRecommendationsVisibility(viewController, viewHolder.recommendations) // Set visibility of recommendations. Loading Loading @@ -153,26 +194,21 @@ object MediaRecommendationsViewBinder { } gutsViewHolder.setDismissible(gutsViewModel.isDismissEnabled) gutsViewHolder.setTextPrimaryColor(gutsViewModel.textPrimaryColor) gutsViewHolder.setAccentPrimaryColor(gutsViewModel.accentPrimaryColor) gutsViewHolder.setSurfaceColor(gutsViewModel.surfaceColor) } private fun bindRecommendationsList( private suspend fun bindRecommendationsList( viewHolder: RecommendationViewHolder, mediaRecs: List<MediaRecViewModel>, falsingManager: FalsingManager falsingManager: FalsingManager, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) { mediaRecs.forEachIndexed { index, mediaRecViewModel -> if (index >= NUM_REQUIRED_RECOMMENDATIONS) return@forEachIndexed val appIconView = viewHolder.mediaAppIcons[index] appIconView.clearColorFilter() if (mediaRecViewModel.appIcon != null) { appIconView.setImageDrawable(mediaRecViewModel.appIcon) } else { appIconView.setImageResource(R.drawable.ic_music_note) } val mediaCoverContainer = viewHolder.mediaCoverContainers[index] mediaCoverContainer.setOnClickListener { Loading @@ -187,29 +223,24 @@ object MediaRecommendationsViewBinder { } val mediaCover = viewHolder.mediaCoverItems[index] val width: Int = mediaCover.context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) val height: Int = mediaCover.context.resources.getDimensionPixelSize( R.dimen.qs_media_rec_album_height_expanded bindRecommendationArtwork( mediaCover.context, viewHolder, mediaRecViewModel, index, backgroundDispatcher, mainDispatcher, ) val coverMatrix = Matrix(mediaCover.imageMatrix) coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height) mediaCover.imageMatrix = coverMatrix mediaCover.setImageDrawable(mediaRecViewModel.albumIcon) mediaCover.contentDescription = mediaRecViewModel.contentDescription val title = viewHolder.mediaTitles[index] title.text = mediaRecViewModel.title title.setTextColor(ColorStateList.valueOf(mediaRecViewModel.titleColor)) val subtitle = viewHolder.mediaSubtitles[index] subtitle.text = mediaRecViewModel.subtitle subtitle.setTextColor(ColorStateList.valueOf(mediaRecViewModel.subtitleColor)) val progressBar = viewHolder.mediaProgressBars[index] progressBar.progress = mediaRecViewModel.progress progressBar.progressTintList = ColorStateList.valueOf(mediaRecViewModel.progressColor) if (mediaRecViewModel.progress == 0) { progressBar.visibility = View.GONE } Loading Loading @@ -291,11 +322,148 @@ object MediaRecommendationsViewBinder { TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, displayAvailableDpWidth.toFloat(), res.displayMetrics res.displayMetrics, ) .toInt() displayAvailableWidth / recCoverWidth } return min(fittedNum.toDouble(), NUM_REQUIRED_RECOMMENDATIONS.toDouble()).toInt() } private suspend fun bindRecommendationArtwork( context: Context, viewHolder: RecommendationViewHolder, viewModel: MediaRecViewModel, index: Int, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) { val traceCookie = viewHolder.hashCode() val traceName = "MediaRecommendationsViewBinder#bindRecommendationArtwork" Trace.beginAsyncSection(traceName, traceCookie) // Capture width & height from views in foreground for artwork scaling in background val width = context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) val height = context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_height_expanded) withContext(backgroundDispatcher) { val artwork = getRecCoverBackground( context, viewModel.albumIcon, width, height, backgroundDispatcher, ) withContext(mainDispatcher) { val mediaCover = viewHolder.mediaCoverItems[index] val coverMatrix = Matrix(mediaCover.imageMatrix) coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height) mediaCover.imageMatrix = coverMatrix mediaCover.setImageDrawable(artwork) } } } /** Returns the recommendation album cover of [width]x[height] size. */ private suspend fun getRecCoverBackground( context: Context, icon: Icon?, width: Int, height: Int, backgroundDispatcher: CoroutineDispatcher, ): Drawable = withContext(backgroundDispatcher) { return@withContext MediaArtworkHelper.getWallpaperColor( context, backgroundDispatcher, icon, TAG, ) ?.let { wallpaperColors -> addGradientToRecommendationAlbum( context, icon!!, ColorScheme(wallpaperColors, true, Style.CONTENT), width, height, ) } ?: ColorDrawable(Color.TRANSPARENT) } private fun addGradientToRecommendationAlbum( context: Context, artworkIcon: Icon, mutableColorScheme: ColorScheme, width: Int, height: Int, ): LayerDrawable { // First try scaling rec card using bitmap drawable. // If returns null, set drawable bounds. val albumArt = getScaledRecommendationCover(context, artworkIcon, width, height) ?: MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height) val gradient = AppCompatResources.getDrawable(context, R.drawable.qs_media_rec_scrim)?.mutate() as GradientDrawable return MediaArtworkHelper.setUpGradientColorOnDrawable( albumArt, gradient, mutableColorScheme, MEDIA_REC_SCRIM_START_ALPHA, MEDIA_REC_SCRIM_END_ALPHA, ) } /** Returns a [Drawable] of a given [artworkIcon] scaled to [width]x[height] size, . */ private fun getScaledRecommendationCover( context: Context, artworkIcon: Icon, width: Int, height: Int, ): Drawable? { check(width > 0) { "Width must be a positive number but was $width" } check(height > 0) { "Height must be a positive number but was $height" } return if ( artworkIcon.type == Icon.TYPE_BITMAP || artworkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP ) { artworkIcon.bitmap?.let { val bitmap = Bitmap.createScaledBitmap(it, width, height, false) BitmapDrawable(context.resources, bitmap) } } else { null } } private suspend fun fetchAndUpdateColors( viewHolder: RecommendationViewHolder, appIcon: Drawable, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) = withContext(backgroundDispatcher) { val colorScheme = ColorScheme(WallpaperColors.fromDrawable(appIcon), /* darkTheme= */ true) withContext(mainDispatcher) { val backgroundColor = surfaceFromScheme(colorScheme) val textPrimaryColor = textPrimaryFromScheme(colorScheme) val textSecondaryColor = textSecondaryFromScheme(colorScheme) viewHolder.cardTitle.setTextColor(textPrimaryColor) viewHolder.recommendations.setBackgroundTintList( ColorStateList.valueOf(backgroundColor) ) viewHolder.mediaTitles.forEach { it.setTextColor(textPrimaryColor) } viewHolder.mediaSubtitles.forEach { it.setTextColor(textSecondaryColor) } viewHolder.mediaProgressBars.forEach { it.progressTintList = ColorStateList.valueOf(textPrimaryColor) } viewHolder.gutsViewHolder.setColors(colorScheme) } } } packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +2 −0 Original line number Diff line number Diff line Loading @@ -768,6 +768,8 @@ constructor( commonViewModel.recsViewModel, viewController, falsingManager, backgroundDispatcher, mainDispatcher, ) mediaContent.addView(viewHolder.recommendations, position) controllerById[commonViewModel.key] = viewController Loading packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt +4 −11 Original line number Diff line number Diff line Loading @@ -87,26 +87,19 @@ object MediaArtworkHelper { gradient: GradientDrawable, colorScheme: ColorScheme, startAlpha: Float, endAlpha: Float endAlpha: Float, ): LayerDrawable { gradient.colors = intArrayOf( getColorWithAlpha(backgroundStartFromScheme(colorScheme), startAlpha), getColorWithAlpha(backgroundEndFromScheme(colorScheme), endAlpha) getColorWithAlpha(backgroundEndFromScheme(colorScheme), endAlpha), ) return LayerDrawable(arrayOf(albumArt, gradient)) } /** Returns [ColorScheme] of media app given its [packageName]. */ fun getColorScheme( applicationContext: Context, packageName: String, tag: String, style: Style = Style.TONAL_SPOT ): ColorScheme? { /** Returns [ColorScheme] of media app given its [icon]. */ fun getColorScheme(icon: Drawable, tag: String, style: Style = Style.TONAL_SPOT): ColorScheme? { return try { // Set up media source app's logo. val icon = applicationContext.packageManager.getApplicationIcon(packageName) ColorScheme(WallpaperColors.fromDrawable(icon), true, style) } catch (e: PackageManager.NameNotFoundException) { Log.w(tag, "Fail to get media app info", e) Loading packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt +0 −4 Original line number Diff line number Diff line Loading @@ -16,15 +16,11 @@ package com.android.systemui.media.controls.ui.viewmodel import android.annotation.ColorInt import android.graphics.drawable.Drawable /** Models UI state for media guts menu */ data class GutsViewModel( val gutsText: CharSequence, @ColorInt val textPrimaryColor: Int, @ColorInt val accentPrimaryColor: Int, @ColorInt val surfaceColor: Int, val isDismissEnabled: Boolean = true, val onDismissClicked: () -> Unit, val cancelTextBackground: Drawable?, Loading Loading
packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +27 −14 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewM import com.android.systemui.media.controls.ui.viewmodel.MediaPlayerViewModel import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.surfaceeffects.ripple.MultiRippleView Loading @@ -66,6 +67,8 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext private const val TAG = "MediaControlViewBinder" object MediaControlViewBinder { fun bind( Loading Loading @@ -234,9 +237,6 @@ object MediaControlViewBinder { } } setDismissible(model.isDismissEnabled) setTextPrimaryColor(model.textPrimaryColor) setAccentPrimaryColor(model.accentPrimaryColor) setSurfaceColor(model.surfaceColor) } } Loading Loading @@ -423,22 +423,37 @@ object MediaControlViewBinder { val width = viewController.widthInSceneContainerPx val height = viewController.heightInSceneContainerPx withContext(backgroundDispatcher) { val wallpaperColors = MediaArtworkHelper.getWallpaperColor( viewHolder.albumView.context, backgroundDispatcher, viewModel.backgroundCover, TAG, ) val isArtworkBound = wallpaperColors != null val scheme = wallpaperColors?.let { ColorScheme(it, true, Style.CONTENT) } ?: let { if (viewModel.launcherIcon is Icon.Loaded) { MediaArtworkHelper.getColorScheme(viewModel.launcherIcon.drawable, TAG) } else { null } } val artwork = if (viewModel.shouldAddGradient) { wallpaperColors?.let { addGradientToPlayerAlbum( viewHolder.albumView.context, viewModel.backgroundCover!!, viewModel.colorScheme, scheme!!, width, height, ) } else { ColorDrawable(Color.TRANSPARENT) } } ?: ColorDrawable(Color.TRANSPARENT) withContext(mainDispatcher) { // Transition Colors to current color scheme val colorSchemeChanged = viewController.colorSchemeTransition.updateColorScheme(viewModel.colorScheme) viewController.colorSchemeTransition.updateColorScheme(scheme) val albumView = viewHolder.albumView // Set up width of album view constraint. Loading @@ -449,7 +464,7 @@ object MediaControlViewBinder { if ( updateBackground || colorSchemeChanged || (!viewController.isArtworkBound && viewModel.shouldAddGradient) (!viewController.isArtworkBound && isArtworkBound) ) { viewController.prevArtwork?.let { // Since we throw away the last transition, this will pop if your Loading @@ -464,12 +479,10 @@ object MediaControlViewBinder { transitionDrawable.isCrossFadeEnabled = true albumView.setImageDrawable(transitionDrawable) transitionDrawable.startTransition( if (viewModel.shouldAddGradient) 333 else 80 ) transitionDrawable.startTransition(if (isArtworkBound) 333 else 80) } ?: albumView.setImageDrawable(artwork) } viewController.isArtworkBound = viewModel.shouldAddGradient viewController.isArtworkBound = isArtworkBound viewController.prevArtwork = artwork if (viewModel.useGrayColorFilter) { Loading
packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt +197 −29 Original line number Diff line number Diff line Loading @@ -16,13 +16,24 @@ package com.android.systemui.media.controls.ui.binder import android.app.WallpaperColors import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Color import android.graphics.Matrix import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.Icon import android.graphics.drawable.LayerDrawable import android.os.Trace import android.util.TypedValue import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope Loading @@ -30,17 +41,29 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.animation.Expandable import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS import com.android.systemui.media.controls.ui.animation.surfaceFromScheme import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme import com.android.systemui.media.controls.ui.animation.textSecondaryFromScheme import com.android.systemui.media.controls.ui.controller.MediaViewController import com.android.systemui.media.controls.ui.util.MediaArtworkHelper import com.android.systemui.media.controls.ui.view.RecommendationViewHolder import com.android.systemui.media.controls.ui.viewmodel.MediaRecViewModel import com.android.systemui.media.controls.ui.viewmodel.MediaRecommendationsViewModel import com.android.systemui.media.controls.ui.viewmodel.MediaRecsCardViewModel import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.util.animation.TransitionLayout import kotlin.math.min import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext private const val TAG = "MediaRecommendationsViewBinder" private const val MEDIA_REC_SCRIM_START_ALPHA = 0.15f private const val MEDIA_REC_SCRIM_END_ALPHA = 1.0f object MediaRecommendationsViewBinder { Loading @@ -50,6 +73,8 @@ object MediaRecommendationsViewBinder { viewModel: MediaRecommendationsViewModel, mediaViewController: MediaViewController, falsingManager: FalsingManager, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) { mediaViewController.recsConfigurationChangeListener = this::updateRecommendationsVisibility val cardView = viewHolder.recommendations Loading @@ -59,7 +84,14 @@ object MediaRecommendationsViewBinder { launch { viewModel.mediaRecsCard.collectLatest { viewModel -> viewModel?.let { bindRecsCard(viewHolder, it, mediaViewController, falsingManager) bindRecsCard( viewHolder, it, mediaViewController, falsingManager, backgroundDispatcher, mainDispatcher, ) } } } Loading @@ -68,19 +100,19 @@ object MediaRecommendationsViewBinder { } } fun bindRecsCard( suspend fun bindRecsCard( viewHolder: RecommendationViewHolder, viewModel: MediaRecsCardViewModel, viewController: MediaViewController, falsingManager: FalsingManager, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) { // Set up media control location and its listener. viewModel.onLocationChanged(viewController.currentEndLocation) viewController.locationChangeListener = viewModel.onLocationChanged // Bind main card. viewHolder.cardTitle.setTextColor(viewModel.cardTitleColor) viewHolder.recommendations.backgroundTintList = ColorStateList.valueOf(viewModel.cardColor) viewHolder.recommendations.contentDescription = viewModel.contentDescription.invoke(viewController.isGutsVisible) Loading @@ -100,8 +132,17 @@ object MediaRecommendationsViewBinder { return@setOnLongClickListener true } // Bind colors val appIcon = viewModel.mediaRecs.first().appIcon fetchAndUpdateColors(viewHolder, appIcon, backgroundDispatcher, mainDispatcher) // Bind all recommendations. bindRecommendationsList(viewHolder, viewModel.mediaRecs, falsingManager) bindRecommendationsList( viewHolder, viewModel.mediaRecs, falsingManager, backgroundDispatcher, mainDispatcher, ) updateRecommendationsVisibility(viewController, viewHolder.recommendations) // Set visibility of recommendations. Loading Loading @@ -153,26 +194,21 @@ object MediaRecommendationsViewBinder { } gutsViewHolder.setDismissible(gutsViewModel.isDismissEnabled) gutsViewHolder.setTextPrimaryColor(gutsViewModel.textPrimaryColor) gutsViewHolder.setAccentPrimaryColor(gutsViewModel.accentPrimaryColor) gutsViewHolder.setSurfaceColor(gutsViewModel.surfaceColor) } private fun bindRecommendationsList( private suspend fun bindRecommendationsList( viewHolder: RecommendationViewHolder, mediaRecs: List<MediaRecViewModel>, falsingManager: FalsingManager falsingManager: FalsingManager, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) { mediaRecs.forEachIndexed { index, mediaRecViewModel -> if (index >= NUM_REQUIRED_RECOMMENDATIONS) return@forEachIndexed val appIconView = viewHolder.mediaAppIcons[index] appIconView.clearColorFilter() if (mediaRecViewModel.appIcon != null) { appIconView.setImageDrawable(mediaRecViewModel.appIcon) } else { appIconView.setImageResource(R.drawable.ic_music_note) } val mediaCoverContainer = viewHolder.mediaCoverContainers[index] mediaCoverContainer.setOnClickListener { Loading @@ -187,29 +223,24 @@ object MediaRecommendationsViewBinder { } val mediaCover = viewHolder.mediaCoverItems[index] val width: Int = mediaCover.context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) val height: Int = mediaCover.context.resources.getDimensionPixelSize( R.dimen.qs_media_rec_album_height_expanded bindRecommendationArtwork( mediaCover.context, viewHolder, mediaRecViewModel, index, backgroundDispatcher, mainDispatcher, ) val coverMatrix = Matrix(mediaCover.imageMatrix) coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height) mediaCover.imageMatrix = coverMatrix mediaCover.setImageDrawable(mediaRecViewModel.albumIcon) mediaCover.contentDescription = mediaRecViewModel.contentDescription val title = viewHolder.mediaTitles[index] title.text = mediaRecViewModel.title title.setTextColor(ColorStateList.valueOf(mediaRecViewModel.titleColor)) val subtitle = viewHolder.mediaSubtitles[index] subtitle.text = mediaRecViewModel.subtitle subtitle.setTextColor(ColorStateList.valueOf(mediaRecViewModel.subtitleColor)) val progressBar = viewHolder.mediaProgressBars[index] progressBar.progress = mediaRecViewModel.progress progressBar.progressTintList = ColorStateList.valueOf(mediaRecViewModel.progressColor) if (mediaRecViewModel.progress == 0) { progressBar.visibility = View.GONE } Loading Loading @@ -291,11 +322,148 @@ object MediaRecommendationsViewBinder { TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, displayAvailableDpWidth.toFloat(), res.displayMetrics res.displayMetrics, ) .toInt() displayAvailableWidth / recCoverWidth } return min(fittedNum.toDouble(), NUM_REQUIRED_RECOMMENDATIONS.toDouble()).toInt() } private suspend fun bindRecommendationArtwork( context: Context, viewHolder: RecommendationViewHolder, viewModel: MediaRecViewModel, index: Int, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) { val traceCookie = viewHolder.hashCode() val traceName = "MediaRecommendationsViewBinder#bindRecommendationArtwork" Trace.beginAsyncSection(traceName, traceCookie) // Capture width & height from views in foreground for artwork scaling in background val width = context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) val height = context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_height_expanded) withContext(backgroundDispatcher) { val artwork = getRecCoverBackground( context, viewModel.albumIcon, width, height, backgroundDispatcher, ) withContext(mainDispatcher) { val mediaCover = viewHolder.mediaCoverItems[index] val coverMatrix = Matrix(mediaCover.imageMatrix) coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height) mediaCover.imageMatrix = coverMatrix mediaCover.setImageDrawable(artwork) } } } /** Returns the recommendation album cover of [width]x[height] size. */ private suspend fun getRecCoverBackground( context: Context, icon: Icon?, width: Int, height: Int, backgroundDispatcher: CoroutineDispatcher, ): Drawable = withContext(backgroundDispatcher) { return@withContext MediaArtworkHelper.getWallpaperColor( context, backgroundDispatcher, icon, TAG, ) ?.let { wallpaperColors -> addGradientToRecommendationAlbum( context, icon!!, ColorScheme(wallpaperColors, true, Style.CONTENT), width, height, ) } ?: ColorDrawable(Color.TRANSPARENT) } private fun addGradientToRecommendationAlbum( context: Context, artworkIcon: Icon, mutableColorScheme: ColorScheme, width: Int, height: Int, ): LayerDrawable { // First try scaling rec card using bitmap drawable. // If returns null, set drawable bounds. val albumArt = getScaledRecommendationCover(context, artworkIcon, width, height) ?: MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height) val gradient = AppCompatResources.getDrawable(context, R.drawable.qs_media_rec_scrim)?.mutate() as GradientDrawable return MediaArtworkHelper.setUpGradientColorOnDrawable( albumArt, gradient, mutableColorScheme, MEDIA_REC_SCRIM_START_ALPHA, MEDIA_REC_SCRIM_END_ALPHA, ) } /** Returns a [Drawable] of a given [artworkIcon] scaled to [width]x[height] size, . */ private fun getScaledRecommendationCover( context: Context, artworkIcon: Icon, width: Int, height: Int, ): Drawable? { check(width > 0) { "Width must be a positive number but was $width" } check(height > 0) { "Height must be a positive number but was $height" } return if ( artworkIcon.type == Icon.TYPE_BITMAP || artworkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP ) { artworkIcon.bitmap?.let { val bitmap = Bitmap.createScaledBitmap(it, width, height, false) BitmapDrawable(context.resources, bitmap) } } else { null } } private suspend fun fetchAndUpdateColors( viewHolder: RecommendationViewHolder, appIcon: Drawable, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, ) = withContext(backgroundDispatcher) { val colorScheme = ColorScheme(WallpaperColors.fromDrawable(appIcon), /* darkTheme= */ true) withContext(mainDispatcher) { val backgroundColor = surfaceFromScheme(colorScheme) val textPrimaryColor = textPrimaryFromScheme(colorScheme) val textSecondaryColor = textSecondaryFromScheme(colorScheme) viewHolder.cardTitle.setTextColor(textPrimaryColor) viewHolder.recommendations.setBackgroundTintList( ColorStateList.valueOf(backgroundColor) ) viewHolder.mediaTitles.forEach { it.setTextColor(textPrimaryColor) } viewHolder.mediaSubtitles.forEach { it.setTextColor(textSecondaryColor) } viewHolder.mediaProgressBars.forEach { it.progressTintList = ColorStateList.valueOf(textPrimaryColor) } viewHolder.gutsViewHolder.setColors(colorScheme) } } }
packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +2 −0 Original line number Diff line number Diff line Loading @@ -768,6 +768,8 @@ constructor( commonViewModel.recsViewModel, viewController, falsingManager, backgroundDispatcher, mainDispatcher, ) mediaContent.addView(viewHolder.recommendations, position) controllerById[commonViewModel.key] = viewController Loading
packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt +4 −11 Original line number Diff line number Diff line Loading @@ -87,26 +87,19 @@ object MediaArtworkHelper { gradient: GradientDrawable, colorScheme: ColorScheme, startAlpha: Float, endAlpha: Float endAlpha: Float, ): LayerDrawable { gradient.colors = intArrayOf( getColorWithAlpha(backgroundStartFromScheme(colorScheme), startAlpha), getColorWithAlpha(backgroundEndFromScheme(colorScheme), endAlpha) getColorWithAlpha(backgroundEndFromScheme(colorScheme), endAlpha), ) return LayerDrawable(arrayOf(albumArt, gradient)) } /** Returns [ColorScheme] of media app given its [packageName]. */ fun getColorScheme( applicationContext: Context, packageName: String, tag: String, style: Style = Style.TONAL_SPOT ): ColorScheme? { /** Returns [ColorScheme] of media app given its [icon]. */ fun getColorScheme(icon: Drawable, tag: String, style: Style = Style.TONAL_SPOT): ColorScheme? { return try { // Set up media source app's logo. val icon = applicationContext.packageManager.getApplicationIcon(packageName) ColorScheme(WallpaperColors.fromDrawable(icon), true, style) } catch (e: PackageManager.NameNotFoundException) { Log.w(tag, "Fail to get media app info", e) Loading
packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt +0 −4 Original line number Diff line number Diff line Loading @@ -16,15 +16,11 @@ package com.android.systemui.media.controls.ui.viewmodel import android.annotation.ColorInt import android.graphics.drawable.Drawable /** Models UI state for media guts menu */ data class GutsViewModel( val gutsText: CharSequence, @ColorInt val textPrimaryColor: Int, @ColorInt val accentPrimaryColor: Int, @ColorInt val surfaceColor: Int, val isDismissEnabled: Boolean = true, val onDismissClicked: () -> Unit, val cancelTextBackground: Drawable?, Loading