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
|
||||
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
|
||||
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
|
||||
|
||||
|
||||
@@ -92,6 +92,9 @@ data class MediaData(
|
||||
|
||||
/** Whether explicit indicator exists */
|
||||
val isExplicit: Boolean = false,
|
||||
|
||||
/** Track progress (0 - 1) to display for players where [resumption] is true */
|
||||
val resumeProgress: Double? = null,
|
||||
) {
|
||||
companion object {
|
||||
/** Media is playing on the local device */
|
||||
|
||||
@@ -237,6 +237,24 @@ constructor(
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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.resume.MediaResumeListener
|
||||
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.MediaUiEventLogger
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
@@ -667,6 +668,11 @@ class MediaDataManager(
|
||||
MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
|
||||
mediaFlags.isExplicitIndicatorEnabled()
|
||||
|
||||
val progress =
|
||||
if (mediaFlags.isResumeProgressEnabled()) {
|
||||
MediaDataUtils.getDescriptionProgress(desc.extras)
|
||||
} else null
|
||||
|
||||
val mediaAction = getResumeMediaAction(resumeAction)
|
||||
val lastActive = systemClock.elapsedRealtime()
|
||||
foregroundExecutor.execute {
|
||||
@@ -697,6 +703,7 @@ class MediaDataManager(
|
||||
instanceId = instanceId,
|
||||
appUid = appUid,
|
||||
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.time.SystemClock;
|
||||
|
||||
import dagger.Lazy;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -124,6 +122,7 @@ import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.Lazy;
|
||||
import kotlin.Triple;
|
||||
import kotlin.Unit;
|
||||
|
||||
@@ -523,8 +522,13 @@ public class MediaControlPanel {
|
||||
}
|
||||
|
||||
// Seek Bar
|
||||
final MediaController controller = getController();
|
||||
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
|
||||
if (data.getResumption() && data.getResumeProgress() != null) {
|
||||
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.
|
||||
mShowBroadcastDialogButton =
|
||||
|
||||
@@ -19,8 +19,12 @@ package com.android.systemui.media.controls.util;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.core.math.MathUtils;
|
||||
import androidx.media.utils.MediaConstants;
|
||||
|
||||
/**
|
||||
* Utility class with common methods for media controls
|
||||
*/
|
||||
@@ -50,4 +54,35 @@ public class MediaDataUtils {
|
||||
: unknownName);
|
||||
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. */
|
||||
fun isRecommendationCardUpdateEnabled() =
|
||||
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,
|
||||
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -644,27 +644,8 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
build()
|
||||
}
|
||||
val currentTime = clock.elapsedRealtime()
|
||||
mediaDataManager.addResumptionControls(
|
||||
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)
|
||||
)
|
||||
addResumeControlAndLoad(desc)
|
||||
|
||||
val data = mediaDataCaptor.value
|
||||
assertThat(data.resumption).isTrue()
|
||||
assertThat(data.song).isEqualTo(SESSION_TITLE)
|
||||
@@ -690,27 +671,8 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
build()
|
||||
}
|
||||
val currentTime = clock.elapsedRealtime()
|
||||
mediaDataManager.addResumptionControls(
|
||||
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)
|
||||
)
|
||||
addResumeControlAndLoad(desc)
|
||||
|
||||
val data = mediaDataCaptor.value
|
||||
assertThat(data.resumption).isTrue()
|
||||
assertThat(data.song).isEqualTo(SESSION_TITLE)
|
||||
@@ -722,6 +684,84 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
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
|
||||
fun testResumptionDisabled_dismissesResumeControls() {
|
||||
// WHEN there are resume controls and resumption is switched off
|
||||
@@ -730,26 +770,8 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
setTitle(SESSION_TITLE)
|
||||
build()
|
||||
}
|
||||
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)
|
||||
)
|
||||
addResumeControlAndLoad(desc)
|
||||
|
||||
val data = mediaDataCaptor.value
|
||||
mediaDataManager.setMediaResumptionEnabled(false)
|
||||
|
||||
@@ -1690,4 +1712,29 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f)
|
||||
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)
|
||||
}
|
||||
|
||||
@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
|
||||
fun bindNotificationActions() {
|
||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||
|
||||
Reference in New Issue
Block a user