Loading packages/SystemUI/res/layout/media_recommendation_view.xml +20 −0 Original line number Original line Diff line number Diff line Loading @@ -62,4 +62,24 @@ android:textSize="11sp" android:textSize="11sp" android:gravity="center_vertical" android:gravity="center_vertical" android:layout_gravity="bottom"/> android:layout_gravity="bottom"/> <!-- Seek Bar --> <SeekBar android:id="@+id/media_progress_bar" android:layout_width="match_parent" android:layout_height="12dp" android:layout_gravity="bottom" android:maxHeight="@dimen/qs_media_enabled_seekbar_height" android:thumb="@android:color/transparent" android:splitTrack="false" android:clickable="false" android:progressTint="?android:attr/textColorPrimary" android:progressBackgroundTint="?android:attr/textColorTertiary" android:paddingTop="5dp" android:paddingBottom="5dp" android:paddingStart="0dp" android:paddingEnd="0dp" android:layout_marginEnd="@dimen/qs_media_info_spacing" android:layout_marginStart="@dimen/qs_media_info_spacing" android:layout_marginBottom="@dimen/qs_media_info_spacing"/> </merge> </merge> No newline at end of file packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt +9 −0 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.view.LayoutInflater import android.view.View import android.view.View import android.view.ViewGroup import android.view.ViewGroup import android.widget.ImageView import android.widget.ImageView import android.widget.SeekBar import android.widget.TextView import android.widget.TextView import com.android.internal.widget.CachingIconView import com.android.internal.widget.CachingIconView import com.android.systemui.R import com.android.systemui.R Loading @@ -37,6 +38,7 @@ class RecommendationViewHolder private constructor(itemView: View, updatedView: // Recommendation screen // Recommendation screen lateinit var cardIcon: ImageView lateinit var cardIcon: ImageView lateinit var mediaAppIcons: List<CachingIconView> lateinit var mediaAppIcons: List<CachingIconView> lateinit var mediaProgressBars: List<SeekBar> lateinit var cardTitle: TextView lateinit var cardTitle: TextView val mediaCoverContainers = val mediaCoverContainers = Loading Loading @@ -82,6 +84,13 @@ class RecommendationViewHolder private constructor(itemView: View, updatedView: if (updatedView) { if (updatedView) { mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) } mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) } cardTitle = itemView.requireViewById(R.id.media_rec_title) cardTitle = itemView.requireViewById(R.id.media_rec_title) mediaProgressBars = mediaCoverContainers.map { it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply { // Media playback is in the direction of tape, not time, so it stays LTR layoutDirection = View.LAYOUT_DIRECTION_LTR } } } else { } else { cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon) cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon) } } Loading packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +25 −0 Original line number Original line Diff line number Diff line Loading @@ -56,6 +56,7 @@ import android.view.ViewGroup; import android.view.animation.Interpolator; import android.view.animation.Interpolator; import android.widget.ImageButton; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.NonNull; Loading Loading @@ -1366,6 +1367,24 @@ public class MediaControlPanel { hasSubtitle |= !TextUtils.isEmpty(subtitle); hasSubtitle |= !TextUtils.isEmpty(subtitle); TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); subtitleView.setText(subtitle); subtitleView.setText(subtitle); // Set up progress bar if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { SeekBar mediaProgressBar = mRecommendationViewHolder.getMediaProgressBars().get(itemIndex); TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); // show progress bar if the recommended album is played. Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras()); if (progress == null || progress <= 0.0) { mediaProgressBar.setVisibility(View.GONE); mediaSubtitle.setVisibility(View.VISIBLE); } else { mediaProgressBar.setProgress((int) (progress * 100)); mediaProgressBar.setVisibility(View.VISIBLE); mediaSubtitle.setVisibility(View.GONE); } } } } mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS; mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS; Loading Loading @@ -1440,6 +1459,12 @@ public class MediaControlPanel { (title) -> title.setTextColor(textPrimaryColor)); (title) -> title.setTextColor(textPrimaryColor)); mRecommendationViewHolder.getMediaSubtitles().forEach( mRecommendationViewHolder.getMediaSubtitles().forEach( (subtitle) -> subtitle.setTextColor(textSecondaryColor)); (subtitle) -> subtitle.setTextColor(textSecondaryColor)); if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { mRecommendationViewHolder.getMediaProgressBars().forEach( (progressBar) -> progressBar.setProgressTintList( ColorStateList.valueOf(textPrimaryColor)) ); } mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme); mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme); } } Loading packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +70 −0 Original line number Original line Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.widget.TextView import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData import androidx.media.utils.MediaConstants import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.logging.InstanceId import com.android.internal.widget.CachingIconView import com.android.internal.widget.CachingIconView Loading Loading @@ -206,6 +207,12 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var coverContainer3: ViewGroup @Mock private lateinit var coverContainer3: ViewGroup @Mock private lateinit var recAppIconItem: CachingIconView @Mock private lateinit var recAppIconItem: CachingIconView @Mock private lateinit var recCardTitle: TextView @Mock private lateinit var recCardTitle: TextView @Mock private lateinit var recProgressBar1: SeekBar @Mock private lateinit var recProgressBar2: SeekBar @Mock private lateinit var recProgressBar3: SeekBar @Mock private lateinit var recSubtitleMock1: TextView @Mock private lateinit var recSubtitleMock2: TextView @Mock private lateinit var recSubtitleMock3: TextView @Mock private lateinit var coverItem: ImageView @Mock private lateinit var coverItem: ImageView private lateinit var coverItem1: ImageView private lateinit var coverItem1: ImageView private lateinit var coverItem2: ImageView private lateinit var coverItem2: ImageView Loading Loading @@ -2081,6 +2088,10 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) whenever(recommendationViewHolder.mediaCoverItems) whenever(recommendationViewHolder.mediaCoverItems) .thenReturn(listOf(coverItem, coverItem, coverItem)) .thenReturn(listOf(coverItem, coverItem, coverItem)) whenever(recommendationViewHolder.mediaProgressBars) .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3)) whenever(recommendationViewHolder.mediaSubtitles) .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3)) val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) val canvas = Canvas(bmp) val canvas = Canvas(bmp) Loading Loading @@ -2118,6 +2129,65 @@ public class MediaControlPanelTest : SysuiTestCase() { verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java)) verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java)) } } @Test fun bindRecommendationWithProgressBars() { fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true) whenever(recommendationViewHolder.mediaAppIcons) .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem)) whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) whenever(recommendationViewHolder.mediaCoverItems) .thenReturn(listOf(coverItem, coverItem, coverItem)) whenever(recommendationViewHolder.mediaProgressBars) .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3)) whenever(recommendationViewHolder.mediaSubtitles) .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3)) val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) val canvas = Canvas(bmp) canvas.drawColor(Color.RED) val albumArt = Icon.createWithBitmap(bmp) val bundle = Bundle().apply { putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED ) putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.5) } val data = smartspaceData.copy( recommendations = listOf( SmartspaceAction.Builder("id1", "title1") .setSubtitle("subtitle1") .setIcon(albumArt) .setExtras(bundle) .build(), SmartspaceAction.Builder("id2", "title2") .setSubtitle("subtitle1") .setIcon(albumArt) .setExtras(Bundle.EMPTY) .build(), SmartspaceAction.Builder("id3", "title3") .setSubtitle("subtitle1") .setIcon(albumArt) .setExtras(Bundle.EMPTY) .build() ) ) player.attachRecommendation(recommendationViewHolder) player.bindRecommendation(data) verify(recProgressBar1).setProgress(50) verify(recProgressBar1).visibility = View.VISIBLE verify(recProgressBar2).visibility = View.GONE verify(recProgressBar3).visibility = View.GONE verify(recSubtitleMock1).visibility = View.GONE verify(recSubtitleMock2).visibility = View.VISIBLE verify(recSubtitleMock3).visibility = View.VISIBLE } @Test @Test fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() { fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() { fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true) fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true) Loading Loading
packages/SystemUI/res/layout/media_recommendation_view.xml +20 −0 Original line number Original line Diff line number Diff line Loading @@ -62,4 +62,24 @@ android:textSize="11sp" android:textSize="11sp" android:gravity="center_vertical" android:gravity="center_vertical" android:layout_gravity="bottom"/> android:layout_gravity="bottom"/> <!-- Seek Bar --> <SeekBar android:id="@+id/media_progress_bar" android:layout_width="match_parent" android:layout_height="12dp" android:layout_gravity="bottom" android:maxHeight="@dimen/qs_media_enabled_seekbar_height" android:thumb="@android:color/transparent" android:splitTrack="false" android:clickable="false" android:progressTint="?android:attr/textColorPrimary" android:progressBackgroundTint="?android:attr/textColorTertiary" android:paddingTop="5dp" android:paddingBottom="5dp" android:paddingStart="0dp" android:paddingEnd="0dp" android:layout_marginEnd="@dimen/qs_media_info_spacing" android:layout_marginStart="@dimen/qs_media_info_spacing" android:layout_marginBottom="@dimen/qs_media_info_spacing"/> </merge> </merge> No newline at end of file
packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt +9 −0 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.view.LayoutInflater import android.view.View import android.view.View import android.view.ViewGroup import android.view.ViewGroup import android.widget.ImageView import android.widget.ImageView import android.widget.SeekBar import android.widget.TextView import android.widget.TextView import com.android.internal.widget.CachingIconView import com.android.internal.widget.CachingIconView import com.android.systemui.R import com.android.systemui.R Loading @@ -37,6 +38,7 @@ class RecommendationViewHolder private constructor(itemView: View, updatedView: // Recommendation screen // Recommendation screen lateinit var cardIcon: ImageView lateinit var cardIcon: ImageView lateinit var mediaAppIcons: List<CachingIconView> lateinit var mediaAppIcons: List<CachingIconView> lateinit var mediaProgressBars: List<SeekBar> lateinit var cardTitle: TextView lateinit var cardTitle: TextView val mediaCoverContainers = val mediaCoverContainers = Loading Loading @@ -82,6 +84,13 @@ class RecommendationViewHolder private constructor(itemView: View, updatedView: if (updatedView) { if (updatedView) { mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) } mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) } cardTitle = itemView.requireViewById(R.id.media_rec_title) cardTitle = itemView.requireViewById(R.id.media_rec_title) mediaProgressBars = mediaCoverContainers.map { it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply { // Media playback is in the direction of tape, not time, so it stays LTR layoutDirection = View.LAYOUT_DIRECTION_LTR } } } else { } else { cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon) cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon) } } Loading
packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +25 −0 Original line number Original line Diff line number Diff line Loading @@ -56,6 +56,7 @@ import android.view.ViewGroup; import android.view.animation.Interpolator; import android.view.animation.Interpolator; import android.widget.ImageButton; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.NonNull; Loading Loading @@ -1366,6 +1367,24 @@ public class MediaControlPanel { hasSubtitle |= !TextUtils.isEmpty(subtitle); hasSubtitle |= !TextUtils.isEmpty(subtitle); TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); subtitleView.setText(subtitle); subtitleView.setText(subtitle); // Set up progress bar if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { SeekBar mediaProgressBar = mRecommendationViewHolder.getMediaProgressBars().get(itemIndex); TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex); // show progress bar if the recommended album is played. Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras()); if (progress == null || progress <= 0.0) { mediaProgressBar.setVisibility(View.GONE); mediaSubtitle.setVisibility(View.VISIBLE); } else { mediaProgressBar.setProgress((int) (progress * 100)); mediaProgressBar.setVisibility(View.VISIBLE); mediaSubtitle.setVisibility(View.GONE); } } } } mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS; mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS; Loading Loading @@ -1440,6 +1459,12 @@ public class MediaControlPanel { (title) -> title.setTextColor(textPrimaryColor)); (title) -> title.setTextColor(textPrimaryColor)); mRecommendationViewHolder.getMediaSubtitles().forEach( mRecommendationViewHolder.getMediaSubtitles().forEach( (subtitle) -> subtitle.setTextColor(textSecondaryColor)); (subtitle) -> subtitle.setTextColor(textSecondaryColor)); if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) { mRecommendationViewHolder.getMediaProgressBars().forEach( (progressBar) -> progressBar.setProgressTintList( ColorStateList.valueOf(textPrimaryColor)) ); } mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme); mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme); } } Loading
packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +70 −0 Original line number Original line Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.widget.TextView import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData import androidx.media.utils.MediaConstants import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.logging.InstanceId import com.android.internal.widget.CachingIconView import com.android.internal.widget.CachingIconView Loading Loading @@ -206,6 +207,12 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var coverContainer3: ViewGroup @Mock private lateinit var coverContainer3: ViewGroup @Mock private lateinit var recAppIconItem: CachingIconView @Mock private lateinit var recAppIconItem: CachingIconView @Mock private lateinit var recCardTitle: TextView @Mock private lateinit var recCardTitle: TextView @Mock private lateinit var recProgressBar1: SeekBar @Mock private lateinit var recProgressBar2: SeekBar @Mock private lateinit var recProgressBar3: SeekBar @Mock private lateinit var recSubtitleMock1: TextView @Mock private lateinit var recSubtitleMock2: TextView @Mock private lateinit var recSubtitleMock3: TextView @Mock private lateinit var coverItem: ImageView @Mock private lateinit var coverItem: ImageView private lateinit var coverItem1: ImageView private lateinit var coverItem1: ImageView private lateinit var coverItem2: ImageView private lateinit var coverItem2: ImageView Loading Loading @@ -2081,6 +2088,10 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) whenever(recommendationViewHolder.mediaCoverItems) whenever(recommendationViewHolder.mediaCoverItems) .thenReturn(listOf(coverItem, coverItem, coverItem)) .thenReturn(listOf(coverItem, coverItem, coverItem)) whenever(recommendationViewHolder.mediaProgressBars) .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3)) whenever(recommendationViewHolder.mediaSubtitles) .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3)) val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) val canvas = Canvas(bmp) val canvas = Canvas(bmp) Loading Loading @@ -2118,6 +2129,65 @@ public class MediaControlPanelTest : SysuiTestCase() { verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java)) verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java)) } } @Test fun bindRecommendationWithProgressBars() { fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true) whenever(recommendationViewHolder.mediaAppIcons) .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem)) whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle) whenever(recommendationViewHolder.mediaCoverItems) .thenReturn(listOf(coverItem, coverItem, coverItem)) whenever(recommendationViewHolder.mediaProgressBars) .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3)) whenever(recommendationViewHolder.mediaSubtitles) .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3)) val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) val canvas = Canvas(bmp) canvas.drawColor(Color.RED) val albumArt = Icon.createWithBitmap(bmp) val bundle = Bundle().apply { putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED ) putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.5) } val data = smartspaceData.copy( recommendations = listOf( SmartspaceAction.Builder("id1", "title1") .setSubtitle("subtitle1") .setIcon(albumArt) .setExtras(bundle) .build(), SmartspaceAction.Builder("id2", "title2") .setSubtitle("subtitle1") .setIcon(albumArt) .setExtras(Bundle.EMPTY) .build(), SmartspaceAction.Builder("id3", "title3") .setSubtitle("subtitle1") .setIcon(albumArt) .setExtras(Bundle.EMPTY) .build() ) ) player.attachRecommendation(recommendationViewHolder) player.bindRecommendation(data) verify(recProgressBar1).setProgress(50) verify(recProgressBar1).visibility = View.VISIBLE verify(recProgressBar2).visibility = View.GONE verify(recProgressBar3).visibility = View.GONE verify(recSubtitleMock1).visibility = View.GONE verify(recSubtitleMock2).visibility = View.VISIBLE verify(recSubtitleMock3).visibility = View.VISIBLE } @Test @Test fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() { fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() { fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true) fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true) Loading