Merge "Check for progress info from resume controls" into tm-qpr-dev am: ad6b846cb8 am: 7a1cc0819a

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21131806

Change-Id: I3afa82eaf252752a8e8df3cf6f5f34807c7a9e83
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Beth Thibodeau
2023-01-31 17:18:57 +00:00
committed by Automerger Merge Worker
10 changed files with 198 additions and 67 deletions

View File

@@ -360,6 +360,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")

View File

@@ -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 */

View File

@@ -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.
*

View File

@@ -68,6 +68,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
@@ -668,6 +669,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 {
@@ -698,6 +704,7 @@ class MediaDataManager(
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
resumeProgress = progress,
)
)
}

View File

@@ -116,8 +116,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;
@@ -125,6 +123,7 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
import dagger.Lazy;
import kotlin.Triple;
import kotlin.Unit;
@@ -524,8 +523,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 =

View File

@@ -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;
}
}

View File

@@ -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)
}

View File

@@ -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);
}

View File

@@ -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)
)
}
}

View File

@@ -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)