diff options
5 files changed, 159 insertions, 85 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 1b6d1d5825da..c9e888edcb5b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -837,7 +837,14 @@ public class MediaControlPanel { scrubbingTimeViewsEnabled(semanticActions) && hideWhenScrubbing && mIsScrubbing; boolean visible = mediaAction != null && !shouldBeHiddenDueToScrubbing; - setVisibleAndAlpha(expandedSet, buttonId, visible); + int notVisibleValue; + if ((buttonId == R.id.actionPrev && semanticActions.getReservePrev()) + || (buttonId == R.id.actionNext && semanticActions.getReserveNext())) { + notVisibleValue = ConstraintSet.INVISIBLE; + } else { + notVisibleValue = ConstraintSet.GONE; + } + setVisibleAndAlpha(expandedSet, buttonId, visible, notVisibleValue); setVisibleAndAlpha(collapsedSet, buttonId, visible && showInCompact); } @@ -1177,7 +1184,12 @@ public class MediaControlPanel { } private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) { - set.setVisibility(actionId, visible ? ConstraintSet.VISIBLE : ConstraintSet.GONE); + setVisibleAndAlpha(set, actionId, visible, ConstraintSet.GONE); + } + + private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible, + int notVisibleValue) { + set.setVisibility(actionId, visible ? ConstraintSet.VISIBLE : notVisibleValue); set.setAlpha(actionId, visible ? 1.0f : 0.0f); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index bc8cca55154d..f6d531b5b9d6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -149,23 +149,31 @@ data class MediaButton( /** * Play/pause button */ - var playOrPause: MediaAction? = null, + val playOrPause: MediaAction? = null, /** * Next button, or custom action */ - var nextOrCustom: MediaAction? = null, + val nextOrCustom: MediaAction? = null, /** * Previous button, or custom action */ - var prevOrCustom: MediaAction? = null, + val prevOrCustom: MediaAction? = null, /** * First custom action space */ - var custom0: MediaAction? = null, + val custom0: MediaAction? = null, /** * Second custom action space */ - var custom1: MediaAction? = null + val custom1: MediaAction? = null, + /** + * Whether to reserve the empty space when the nextOrCustom is null + */ + val reserveNext: Boolean = false, + /** + * Whether to reserve the empty space when the prevOrCustom is null + */ + val reservePrev: Boolean = false ) { fun getActionById(id: Int): MediaAction? { return when (id) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 0ad15facee66..0d65514bddc2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -173,10 +173,6 @@ class MediaDataManager( // Maximum number of actions allowed in expanded view @JvmField val MAX_NOTIFICATION_ACTIONS = MediaViewHolder.genericButtonIds.size - - /** Maximum number of [PlaybackState.CustomAction] buttons supported */ - @JvmField - val MAX_CUSTOM_ACTIONS = 4 } private val themeText = com.android.settingslib.Utils.getColorAttr(context, @@ -795,71 +791,74 @@ class MediaDataManager( */ private fun createActionsFromState(packageName: String, controller: MediaController): MediaButton? { - val actions = MediaButton() - controller.playbackState?.let { state -> - // First, check for standard actions - actions.playOrPause = if (isConnectingState(state.state)) { - // Spinner needs to be animating to render anything. Start it here. - val drawable = context.getDrawable( - com.android.internal.R.drawable.progress_small_material) - (drawable as Animatable).start() - MediaAction( - drawable, - null, // no action to perform when clicked - context.getString(R.string.controls_media_button_connecting), - context.getDrawable(R.drawable.ic_media_connecting_container), - // Specify a rebind id to prevent the spinner from restarting on later binds. - com.android.internal.R.drawable.progress_small_material - ) - } else if (isPlayingState(state.state)) { - getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE) - } else { - getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY) - } - val prevButton = getStandardAction(controller, state.actions, - PlaybackState.ACTION_SKIP_TO_PREVIOUS) - val nextButton = getStandardAction(controller, state.actions, - PlaybackState.ACTION_SKIP_TO_NEXT) - - // Then, check for custom actions - val customActions = MutableList<MediaAction?>(MAX_CUSTOM_ACTIONS) { null } - var customCount = 0 - for (i in 0..(MAX_CUSTOM_ACTIONS - 1)) { - getCustomAction(state, packageName, controller, customCount)?.let { - customActions[customCount++] = it - } - } - - // Finally, assign the remaining button slots: play/pause A B C D - // A = previous, else custom action (if not reserved) - // B = next, else custom action (if not reserved) - // C and D are always custom actions - val reservePrev = controller.extras?.getBoolean( - MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV) == true - val reserveNext = controller.extras?.getBoolean( - MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT) == true - var customIdx = 0 - - actions.prevOrCustom = if (prevButton != null) { - prevButton - } else if (!reservePrev) { - customActions[customIdx++] - } else { - null - } - - actions.nextOrCustom = if (nextButton != null) { - nextButton - } else if (!reserveNext) { - customActions[customIdx++] - } else { - null - } + val state = controller.playbackState + if (state == null) { + return MediaButton() + } + // First, check for} standard actions + val playOrPause = if (isConnectingState(state.state)) { + // Spinner needs to be animating to render anything. Start it here. + val drawable = context.getDrawable( + com.android.internal.R.drawable.progress_small_material) + (drawable as Animatable).start() + MediaAction( + drawable, + null, // no action to perform when clicked + context.getString(R.string.controls_media_button_connecting), + context.getDrawable(R.drawable.ic_media_connecting_container), + // Specify a rebind id to prevent the spinner from restarting on later binds. + com.android.internal.R.drawable.progress_small_material + ) + } else if (isPlayingState(state.state)) { + getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE) + } else { + getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY) + } + val prevButton = getStandardAction(controller, state.actions, + PlaybackState.ACTION_SKIP_TO_PREVIOUS) + val nextButton = getStandardAction(controller, state.actions, + PlaybackState.ACTION_SKIP_TO_NEXT) + + // Then, create a way to build any custom actions that will be needed + val customActions = state.customActions.asSequence().filterNotNull().map { + getCustomAction(state, packageName, controller, it) + }.iterator() + fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null + + // Finally, assign the remaining button slots: play/pause A B C D + // A = previous, else custom action (if not reserved) + // B = next, else custom action (if not reserved) + // C and D are always custom actions + val reservePrev = controller.extras?.getBoolean( + MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV) == true + val reserveNext = controller.extras?.getBoolean( + MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT) == true + + val prevOrCustom = if (prevButton != null) { + prevButton + } else if (!reservePrev) { + nextCustomAction() + } else { + null + } - actions.custom0 = customActions[customIdx++] - actions.custom1 = customActions[customIdx++] + val nextOrCustom = if (nextButton != null) { + nextButton + } else if (!reserveNext) { + nextCustomAction() + } else { + null } - return actions + + return MediaButton( + playOrPause, + nextOrCustom, + prevOrCustom, + nextCustomAction(), + nextCustomAction(), + reserveNext, + reservePrev + ) } /** @@ -938,18 +937,12 @@ class MediaDataManager( state: PlaybackState, packageName: String, controller: MediaController, - index: Int - ): MediaAction? { - if (state.customActions.size <= index || state.customActions[index] == null) { - if (DEBUG) { Log.d(TAG, "not enough actions or action was null at $index") } - return null - } - - val it = state.customActions[index] + customAction: PlaybackState.CustomAction + ): MediaAction { return MediaAction( - Icon.createWithResource(packageName, it.icon).loadDrawable(context), - { controller.transportControls.sendCustomAction(it, it.extras) }, - it.name, + Icon.createWithResource(packageName, customAction.icon).loadDrawable(context), + { controller.transportControls.sendCustomAction(customAction, customAction.extras) }, + customAction.name, null ) } 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 a39ae6c231bd..1bc8881cd12c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -449,6 +449,64 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test + fun bindSemanticActions_reservedPrev() { + val icon = context.getDrawable(android.R.drawable.ic_media_play) + val bg = context.getDrawable(R.drawable.qs_media_round_button_background) + + // Setup button state: no prev or next button and their slots reserved + val semanticActions = MediaButton( + playOrPause = MediaAction(icon, Runnable {}, "play", bg), + nextOrCustom = null, + prevOrCustom = null, + custom0 = MediaAction(icon, null, "custom 0", bg), + custom1 = MediaAction(icon, null, "custom 1", bg), + false, + true + ) + val state = mediaData.copy(semanticActions = semanticActions) + + player.attachPlayer(viewHolder) + player.bindPlayer(state, PACKAGE) + + assertThat(actionPrev.isEnabled()).isFalse() + assertThat(actionPrev.drawable).isNull() + verify(expandedSet).setVisibility(R.id.actionPrev, ConstraintSet.INVISIBLE) + + assertThat(actionNext.isEnabled()).isFalse() + assertThat(actionNext.drawable).isNull() + verify(expandedSet).setVisibility(R.id.actionNext, ConstraintSet.GONE) + } + + @Test + fun bindSemanticActions_reservedNext() { + val icon = context.getDrawable(android.R.drawable.ic_media_play) + val bg = context.getDrawable(R.drawable.qs_media_round_button_background) + + // Setup button state: no prev or next button and their slots reserved + val semanticActions = MediaButton( + playOrPause = MediaAction(icon, Runnable {}, "play", bg), + nextOrCustom = null, + prevOrCustom = null, + custom0 = MediaAction(icon, null, "custom 0", bg), + custom1 = MediaAction(icon, null, "custom 1", bg), + true, + false + ) + val state = mediaData.copy(semanticActions = semanticActions) + + player.attachPlayer(viewHolder) + player.bindPlayer(state, PACKAGE) + + assertThat(actionPrev.isEnabled()).isFalse() + assertThat(actionPrev.drawable).isNull() + verify(expandedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE) + + assertThat(actionNext.isEnabled()).isFalse() + assertThat(actionNext.drawable).isNull() + verify(expandedSet).setVisibility(R.id.actionNext, ConstraintSet.INVISIBLE) + } + + @Test fun bind_seekBarDisabled_seekBarVisibilityIsSetToInvisible() { whenever(seekBarViewModel.getEnabled()).thenReturn(false) 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 858249960a6e..7ec31a7ae829 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -838,6 +838,9 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(actions.custom1).isNotNull() assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1]) + + assertThat(actions.reserveNext).isTrue() + assertThat(actions.reservePrev).isTrue() } @Test |