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:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user