Merge "Check for progress info from resume controls" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ad6b846cb8
@@ -361,6 +361,9 @@ object Flags {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
|
val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
|
||||||
|
|
||||||
|
// TODO(b/267007629): Tracking Bug
|
||||||
|
val MEDIA_RESUME_PROGRESS = unreleasedFlag(915, "media_resume_progress")
|
||||||
|
|
||||||
// 1000 - dock
|
// 1000 - dock
|
||||||
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
|
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ data class MediaData(
|
|||||||
|
|
||||||
/** Whether explicit indicator exists */
|
/** Whether explicit indicator exists */
|
||||||
val isExplicit: Boolean = false,
|
val isExplicit: Boolean = false,
|
||||||
|
|
||||||
|
/** Track progress (0 - 1) to display for players where [resumption] is true */
|
||||||
|
val resumeProgress: Double? = null,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
/** Media is playing on the local device */
|
/** Media is playing on the local device */
|
||||||
|
|||||||
@@ -237,6 +237,24 @@ constructor(
|
|||||||
checkIfPollingNeeded()
|
checkIfPollingNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the progress to a fixed percentage value that cannot be changed by the user.
|
||||||
|
*
|
||||||
|
* @param percent value between 0 and 1
|
||||||
|
*/
|
||||||
|
fun updateStaticProgress(percent: Double) {
|
||||||
|
val position = (percent * 100).toInt()
|
||||||
|
_data =
|
||||||
|
Progress(
|
||||||
|
enabled = true,
|
||||||
|
seekAvailable = false,
|
||||||
|
playing = false,
|
||||||
|
scrubbing = false,
|
||||||
|
elapsedTime = position,
|
||||||
|
duration = 100,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts the seek bar into a resumption state.
|
* Puts the seek bar into a resumption state.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import com.android.systemui.media.controls.models.recommendation.SmartspaceMedia
|
|||||||
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
|
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
|
||||||
import com.android.systemui.media.controls.resume.MediaResumeListener
|
import com.android.systemui.media.controls.resume.MediaResumeListener
|
||||||
import com.android.systemui.media.controls.util.MediaControllerFactory
|
import com.android.systemui.media.controls.util.MediaControllerFactory
|
||||||
|
import com.android.systemui.media.controls.util.MediaDataUtils
|
||||||
import com.android.systemui.media.controls.util.MediaFlags
|
import com.android.systemui.media.controls.util.MediaFlags
|
||||||
import com.android.systemui.media.controls.util.MediaUiEventLogger
|
import com.android.systemui.media.controls.util.MediaUiEventLogger
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
@@ -667,6 +668,11 @@ class MediaDataManager(
|
|||||||
MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
|
MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
|
||||||
mediaFlags.isExplicitIndicatorEnabled()
|
mediaFlags.isExplicitIndicatorEnabled()
|
||||||
|
|
||||||
|
val progress =
|
||||||
|
if (mediaFlags.isResumeProgressEnabled()) {
|
||||||
|
MediaDataUtils.getDescriptionProgress(desc.extras)
|
||||||
|
} else null
|
||||||
|
|
||||||
val mediaAction = getResumeMediaAction(resumeAction)
|
val mediaAction = getResumeMediaAction(resumeAction)
|
||||||
val lastActive = systemClock.elapsedRealtime()
|
val lastActive = systemClock.elapsedRealtime()
|
||||||
foregroundExecutor.execute {
|
foregroundExecutor.execute {
|
||||||
@@ -697,6 +703,7 @@ class MediaDataManager(
|
|||||||
instanceId = instanceId,
|
instanceId = instanceId,
|
||||||
appUid = appUid,
|
appUid = appUid,
|
||||||
isExplicit = isExplicit,
|
isExplicit = isExplicit,
|
||||||
|
resumeProgress = progress,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,8 +115,6 @@ import com.android.systemui.util.animation.TransitionLayout;
|
|||||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||||
import com.android.systemui.util.time.SystemClock;
|
import com.android.systemui.util.time.SystemClock;
|
||||||
|
|
||||||
import dagger.Lazy;
|
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -124,6 +122,7 @@ import java.util.concurrent.Executor;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
import kotlin.Triple;
|
import kotlin.Triple;
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
|
|
||||||
@@ -523,8 +522,13 @@ public class MediaControlPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Seek Bar
|
// Seek Bar
|
||||||
final MediaController controller = getController();
|
if (data.getResumption() && data.getResumeProgress() != null) {
|
||||||
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
|
double progress = data.getResumeProgress();
|
||||||
|
mSeekBarViewModel.updateStaticProgress(progress);
|
||||||
|
} else {
|
||||||
|
final MediaController controller = getController();
|
||||||
|
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
|
||||||
|
}
|
||||||
|
|
||||||
// Show the broadcast dialog button only when the le audio is enabled.
|
// Show the broadcast dialog button only when the le audio is enabled.
|
||||||
mShowBroadcastDialogButton =
|
mShowBroadcastDialogButton =
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ package com.android.systemui.media.controls.util;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.core.math.MathUtils;
|
||||||
|
import androidx.media.utils.MediaConstants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class with common methods for media controls
|
* Utility class with common methods for media controls
|
||||||
*/
|
*/
|
||||||
@@ -50,4 +54,35 @@ public class MediaDataUtils {
|
|||||||
: unknownName);
|
: unknownName);
|
||||||
return applicationName;
|
return applicationName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the bundle for extras indicating the progress percentage
|
||||||
|
*
|
||||||
|
* @param extras
|
||||||
|
* @return the progress value between 0-1 inclusive if prsent, otherwise null
|
||||||
|
*/
|
||||||
|
public static Double getDescriptionProgress(Bundle extras) {
|
||||||
|
if (!extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS);
|
||||||
|
switch (status) {
|
||||||
|
case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED:
|
||||||
|
return 0.0;
|
||||||
|
case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED:
|
||||||
|
return 1.0;
|
||||||
|
case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED: {
|
||||||
|
if (extras
|
||||||
|
.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE)) {
|
||||||
|
double percent = extras
|
||||||
|
.getDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE);
|
||||||
|
return MathUtils.clamp(percent, 0.0, 1.0);
|
||||||
|
} else {
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,4 +55,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
|
|||||||
/** Check whether we show the updated recommendation card. */
|
/** Check whether we show the updated recommendation card. */
|
||||||
fun isRecommendationCardUpdateEnabled() =
|
fun isRecommendationCardUpdateEnabled() =
|
||||||
featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
|
featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
|
||||||
|
|
||||||
|
/** Check whether to get progress information for resume players */
|
||||||
|
fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
|
|||||||
USER_ID, true, APP, null, ARTIST, TITLE, null,
|
USER_ID, true, APP, null, ARTIST, TITLE, null,
|
||||||
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
|
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
|
||||||
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
|
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
|
||||||
InstanceId.fakeInstanceId(-1), -1, false);
|
InstanceId.fakeInstanceId(-1), -1, false, null);
|
||||||
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
|
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -644,27 +644,8 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
val currentTime = clock.elapsedRealtime()
|
val currentTime = clock.elapsedRealtime()
|
||||||
mediaDataManager.addResumptionControls(
|
addResumeControlAndLoad(desc)
|
||||||
USER_ID,
|
|
||||||
desc,
|
|
||||||
Runnable {},
|
|
||||||
session.sessionToken,
|
|
||||||
APP_NAME,
|
|
||||||
pendingIntent,
|
|
||||||
PACKAGE_NAME
|
|
||||||
)
|
|
||||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
|
||||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
|
||||||
// THEN the media data indicates that it is for resumption
|
|
||||||
verify(listener)
|
|
||||||
.onMediaDataLoaded(
|
|
||||||
eq(PACKAGE_NAME),
|
|
||||||
eq(null),
|
|
||||||
capture(mediaDataCaptor),
|
|
||||||
eq(true),
|
|
||||||
eq(0),
|
|
||||||
eq(false)
|
|
||||||
)
|
|
||||||
val data = mediaDataCaptor.value
|
val data = mediaDataCaptor.value
|
||||||
assertThat(data.resumption).isTrue()
|
assertThat(data.resumption).isTrue()
|
||||||
assertThat(data.song).isEqualTo(SESSION_TITLE)
|
assertThat(data.song).isEqualTo(SESSION_TITLE)
|
||||||
@@ -690,27 +671,8 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
val currentTime = clock.elapsedRealtime()
|
val currentTime = clock.elapsedRealtime()
|
||||||
mediaDataManager.addResumptionControls(
|
addResumeControlAndLoad(desc)
|
||||||
USER_ID,
|
|
||||||
desc,
|
|
||||||
Runnable {},
|
|
||||||
session.sessionToken,
|
|
||||||
APP_NAME,
|
|
||||||
pendingIntent,
|
|
||||||
PACKAGE_NAME
|
|
||||||
)
|
|
||||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
|
||||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
|
||||||
// THEN the media data indicates that it is for resumption
|
|
||||||
verify(listener)
|
|
||||||
.onMediaDataLoaded(
|
|
||||||
eq(PACKAGE_NAME),
|
|
||||||
eq(null),
|
|
||||||
capture(mediaDataCaptor),
|
|
||||||
eq(true),
|
|
||||||
eq(0),
|
|
||||||
eq(false)
|
|
||||||
)
|
|
||||||
val data = mediaDataCaptor.value
|
val data = mediaDataCaptor.value
|
||||||
assertThat(data.resumption).isTrue()
|
assertThat(data.resumption).isTrue()
|
||||||
assertThat(data.song).isEqualTo(SESSION_TITLE)
|
assertThat(data.song).isEqualTo(SESSION_TITLE)
|
||||||
@@ -722,6 +684,84 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
|
verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAddResumptionControls_hasPartialProgress() {
|
||||||
|
whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
|
||||||
|
|
||||||
|
// WHEN resumption controls are added with partial progress
|
||||||
|
val progress = 0.5
|
||||||
|
val extras =
|
||||||
|
Bundle().apply {
|
||||||
|
putInt(
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
|
||||||
|
)
|
||||||
|
putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress)
|
||||||
|
}
|
||||||
|
val desc =
|
||||||
|
MediaDescription.Builder().run {
|
||||||
|
setTitle(SESSION_TITLE)
|
||||||
|
setExtras(extras)
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
addResumeControlAndLoad(desc)
|
||||||
|
|
||||||
|
val data = mediaDataCaptor.value
|
||||||
|
assertThat(data.resumption).isTrue()
|
||||||
|
assertThat(data.resumeProgress).isEqualTo(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAddResumptionControls_hasNotPlayedProgress() {
|
||||||
|
whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
|
||||||
|
|
||||||
|
// WHEN resumption controls are added that have not been played
|
||||||
|
val extras =
|
||||||
|
Bundle().apply {
|
||||||
|
putInt(
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val desc =
|
||||||
|
MediaDescription.Builder().run {
|
||||||
|
setTitle(SESSION_TITLE)
|
||||||
|
setExtras(extras)
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
addResumeControlAndLoad(desc)
|
||||||
|
|
||||||
|
val data = mediaDataCaptor.value
|
||||||
|
assertThat(data.resumption).isTrue()
|
||||||
|
assertThat(data.resumeProgress).isEqualTo(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAddResumptionControls_hasFullProgress() {
|
||||||
|
whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
|
||||||
|
|
||||||
|
// WHEN resumption controls are added with progress info
|
||||||
|
val extras =
|
||||||
|
Bundle().apply {
|
||||||
|
putInt(
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val desc =
|
||||||
|
MediaDescription.Builder().run {
|
||||||
|
setTitle(SESSION_TITLE)
|
||||||
|
setExtras(extras)
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
addResumeControlAndLoad(desc)
|
||||||
|
|
||||||
|
// THEN the media data includes the progress
|
||||||
|
val data = mediaDataCaptor.value
|
||||||
|
assertThat(data.resumption).isTrue()
|
||||||
|
assertThat(data.resumeProgress).isEqualTo(1)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testResumptionDisabled_dismissesResumeControls() {
|
fun testResumptionDisabled_dismissesResumeControls() {
|
||||||
// WHEN there are resume controls and resumption is switched off
|
// WHEN there are resume controls and resumption is switched off
|
||||||
@@ -730,26 +770,8 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
setTitle(SESSION_TITLE)
|
setTitle(SESSION_TITLE)
|
||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
mediaDataManager.addResumptionControls(
|
addResumeControlAndLoad(desc)
|
||||||
USER_ID,
|
|
||||||
desc,
|
|
||||||
Runnable {},
|
|
||||||
session.sessionToken,
|
|
||||||
APP_NAME,
|
|
||||||
pendingIntent,
|
|
||||||
PACKAGE_NAME
|
|
||||||
)
|
|
||||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
|
||||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
|
||||||
verify(listener)
|
|
||||||
.onMediaDataLoaded(
|
|
||||||
eq(PACKAGE_NAME),
|
|
||||||
eq(null),
|
|
||||||
capture(mediaDataCaptor),
|
|
||||||
eq(true),
|
|
||||||
eq(0),
|
|
||||||
eq(false)
|
|
||||||
)
|
|
||||||
val data = mediaDataCaptor.value
|
val data = mediaDataCaptor.value
|
||||||
mediaDataManager.setMediaResumptionEnabled(false)
|
mediaDataManager.setMediaResumptionEnabled(false)
|
||||||
|
|
||||||
@@ -1690,4 +1712,29 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f)
|
stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f)
|
||||||
whenever(controller.playbackState).thenReturn(stateBuilder.build())
|
whenever(controller.playbackState).thenReturn(stateBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Helper function to add a resumption control and capture the resulting MediaData */
|
||||||
|
private fun addResumeControlAndLoad(desc: MediaDescription) {
|
||||||
|
mediaDataManager.addResumptionControls(
|
||||||
|
USER_ID,
|
||||||
|
desc,
|
||||||
|
Runnable {},
|
||||||
|
session.sessionToken,
|
||||||
|
APP_NAME,
|
||||||
|
pendingIntent,
|
||||||
|
PACKAGE_NAME
|
||||||
|
)
|
||||||
|
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||||
|
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||||
|
|
||||||
|
verify(listener)
|
||||||
|
.onMediaDataLoaded(
|
||||||
|
eq(PACKAGE_NAME),
|
||||||
|
eq(null),
|
||||||
|
capture(mediaDataCaptor),
|
||||||
|
eq(true),
|
||||||
|
eq(0),
|
||||||
|
eq(false)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -905,6 +905,17 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
verify(expandedSet).setVisibility(R.id.actionNext, ConstraintSet.VISIBLE)
|
verify(expandedSet).setVisibility(R.id.actionNext, ConstraintSet.VISIBLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun bind_resumeState_withProgress() {
|
||||||
|
val progress = 0.5
|
||||||
|
val state = mediaData.copy(resumption = true, resumeProgress = progress)
|
||||||
|
|
||||||
|
player.attachPlayer(viewHolder)
|
||||||
|
player.bindPlayer(state, PACKAGE)
|
||||||
|
|
||||||
|
verify(seekBarViewModel).updateStaticProgress(progress)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun bindNotificationActions() {
|
fun bindNotificationActions() {
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
|
|||||||
Reference in New Issue
Block a user