From f9e74f5fcec9fc45929e7efca704bf7203192fe9 Mon Sep 17 00:00:00 2001 From: Beth Thibodeau Date: Mon, 7 Mar 2022 17:21:47 -0500 Subject: [PATCH] Add fallback media layout for notification actions Adds more button slots which are dynamically shown depending on which actions are supported for the app's media. When PlaybackState actions are available, the large play/pause button will appear, along with prev/next on either side of the progress bar. When Notification actions are used, those buttons will be hidden and up to 5 actions will be shown to the right of the progress bar instead. Also refactored view holder classes and test since both templates have 5 generic action buttons now. Fixes: 218875984 Bug: 218876469 Test: atest MediaControlPanelTest Change-Id: I464834d2eee53d661f2407f6280e2856c96c63ac --- .../res/layout/media_session_view.xml | 111 ++++++--- packages/SystemUI/res/values/dimens.xml | 1 + .../res/xml/media_session_collapsed.xml | 120 ++++++++-- .../res/xml/media_session_expanded.xml | 101 +++++++-- .../systemui/media/MediaControlPanel.java | 80 ++++++- .../com/android/systemui/media/MediaData.kt | 6 +- .../systemui/media/MediaDataManager.kt | 4 +- .../android/systemui/media/MediaViewHolder.kt | 12 + .../systemui/media/PlayerSessionViewHolder.kt | 18 +- .../systemui/media/PlayerViewHolder.kt | 17 -- .../systemui/media/SeekBarViewModel.kt | 2 + .../systemui/media/MediaControlPanelTest.kt | 212 +++++++++++------- .../systemui/media/MediaDataManagerTest.kt | 24 +- 13 files changed, 505 insertions(+), 203 deletions(-) diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 9ad01528686ee..f030f3130be49 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -44,6 +44,7 @@ android:background="@drawable/qs_media_scrim" /> + + + + + android:layout_height="wrap_content" /> + android:layout_height="wrap_content" /> + /> + + + + + + /> @@ -174,12 +190,12 @@ android:id="@+id/media_progress_bar" style="@style/MediaPlayer.ProgressBar" android:layout_width="0dp" - android:layout_height="wrap_content" + android:layout_height="48dp" android:paddingTop="@dimen/qs_media_session_enabled_seekbar_vertical_padding" android:paddingBottom="12dp" android:maxHeight="@dimen/qs_media_enabled_seekbar_height" android:splitTrack="false" - android:layout_marginBottom="0dp" + android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" android:layout_marginStart="0dp" android:layout_marginEnd="0dp" /> @@ -191,29 +207,58 @@ android:layout_height="48dp" android:layout_marginStart="0dp" android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="0dp" + android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" /> + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp"/> + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp" /> + + + + + + 1.5dp 8dp 2dp + 144dp 72dp diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml index c6e18a6f8740c..f00e031163377 100644 --- a/packages/SystemUI/res/xml/media_session_collapsed.xml +++ b/packages/SystemUI/res/xml/media_session_collapsed.xml @@ -18,6 +18,13 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + + + app:layout_constraintStart_toEndOf="@id/media_action_barrier_end" /> + + app:layout_constraintTop_toBottomOf="@id/media_seamless" + app:layout_constraintLeft_toRightOf="@id/media_action_barrier" /> @@ -62,29 +109,70 @@ android:id="@+id/actionNext" android:layout_width="48dp" android:layout_height="48dp" - app:layout_constraintStart_toEndOf="@id/media_progress_bar" - app:layout_constraintEnd_toStartOf="@id/actionStart" + android:layout_marginBottom="@dimen/qs_media_padding" + app:layout_constraintVertical_bias="1" + app:layout_constraintLeft_toRightOf="@id/media_progress_bar" + app:layout_constraintRight_toLeftOf="@id/action0" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/media_seamless" /> - \ No newline at end of file + + + + + + diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml index 18ec7aa4cab4c..10da70447ec00 100644 --- a/packages/SystemUI/res/xml/media_session_expanded.xml +++ b/packages/SystemUI/res/xml/media_session_expanded.xml @@ -27,58 +27,119 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> + + + + app:layout_constraintBottom_toBottomOf="@id/header_artist" /> + + app:layout_constraintTop_toBottomOf="@id/header_artist" + app:layout_constraintHorizontal_chainStyle="spread" /> + app:layout_constraintTop_toBottomOf="@id/header_artist" + app:layout_constraintHorizontal_weight="1" /> + app:layout_constraintTop_toBottomOf="@id/header_artist" /> + app:layout_constraintTop_toBottomOf="@id/header_artist" /> + app:layout_constraintTop_toBottomOf="@id/header_artist" /> - \ No newline at end of file + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 831a606e95b2a..4a5b6f17af842 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -104,6 +104,13 @@ public class MediaControlPanel { R.id.action4 }; + // Buttons to show in small player when using semantic actions + private static final List SEMANTIC_ACTION_IDS = List.of( + R.id.actionPlayPause, + R.id.actionPrev, + R.id.actionNext + ); + private final SeekBarViewModel mSeekBarViewModel; private SeekBarObserver mSeekBarObserver; protected final Executor mBackgroundExecutor; @@ -501,11 +508,11 @@ public class MediaControlPanel { MediaButton semanticActions = data.getSemanticActions(); actionIcons = new ArrayList(); - actionIcons.add(semanticActions.getStartCustom()); + actionIcons.add(semanticActions.getCustom0()); actionIcons.add(semanticActions.getPrevOrCustom()); actionIcons.add(semanticActions.getPlayOrPause()); actionIcons.add(semanticActions.getNextOrCustom()); - actionIcons.add(semanticActions.getEndCustom()); + actionIcons.add(semanticActions.getCustom1()); actionsWhenCollapsed = new ArrayList(); actionsWhenCollapsed.add(1); @@ -562,6 +569,9 @@ public class MediaControlPanel { /** Bind elements specific to PlayerSessionViewHolder */ private void bindSessionPlayer(@NonNull MediaData data, String key) { + ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); + ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); + // Default colors int surfaceColor = mBackgroundColor; int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext, @@ -660,26 +670,68 @@ public class MediaControlPanel { // Media action buttons MediaButton semanticActions = data.getSemanticActions(); + PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder; + ImageButton[] genericButtons = new ImageButton[]{ + sessionHolder.getAction0(), + sessionHolder.getAction1(), + sessionHolder.getAction2(), + sessionHolder.getAction3(), + sessionHolder.getAction4()}; + + ImageButton[] semanticButtons = new ImageButton[]{ + sessionHolder.getActionPlayPause(), + sessionHolder.getActionNext(), + sessionHolder.getActionPrev()}; + if (semanticActions != null) { - PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder; + // Hide all the generic buttons + for (ImageButton b: genericButtons) { + setVisibleAndAlpha(collapsedSet, b.getId(), false); + setVisibleAndAlpha(expandedSet, b.getId(), false); + } // Play/pause button has a background sessionHolder.getActionPlayPause().setBackgroundTintList(accentColorList); setSemanticButton(sessionHolder.getActionPlayPause(), semanticActions.getPlayOrPause(), - ColorStateList.valueOf(textPrimaryInverse)); + ColorStateList.valueOf(textPrimaryInverse), collapsedSet, expandedSet, true); setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(), - textColorList); + textColorList, collapsedSet, expandedSet, true); setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(), - textColorList); - setSemanticButton(sessionHolder.getActionStart(), semanticActions.getStartCustom(), - textColorList); - setSemanticButton(sessionHolder.getActionEnd(), semanticActions.getEndCustom(), - textColorList); + textColorList, collapsedSet, expandedSet, true); + setSemanticButton(sessionHolder.getAction0(), semanticActions.getCustom0(), + textColorList, collapsedSet, expandedSet, false); + setSemanticButton(sessionHolder.getAction1(), semanticActions.getCustom1(), + textColorList, collapsedSet, expandedSet, false); } else { - Log.w(TAG, "Using semantic player, but did not get buttons"); + // Hide all the semantic buttons + for (int id : SEMANTIC_ACTION_IDS) { + setVisibleAndAlpha(collapsedSet, id, false); + setVisibleAndAlpha(expandedSet, id, false); + } + + // Set all the generic buttons + List actionsWhenCollapsed = data.getActionsToShowInCompact(); + List actions = data.getActions(); + int i = 0; + for (; i < actions.size(); i++) { + boolean showInCompact = actionsWhenCollapsed.contains(i); + setSemanticButton(genericButtons[i], actions.get(i), textColorList, collapsedSet, + expandedSet, showInCompact); + } + for (; i < 5; i++) { + // Hide any unused buttons + setSemanticButton(genericButtons[i], null, textColorList, collapsedSet, + expandedSet, false); + } } + // If disabled, set progress bar to INVISIBLE instead of GONE so layout weights still work + boolean seekbarEnabled = mSeekBarViewModel.getEnabled(); + expandedSet.setVisibility(R.id.media_progress_bar, + seekbarEnabled ? ConstraintSet.VISIBLE : ConstraintSet.INVISIBLE); + expandedSet.setAlpha(R.id.media_progress_bar, seekbarEnabled ? 1.0f : 0.0f); + // Long press buttons mMediaViewHolder.getLongPressText().setTextColor(textColorList); mMediaViewHolder.getSettingsText().setTextColor(textColorList); @@ -692,7 +744,8 @@ public class MediaControlPanel { } private void setSemanticButton(final ImageButton button, MediaAction mediaAction, - ColorStateList fgColor) { + ColorStateList fgColor, ConstraintSet collapsedSet, ConstraintSet expandedSet, + boolean showInCompact) { button.setImageTintList(fgColor); if (mediaAction != null) { button.setImageIcon(mediaAction.getIcon()); @@ -716,6 +769,9 @@ public class MediaControlPanel { button.setContentDescription(null); button.setEnabled(false); } + + setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact); + setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null); } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 500e82efdb0a5..4cf6291fe35b5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -149,11 +149,11 @@ data class MediaButton( /** * First custom action space */ - var startCustom: MediaAction? = null, + var custom0: MediaAction? = null, /** - * Last custom action space + * Second custom action space */ - var endCustom: MediaAction? = null + var custom1: MediaAction? = null ) /** State of a media action. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index fab06c288ce1b..9e14fe91f21d5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -752,8 +752,8 @@ class MediaDataManager( null } - actions.startCustom = customActions[customIdx++] - actions.endCustom = customActions[customIdx++] + actions.custom0 = customActions[customIdx++] + actions.custom1 = customActions[customIdx++] } return actions } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt index e57b247da055a..5f606969153ce 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt @@ -60,12 +60,24 @@ abstract class MediaViewHolder constructor(itemView: View) { val settings = itemView.requireViewById(R.id.settings) val settingsText = itemView.requireViewById(R.id.settings_text) + // Action Buttons + val action0 = itemView.requireViewById(R.id.action0) + val action1 = itemView.requireViewById(R.id.action1) + val action2 = itemView.requireViewById(R.id.action2) + val action3 = itemView.requireViewById(R.id.action3) + val action4 = itemView.requireViewById(R.id.action4) + init { (player.background as IlluminationDrawable).let { it.registerLightSource(seamless) it.registerLightSource(cancel) it.registerLightSource(dismiss) it.registerLightSource(settings) + it.registerLightSource(action0) + it.registerLightSource(action1) + it.registerLightSource(action2) + it.registerLightSource(action3) + it.registerLightSource(action4) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt index 87d2cffea2574..6928ebb8bb320 100644 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt @@ -31,16 +31,12 @@ class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHol val actionPlayPause = itemView.requireViewById(R.id.actionPlayPause) val actionNext = itemView.requireViewById(R.id.actionNext) val actionPrev = itemView.requireViewById(R.id.actionPrev) - val actionStart = itemView.requireViewById(R.id.actionStart) - val actionEnd = itemView.requireViewById(R.id.actionEnd) init { (player.background as IlluminationDrawable).let { it.registerLightSource(actionPlayPause) it.registerLightSource(actionNext) it.registerLightSource(actionPrev) - it.registerLightSource(actionStart) - it.registerLightSource(actionEnd) } } @@ -49,8 +45,11 @@ class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHol R.id.actionPlayPause -> actionPlayPause R.id.actionNext -> actionNext R.id.actionPrev -> actionPrev - R.id.actionStart -> actionStart - R.id.actionEnd -> actionEnd + R.id.action0 -> action0 + R.id.action1 -> action1 + R.id.action2 -> action2 + R.id.action3 -> action3 + R.id.action4 -> action4 else -> { throw IllegalArgumentException() } @@ -90,8 +89,11 @@ class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHol R.id.actionPlayPause, R.id.actionNext, R.id.actionPrev, - R.id.actionStart, - R.id.actionEnd, + R.id.action0, + R.id.action1, + R.id.action2, + R.id.action3, + R.id.action4, R.id.icon ) val gutsIds = setOf( diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt index 20b2d4a452f5e..dd3fa89dea669 100644 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt @@ -33,23 +33,6 @@ class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(ite override val elapsedTimeView = itemView.requireViewById(R.id.media_elapsed_time) override val totalTimeView = itemView.requireViewById(R.id.media_total_time) - // Action Buttons - val action0 = itemView.requireViewById(R.id.action0) - val action1 = itemView.requireViewById(R.id.action1) - val action2 = itemView.requireViewById(R.id.action2) - val action3 = itemView.requireViewById(R.id.action3) - val action4 = itemView.requireViewById(R.id.action4) - - init { - (player.background as IlluminationDrawable).let { - it.registerLightSource(action0) - it.registerLightSource(action1) - it.registerLightSource(action2) - it.registerLightSource(action3) - it.registerLightSource(action4) - } - } - override fun getAction(id: Int): ImageButton { return when (id) { R.id.action0 -> action0 diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt index 329dec24a5c34..49cd16175f0a9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -132,6 +132,8 @@ class SeekBarViewModel @Inject constructor( lateinit var logSmartspaceClick: () -> Unit + fun getEnabled() = _data.enabled + /** * Event indicating that the user has started interacting with the seek bar. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 708fc915410cf..c024087a22eaa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -59,6 +59,7 @@ import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever @@ -119,8 +120,6 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var actionPlayPause: ImageButton private lateinit var actionNext: ImageButton private lateinit var actionPrev: ImageButton - private lateinit var actionStart: ImageButton - private lateinit var actionEnd: ImageButton @Mock private lateinit var longPressText: TextView @Mock private lateinit var handler: Handler private lateinit var settings: View @@ -168,6 +167,17 @@ public class MediaControlPanelTest : SysuiTestCase() { cancelText = TextView(context) dismiss = FrameLayout(context) dismissText = TextView(context) + + action0 = ImageButton(context).also { it.setId(R.id.action0) } + action1 = ImageButton(context).also { it.setId(R.id.action1) } + action2 = ImageButton(context).also { it.setId(R.id.action2) } + action3 = ImageButton(context).also { it.setId(R.id.action3) } + action4 = ImageButton(context).also { it.setId(R.id.action4) } + + actionPlayPause = ImageButton(context).also { it.setId(R.id.actionPlayPause) } + actionPrev = ImageButton(context).also { it.setId(R.id.actionPrev) } + actionNext = ImageButton(context).also { it.setId(R.id.actionNext) } + initPlayerHolderMocks() initSessionHolderMocks() @@ -208,90 +218,64 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false) } - /** Mock view holder for the notification player */ - private fun initPlayerHolderMocks() { - whenever(holder.player).thenReturn(view) - whenever(holder.appIcon).thenReturn(appIcon) - whenever(holder.albumView).thenReturn(albumView) - whenever(holder.titleText).thenReturn(titleText) - whenever(holder.artistText).thenReturn(artistText) + /** + * Initialize elements common to both view holders + */ + private fun initMediaViewHolderMocks(viewHolder: MediaViewHolder) { + whenever(viewHolder.player).thenReturn(view) + whenever(viewHolder.appIcon).thenReturn(appIcon) + whenever(viewHolder.albumView).thenReturn(albumView) + whenever(viewHolder.titleText).thenReturn(titleText) + whenever(viewHolder.artistText).thenReturn(artistText) whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java)) - whenever(holder.seamless).thenReturn(seamless) - whenever(holder.seamlessButton).thenReturn(seamlessButton) - whenever(holder.seamlessIcon).thenReturn(seamlessIcon) - whenever(holder.seamlessText).thenReturn(seamlessText) - whenever(holder.seekBar).thenReturn(seekBar) - whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView) - whenever(holder.totalTimeView).thenReturn(totalTimeView) + whenever(viewHolder.seamless).thenReturn(seamless) + whenever(viewHolder.seamlessButton).thenReturn(seamlessButton) + whenever(viewHolder.seamlessIcon).thenReturn(seamlessIcon) + whenever(viewHolder.seamlessText).thenReturn(seamlessText) + whenever(viewHolder.seekBar).thenReturn(seekBar) // Action buttons - action0 = ImageButton(context) - whenever(holder.action0).thenReturn(action0) - whenever(holder.getAction(R.id.action0)).thenReturn(action0) - action1 = ImageButton(context) - whenever(holder.action1).thenReturn(action1) - whenever(holder.getAction(R.id.action1)).thenReturn(action1) - action2 = ImageButton(context) - whenever(holder.action2).thenReturn(action2) - whenever(holder.getAction(R.id.action2)).thenReturn(action2) - action3 = ImageButton(context) - whenever(holder.action3).thenReturn(action3) - whenever(holder.getAction(R.id.action3)).thenReturn(action3) - action4 = ImageButton(context) - whenever(holder.action4).thenReturn(action4) - whenever(holder.getAction(R.id.action4)).thenReturn(action4) + whenever(viewHolder.action0).thenReturn(action0) + whenever(viewHolder.getAction(R.id.action0)).thenReturn(action0) + whenever(viewHolder.action1).thenReturn(action1) + whenever(viewHolder.getAction(R.id.action1)).thenReturn(action1) + whenever(viewHolder.action2).thenReturn(action2) + whenever(viewHolder.getAction(R.id.action2)).thenReturn(action2) + whenever(viewHolder.action3).thenReturn(action3) + whenever(viewHolder.getAction(R.id.action3)).thenReturn(action3) + whenever(viewHolder.action4).thenReturn(action4) + whenever(viewHolder.getAction(R.id.action4)).thenReturn(action4) // Long press menu - whenever(holder.longPressText).thenReturn(longPressText) + whenever(viewHolder.longPressText).thenReturn(longPressText) whenever(longPressText.handler).thenReturn(handler) - whenever(holder.settings).thenReturn(settings) - whenever(holder.settingsText).thenReturn(settingsText) - whenever(holder.cancel).thenReturn(cancel) - whenever(holder.cancelText).thenReturn(cancelText) - whenever(holder.dismiss).thenReturn(dismiss) - whenever(holder.dismissText).thenReturn(dismissText) + whenever(viewHolder.settings).thenReturn(settings) + whenever(viewHolder.settingsText).thenReturn(settingsText) + whenever(viewHolder.cancel).thenReturn(cancel) + whenever(viewHolder.cancelText).thenReturn(cancelText) + whenever(viewHolder.dismiss).thenReturn(dismiss) + whenever(viewHolder.dismissText).thenReturn(dismissText) + } + + /** Mock view holder for the notification player */ + private fun initPlayerHolderMocks() { + initMediaViewHolderMocks(holder) + + whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView) + whenever(holder.totalTimeView).thenReturn(totalTimeView) } /** Mock view holder for session player */ private fun initSessionHolderMocks() { - whenever(sessionHolder.player).thenReturn(view) - whenever(sessionHolder.albumView).thenReturn(albumView) - whenever(sessionHolder.appIcon).thenReturn(appIcon) - whenever(sessionHolder.titleText).thenReturn(titleText) - whenever(sessionHolder.artistText).thenReturn(artistText) - val seamlessBackground = mock(RippleDrawable::class.java) - whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java)) - whenever(sessionHolder.seamless).thenReturn(seamless) - whenever(sessionHolder.seamlessButton).thenReturn(seamlessButton) - whenever(sessionHolder.seamlessIcon).thenReturn(seamlessIcon) - whenever(sessionHolder.seamlessText).thenReturn(seamlessText) - whenever(sessionHolder.seekBar).thenReturn(seekBar) + initMediaViewHolderMocks(sessionHolder) - // Action buttons - actionPlayPause = ImageButton(context) + // Semantic action buttons whenever(sessionHolder.actionPlayPause).thenReturn(actionPlayPause) whenever(sessionHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause) - actionNext = ImageButton(context) whenever(sessionHolder.actionNext).thenReturn(actionNext) whenever(sessionHolder.getAction(R.id.actionNext)).thenReturn(actionNext) - actionPrev = ImageButton(context) whenever(sessionHolder.actionPrev).thenReturn(actionPrev) whenever(sessionHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev) - actionStart = ImageButton(context) - whenever(sessionHolder.actionStart).thenReturn(actionStart) - whenever(sessionHolder.getAction(R.id.actionStart)).thenReturn(actionStart) - actionEnd = ImageButton(context) - whenever(sessionHolder.actionEnd).thenReturn(actionEnd) - whenever(sessionHolder.getAction(R.id.actionEnd)).thenReturn(actionEnd) - - // Long press menu - whenever(sessionHolder.longPressText).thenReturn(longPressText) - whenever(sessionHolder.settings).thenReturn(settings) - whenever(sessionHolder.settingsText).thenReturn(settingsText) - whenever(sessionHolder.cancel).thenReturn(cancel) - whenever(sessionHolder.cancelText).thenReturn(cancelText) - whenever(sessionHolder.dismiss).thenReturn(dismiss) - whenever(sessionHolder.dismissText).thenReturn(dismissText) } @After @@ -316,8 +300,8 @@ public class MediaControlPanelTest : SysuiTestCase() { val semanticActions = MediaButton( playOrPause = MediaAction(icon, Runnable {}, "play"), nextOrCustom = MediaAction(icon, Runnable {}, "next"), - startCustom = MediaAction(icon, null, "custom 1"), - endCustom = MediaAction(icon, null, "custom 2") + custom0 = MediaAction(icon, null, "custom 0"), + custom1 = MediaAction(icon, null, "custom 1") ) val state = mediaData.copy(semanticActions = semanticActions) @@ -325,7 +309,7 @@ public class MediaControlPanelTest : SysuiTestCase() { player.bindPlayer(state, PACKAGE) verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE) - assertThat(action0.contentDescription).isEqualTo("custom 1") + assertThat(action0.contentDescription).isEqualTo("custom 0") assertThat(action0.isEnabled()).isFalse() verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE) @@ -340,7 +324,7 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(action3.contentDescription).isEqualTo("next") verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE) - assertThat(action4.contentDescription).isEqualTo("custom 2") + assertThat(action4.contentDescription).isEqualTo("custom 1") assertThat(action4.isEnabled()).isFalse() } @@ -353,28 +337,96 @@ public class MediaControlPanelTest : SysuiTestCase() { val semanticActions = MediaButton( playOrPause = MediaAction(icon, Runnable {}, "play"), nextOrCustom = MediaAction(icon, Runnable {}, "next"), - startCustom = MediaAction(icon, null, "custom 1"), - endCustom = MediaAction(icon, null, "custom 2") + custom0 = MediaAction(icon, null, "custom 0"), + custom1 = MediaAction(icon, null, "custom 1") ) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION) player.bindPlayer(state, PACKAGE) - assertThat(actionStart.contentDescription).isEqualTo("custom 1") - assertThat(actionStart.isEnabled()).isFalse() - assertThat(actionPrev.isEnabled()).isFalse() assertThat(actionPrev.drawable).isNull() + verify(collapsedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE) assertThat(actionPlayPause.isEnabled()).isTrue() assertThat(actionPlayPause.contentDescription).isEqualTo("play") + verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.VISIBLE) assertThat(actionNext.isEnabled()).isTrue() assertThat(actionNext.contentDescription).isEqualTo("next") + verify(collapsedSet).setVisibility(R.id.actionNext, ConstraintSet.VISIBLE) - assertThat(actionEnd.contentDescription).isEqualTo("custom 2") - assertThat(actionEnd.isEnabled()).isFalse() + // Called twice since these IDs are used as generic buttons + assertThat(action0.contentDescription).isEqualTo("custom 0") + assertThat(action0.isEnabled()).isFalse() + verify(collapsedSet, times(2)).setVisibility(R.id.action0, ConstraintSet.GONE) + + assertThat(action1.contentDescription).isEqualTo("custom 1") + assertThat(action1.isEnabled()).isFalse() + verify(collapsedSet, times(2)).setVisibility(R.id.action1, ConstraintSet.GONE) + + // Verify generic buttons are hidden + verify(collapsedSet).setVisibility(R.id.action2, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.GONE) + + verify(collapsedSet).setVisibility(R.id.action3, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.GONE) + + verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.GONE) + } + + @Test + fun bindNotificationActionsNewLayout() { + whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) + whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true) + + val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) + val actions = listOf( + MediaAction(icon, Runnable {}, "previous"), + MediaAction(icon, Runnable {}, "play"), + MediaAction(icon, null, "next"), + MediaAction(icon, null, "custom 0"), + MediaAction(icon, Runnable {}, "custom 1") + ) + val state = mediaData.copy(actions = actions, + actionsToShowInCompact = listOf(1, 2), + semanticActions = null) + + player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION) + player.bindPlayer(state, PACKAGE) + + // Verify semantic actions are hidden + verify(collapsedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE) + + verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.GONE) + + verify(collapsedSet).setVisibility(R.id.actionNext, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.actionNext, ConstraintSet.GONE) + + // Generic actions all enabled + assertThat(action0.contentDescription).isEqualTo("previous") + assertThat(action0.isEnabled()).isTrue() + verify(collapsedSet).setVisibility(R.id.action0, ConstraintSet.GONE) + + assertThat(action1.contentDescription).isEqualTo("play") + assertThat(action1.isEnabled()).isTrue() + verify(collapsedSet).setVisibility(R.id.action1, ConstraintSet.VISIBLE) + + assertThat(action2.contentDescription).isEqualTo("next") + assertThat(action2.isEnabled()).isFalse() + verify(collapsedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE) + + assertThat(action3.contentDescription).isEqualTo("custom 0") + assertThat(action3.isEnabled()).isFalse() + verify(collapsedSet).setVisibility(R.id.action3, ConstraintSet.GONE) + + assertThat(action4.contentDescription).isEqualTo("custom 1") + assertThat(action4.isEnabled()).isTrue() + verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 021f70e05b326..925ae30e87733 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -659,11 +659,11 @@ class MediaDataManagerTest : SysuiTestCase() { actions.nextOrCustom!!.action!!.run() verify(transportControls).skipToNext() - assertThat(actions.startCustom).isNotNull() - assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0]) + assertThat(actions.custom0).isNotNull() + assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0]) - assertThat(actions.endCustom).isNotNull() - assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1]) + assertThat(actions.custom1).isNotNull() + assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1]) } @Test @@ -697,11 +697,11 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(actions.nextOrCustom).isNotNull() assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1]) - assertThat(actions.startCustom).isNotNull() - assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[2]) + assertThat(actions.custom0).isNotNull() + assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[2]) - assertThat(actions.endCustom).isNotNull() - assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[3]) + assertThat(actions.custom1).isNotNull() + assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[3]) } @Test @@ -737,10 +737,10 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(actions.prevOrCustom).isNull() assertThat(actions.nextOrCustom).isNull() - assertThat(actions.startCustom).isNotNull() - assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0]) + assertThat(actions.custom0).isNotNull() + assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0]) - assertThat(actions.endCustom).isNotNull() - assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1]) + assertThat(actions.custom1).isNotNull() + assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1]) } }