Merge changes from topic "b224749799" into tm-dev

* changes:
  Allow null prev/next buttons to be INVISIBLE instead of GONE
  Remove logging and nullable return type from getCustomAction
This commit is contained in:
Justin Weir
2022-04-14 11:34:00 +00:00
committed by Android (Google) Code Review
5 changed files with 160 additions and 86 deletions

View File

@@ -837,7 +837,14 @@ public class MediaControlPanel {
scrubbingTimeViewsEnabled(semanticActions) && hideWhenScrubbing && mIsScrubbing; scrubbingTimeViewsEnabled(semanticActions) && hideWhenScrubbing && mIsScrubbing;
boolean visible = mediaAction != null && !shouldBeHiddenDueToScrubbing; 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); setVisibleAndAlpha(collapsedSet, buttonId, visible && showInCompact);
} }
@@ -1177,7 +1184,12 @@ public class MediaControlPanel {
} }
private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) { 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); set.setAlpha(actionId, visible ? 1.0f : 0.0f);
} }

View File

@@ -149,23 +149,31 @@ data class MediaButton(
/** /**
* Play/pause button * Play/pause button
*/ */
var playOrPause: MediaAction? = null, val playOrPause: MediaAction? = null,
/** /**
* Next button, or custom action * Next button, or custom action
*/ */
var nextOrCustom: MediaAction? = null, val nextOrCustom: MediaAction? = null,
/** /**
* Previous button, or custom action * Previous button, or custom action
*/ */
var prevOrCustom: MediaAction? = null, val prevOrCustom: MediaAction? = null,
/** /**
* First custom action space * First custom action space
*/ */
var custom0: MediaAction? = null, val custom0: MediaAction? = null,
/** /**
* Second custom action space * 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? { fun getActionById(id: Int): MediaAction? {
return when (id) { return when (id) {

View File

@@ -173,10 +173,6 @@ class MediaDataManager(
// Maximum number of actions allowed in expanded view // Maximum number of actions allowed in expanded view
@JvmField @JvmField
val MAX_NOTIFICATION_ACTIONS = MediaViewHolder.genericButtonIds.size 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, private val themeText = com.android.settingslib.Utils.getColorAttr(context,
@@ -795,71 +791,74 @@ class MediaDataManager(
*/ */
private fun createActionsFromState(packageName: String, controller: MediaController): private fun createActionsFromState(packageName: String, controller: MediaController):
MediaButton? { MediaButton? {
val actions = MediaButton() val state = controller.playbackState
controller.playbackState?.let { state -> if (state == null) {
// First, check for standard actions return MediaButton()
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
}
actions.custom0 = customActions[customIdx++]
actions.custom1 = customActions[customIdx++]
} }
return actions // 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
}
val nextOrCustom = if (nextButton != null) {
nextButton
} else if (!reserveNext) {
nextCustomAction()
} else {
null
}
return MediaButton(
playOrPause,
nextOrCustom,
prevOrCustom,
nextCustomAction(),
nextCustomAction(),
reserveNext,
reservePrev
)
} }
/** /**
@@ -938,18 +937,12 @@ class MediaDataManager(
state: PlaybackState, state: PlaybackState,
packageName: String, packageName: String,
controller: MediaController, controller: MediaController,
index: Int customAction: PlaybackState.CustomAction
): MediaAction? { ): 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]
return MediaAction( return MediaAction(
Icon.createWithResource(packageName, it.icon).loadDrawable(context), Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
{ controller.transportControls.sendCustomAction(it, it.extras) }, { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
it.name, customAction.name,
null null
) )
} }

View File

@@ -448,6 +448,64 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.GONE) verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.GONE)
} }
@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 @Test
fun bind_seekBarDisabled_seekBarVisibilityIsSetToInvisible() { fun bind_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
whenever(seekBarViewModel.getEnabled()).thenReturn(false) whenever(seekBarViewModel.getEnabled()).thenReturn(false)

View File

@@ -838,6 +838,9 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(actions.custom1).isNotNull() assertThat(actions.custom1).isNotNull()
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1]) assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
assertThat(actions.reserveNext).isTrue()
assertThat(actions.reservePrev).isTrue()
} }
@Test @Test