diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 47904128916d0..43254aa5c5387 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2164,6 +2164,15 @@ %1$s of %2$s + + Play + + Pause + + Previous track + + Next track + Play diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index c2934931662bf..97533c90adbad 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -121,6 +121,7 @@ public class Flags { /***************************************/ // 900 - media public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false); + public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false); // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index b85f107260403..e921ad29c4457 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -862,8 +862,23 @@ class MediaCarouselController @Inject constructor( @VisibleForTesting internal object MediaPlayerData { - private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null, - emptyList(), emptyList(), "INVALID", null, null, null, true, null) + private val EMPTY = MediaData( + userId = -1, + initialized = false, + backgroundColor = 0, + app = null, + appIcon = null, + artist = null, + song = null, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = "INVALID", + token = null, + clickIntent = null, + device = null, + active = true, + resumeAction = null) // Whether should prioritize Smartspace card. internal var shouldPrioritizeSs: Boolean = false private set diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index f66eb5ba008a4..63555bbd3ba9f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -62,6 +62,7 @@ import com.android.systemui.util.animation.TransitionLayout; import com.android.systemui.util.time.SystemClock; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -122,6 +123,8 @@ public class MediaControlPanel { private MediaCarouselController mMediaCarouselController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final FalsingManager mFalsingManager; + private final MediaFlags mMediaFlags; + // Used for swipe-to-dismiss logging. protected boolean mIsImpressed = false; private SystemClock mSystemClock; @@ -138,7 +141,7 @@ public class MediaControlPanel { SeekBarViewModel seekBarViewModel, Lazy lazyMediaDataManager, KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory mediaOutputDialogFactory, MediaCarouselController mediaCarouselController, - FalsingManager falsingManager, SystemClock systemClock) { + FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; @@ -149,8 +152,8 @@ public class MediaControlPanel { mMediaOutputDialogFactory = mediaOutputDialogFactory; mMediaCarouselController = mediaCarouselController; mFalsingManager = falsingManager; + mMediaFlags = mediaFlags; mSystemClock = systemClock; - loadDimens(); mSeekBarViewModel.setLogSmartspaceClick(() -> { @@ -426,33 +429,61 @@ public class MediaControlPanel { deviceName.setText(deviceString); seamlessView.setContentDescription(deviceString); - List actionsWhenCollapsed = data.getActionsToShowInCompact(); - // Media controls - int i = 0; + // Media action buttons List actionIcons = data.getActions(); + List actionsWhenCollapsed = data.getActionsToShowInCompact(); + + if (mMediaFlags.areMediaSessionActionsEnabled() && data.getSemanticActions() != null) { + // Use PlaybackState actions instead + MediaButton semanticActions = data.getSemanticActions(); + + actionIcons = new ArrayList(); + actionIcons.add(semanticActions.getStartCustom()); + actionIcons.add(semanticActions.getPrevOrCustom()); + actionIcons.add(semanticActions.getPlayOrPause()); + actionIcons.add(semanticActions.getNextOrCustom()); + actionIcons.add(semanticActions.getEndCustom()); + + actionsWhenCollapsed = new ArrayList(); + actionsWhenCollapsed.add(1); + actionsWhenCollapsed.add(2); + actionsWhenCollapsed.add(3); + } + + int i = 0; for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) { int actionId = ACTION_IDS[i]; + boolean visibleInCompat = actionsWhenCollapsed.contains(i); final ImageButton button = mPlayerViewHolder.getAction(actionId); MediaAction mediaAction = actionIcons.get(i); - button.setImageIcon(mediaAction.getIcon()); - button.setContentDescription(mediaAction.getContentDescription()); - Runnable action = mediaAction.getAction(); + if (mediaAction != null) { + button.setImageIcon(mediaAction.getIcon()); + button.setContentDescription(mediaAction.getContentDescription()); + Runnable action = mediaAction.getAction(); - if (action == null) { - button.setEnabled(false); + if (action == null) { + button.setEnabled(false); + } else { + button.setEnabled(true); + button.setOnClickListener(v -> { + if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK + /* isRecommendationCard */ false); + action.run(); + } + }); + } + setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat); + setVisibleAndAlpha(expandedSet, actionId, true /*visible */); } else { - button.setEnabled(true); - button.setOnClickListener(v -> { - if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK - /* isRecommendationCard */ false); - action.run(); - } - }); + button.setImageIcon(null); + button.setContentDescription(null); + button.setEnabled(false); + setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat); + // for expanded layout, set as INVISIBLE so that we still reserve space in the UI + expandedSet.setVisibility(actionId, ConstraintSet.INVISIBLE); + expandedSet.setAlpha(actionId, 0.0f); } - boolean visibleInCompat = actionsWhenCollapsed.contains(i); - setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat); - setVisibleAndAlpha(expandedSet, actionId, true /*visible */); } // Hide any unused buttons diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index bda07f4282186..4b8dfdee040e9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -47,13 +47,18 @@ data class MediaData( */ val artwork: Icon?, /** - * List of actions that can be performed on the player: prev, next, play, pause, etc. + * List of generic action buttons for the media player, based on notification actions */ val actions: List, /** * Same as above, but shown on smaller versions of the player, like in QQS or keyguard. */ val actionsToShowInCompact: List, + /** + * Semantic actions buttons, based on the PlaybackState of the media session. + * If present, these actions will be preferred in the UI over [actions] + */ + val semanticActions: MediaButton? = null, /** * Package name of the app that's posting the media. */ @@ -125,6 +130,32 @@ data class MediaData( } } +/** + * Contains [MediaAction] objects which represent specific buttons in the UI + */ +data class MediaButton( + /** + * Play/pause button + */ + var playOrPause: MediaAction? = null, + /** + * Next button, or custom action + */ + var nextOrCustom: MediaAction? = null, + /** + * Previous button, or custom action + */ + var prevOrCustom: MediaAction? = null, + /** + * First custom action space + */ + var startCustom: MediaAction? = null, + /** + * Last custom action space + */ + var endCustom: MediaAction? = null +) + /** State of a media action. */ data class MediaAction( val icon: Icon?, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 7c0f7fc2967e4..49a63c3eaa575 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -38,6 +38,7 @@ import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession +import android.media.session.PlaybackState import android.net.Uri import android.os.Parcelable import android.os.UserHandle @@ -45,6 +46,7 @@ import android.provider.Settings import android.service.notification.StatusBarNotification import android.text.TextUtils import android.util.Log +import androidx.media.utils.MediaConstants import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.R @@ -80,8 +82,24 @@ private const val TAG = "MediaDataManager" private const val DEBUG = true private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent" -private val LOADING = MediaData(-1, false, 0, null, null, null, null, null, - emptyList(), emptyList(), "INVALID", null, null, null, true, null) +private val LOADING = MediaData( + userId = -1, + initialized = false, + backgroundColor = 0, + app = null, + appIcon = null, + artist = null, + song = null, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = "INVALID", + token = null, + clickIntent = null, + device = null, + active = true, + resumeAction = null) + @VisibleForTesting internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false, "INVALID", null, emptyList(), null, 0, 0) @@ -112,7 +130,8 @@ class MediaDataManager( private var useMediaResumption: Boolean, private val useQsMediaPlayer: Boolean, private val systemClock: SystemClock, - private val tunerService: TunerService + private val tunerService: TunerService, + private val mediaFlags: MediaFlags, ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -127,6 +146,10 @@ class MediaDataManager( // Maximum number of actions allowed in compact view @JvmField val MAX_COMPACT_ACTIONS = 3 + + /** Maximum number of [PlaybackState.CustomAction] buttons supported */ + @JvmField + val MAX_CUSTOM_ACTIONS = 4 } private val themeText = com.android.settingslib.Utils.getColorAttr(context, @@ -182,12 +205,13 @@ class MediaDataManager( activityStarter: ActivityStarter, smartspaceMediaDataProvider: SmartspaceMediaDataProvider, clock: SystemClock, - tunerService: TunerService + tunerService: TunerService, + mediaFlags: MediaFlags ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory, broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener, mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter, activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context), - Utils.useQsMediaPlayer(context), clock, tunerService) + Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags) private val appChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -522,7 +546,7 @@ class MediaDataManager( foregroundExecutor.execute { onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName, null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), - packageName, token, appIntent, device = null, active = false, + null, packageName, token, appIntent, device = null, active = false, resumeAction = resumeAction, resumption = true, notificationKey = packageName, hasCheckedForResume = true, lastActive = lastActive)) } @@ -594,15 +618,55 @@ class MediaDataManager( } // Control buttons + // If flag is enabled and controller has a PlaybackState, create actions from session info + // Otherwise, use the notification actions + var actionIcons: List = emptyList() + var actionsToShowCollapsed: List = emptyList() + var semanticActions: MediaButton? = null + if (mediaFlags.areMediaSessionActionsEnabled() && mediaController.playbackState != null) { + semanticActions = createActionsFromState(sbn.packageName, mediaController) + } else { + val actions = createActionsFromNotification(sbn) + actionIcons = actions.first + actionsToShowCollapsed = actions.second + } + + val playbackLocation = + if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE + else if (mediaController.playbackInfo?.playbackType == + MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL + else MediaData.PLAYBACK_CAST_LOCAL + val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null + val lastActive = systemClock.elapsedRealtime() + foregroundExecutor.execute { + val resumeAction: Runnable? = mediaEntries[key]?.resumeAction + val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true + val active = mediaEntries[key]?.active ?: true + onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app, + smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, + semanticActions, sbn.packageName, token, notif.contentIntent, null, + active, resumeAction = resumeAction, playbackLocation = playbackLocation, + notificationKey = key, hasCheckedForResume = hasCheckedForResume, + isPlaying = isPlaying, isClearable = sbn.isClearable(), + lastActive = lastActive)) + } + } + + /** + * Generate action buttons based on notification actions + */ + private fun createActionsFromNotification(sbn: StatusBarNotification): + Pair, List> { + val notif = sbn.notification val actionIcons: MutableList = ArrayList() val actions = notif.actions var actionsToShowCollapsed = notif.extras.getIntArray( - Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf() + Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf() if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) { - Log.e(TAG, "Too many compact actions for $key, limiting to first $MAX_COMPACT_ACTIONS") + Log.e(TAG, "Too many compact actions for ${sbn.key}," + + "limiting to first $MAX_COMPACT_ACTIONS") actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS) } - // TODO: b/153736623 look into creating actions when this isn't a media style notification if (actions != null) { for ((index, action) in actions.withIndex()) { @@ -631,32 +695,150 @@ class MediaDataManager( action.getIcon() }.setTint(themeText) val mediaAction = MediaAction( - mediaActionIcon, - runnable, - action.title) + mediaActionIcon, + runnable, + action.title) actionIcons.add(mediaAction) } } + return Pair(actionIcons, actionsToShowCollapsed) + } - val playbackLocation = - if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE - else if (mediaController.playbackInfo?.playbackType == - MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL - else MediaData.PLAYBACK_CAST_LOCAL - val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null - val lastActive = systemClock.elapsedRealtime() - foregroundExecutor.execute { - val resumeAction: Runnable? = mediaEntries[key]?.resumeAction - val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true - val active = mediaEntries[key]?.active ?: true - onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app, - smallIcon, artist, song, artWorkIcon, actionIcons, - actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null, - active, resumeAction = resumeAction, playbackLocation = playbackLocation, - notificationKey = key, hasCheckedForResume = hasCheckedForResume, - isPlaying = isPlaying, isClearable = sbn.isClearable(), - lastActive = lastActive)) + /** + * Generates action button info for this media session based on the PlaybackState + * + * @param packageName Package name for the media app + * @param controller MediaController for the current session + * @return a Pair consisting of a list of media actions, and a list of ints representing which + * of those actions should be shown in the compact player + */ + private fun createActionsFromState(packageName: String, controller: MediaController): + MediaButton? { + val actions = MediaButton() + controller.playbackState?.let { state -> + // First, check for standard actions + actions.playOrPause = 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(4) { null } + var customCount = 0 + for (i in 0..MAX_CUSTOM_ACTIONS) { + getCustomAction(state, packageName, controller, customCount)?.let { + customActions[customCount++] = it + } + } + + // Finally, assign the remaining button slots: C A play/pause B 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.startCustom = customActions[customIdx++] + actions.endCustom = customActions[customIdx++] } + return actions + } + + /** + * Get a [MediaAction] representing one of + * - [PlaybackState.ACTION_PLAY] + * - [PlaybackState.ACTION_PAUSE] + * - [PlaybackState.ACTION_SKIP_TO_PREVIOUS] + * - [PlaybackState.ACTION_SKIP_TO_NEXT] + */ + private fun getStandardAction( + controller: MediaController, + stateActions: Long, + action: Long + ): MediaAction? { + if (stateActions and action == 0L) { + return null + } + + return when (action) { + PlaybackState.ACTION_PLAY -> { + MediaAction( + Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_play), + { controller.transportControls.play() }, + context.getString(R.string.controls_media_button_play) + ) + } + PlaybackState.ACTION_PAUSE -> { + MediaAction( + Icon.createWithResource(context, + com.android.internal.R.drawable.ic_media_pause), + { controller.transportControls.pause() }, + context.getString(R.string.controls_media_button_pause) + ) + } + PlaybackState.ACTION_SKIP_TO_PREVIOUS -> { + MediaAction( + Icon.createWithResource(context, + com.android.internal.R.drawable.ic_media_previous), + { controller.transportControls.skipToPrevious() }, + context.getString(R.string.controls_media_button_prev) + ) + } + PlaybackState.ACTION_SKIP_TO_NEXT -> { + MediaAction( + Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_next), + { controller.transportControls.skipToNext() }, + context.getString(R.string.controls_media_button_next) + ) + } + else -> null + } + } + + /** + * Get a [MediaAction] representing a [PlaybackState.CustomAction] + */ + private fun getCustomAction( + state: PlaybackState, + packageName: String, + controller: MediaController, + index: Int + ): 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( + Icon.createWithResource(packageName, it.icon), + { controller.transportControls.sendCustomAction(it, it.extras) }, + it.name + ) } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt new file mode 100644 index 0000000000000..b4a4b42d1509e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject + +@SysUISingleton +class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { + /** + * Check whether media control actions should be based on PlaybackState instead of notification + */ + fun areMediaSessionActionsEnabled(): Boolean { + return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS) + } +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 41ce9417c78f9..7cc0172399361 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.media import android.content.Intent import android.graphics.Color import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.Icon import android.graphics.drawable.RippleDrawable import android.media.MediaMetadata import android.media.session.MediaSession @@ -97,6 +98,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory @Mock private lateinit var mediaCarouselController: MediaCarouselController @Mock private lateinit var falsingManager: FalsingManager + @Mock private lateinit var mediaFlags: MediaFlags private lateinit var appIcon: ImageView private lateinit var albumView: ImageView private lateinit var titleText: TextView @@ -123,6 +125,7 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var session: MediaSession private val device = MediaDeviceData(true, null, DEVICE_NAME) private val disabledDevice = MediaDeviceData(false, null, "Disabled Device") + private lateinit var mediaData: MediaData private val clock = FakeSystemClock() @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -135,7 +138,7 @@ public class MediaControlPanelTest : SysuiTestCase() { player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController, seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil, - mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock) + mediaOutputDialogFactory, mediaCarouselController, falsingManager, mediaFlags, clock) whenever(seekBarViewModel.progress).thenReturn(seekBarData) // Mock out a view holder for the player to attach to. @@ -165,14 +168,19 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(holder.totalTimeView).thenReturn(totalTimeView) action0 = ImageButton(context) whenever(holder.action0).thenReturn(action0) + whenever(holder.getAction(R.id.action0)).thenReturn(action0) action1 = ImageButton(context) whenever(holder.action1).thenReturn(action1) + whenever(holder.getAction(R.id.action1)).thenReturn(action1) action2 = ImageButton(context) whenever(holder.action2).thenReturn(action2) + whenever(holder.getAction(R.id.action2)).thenReturn(action2) action3 = ImageButton(context) whenever(holder.action3).thenReturn(action3) + whenever(holder.getAction(R.id.action3)).thenReturn(action3) action4 = ImageButton(context) whenever(holder.action4).thenReturn(action4) + whenever(holder.getAction(R.id.action4)).thenReturn(action4) whenever(holder.longPressText).thenReturn(longPressText) whenever(longPressText.handler).thenReturn(handler) settings = View(context) @@ -200,6 +208,26 @@ public class MediaControlPanelTest : SysuiTestCase() { setPlaybackState(playbackBuilder.build()) } session.setActive(true) + + mediaData = MediaData( + userId = USER_ID, + initialized = true, + backgroundColor = BG_COLOR, + app = APP, + appIcon = null, + artist = ARTIST, + song = TITLE, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = PACKAGE, + token = session.sessionToken, + clickIntent = null, + device = device, + active = true, + resumeAction = null) + + whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false) } @After @@ -210,18 +238,50 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindWhenUnattached() { - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, null, null, device, true, null) + val state = mediaData.copy(token = null) player.bindPlayer(state, PACKAGE) assertThat(player.isPlaying()).isFalse() } + @Test + fun bindSemanticActions() { + whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) + val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) + val semanticActions = MediaButton( + playOrPause = MediaAction(icon, Runnable {}, "play"), + nextOrCustom = MediaAction(icon, Runnable {}, "next"), + startCustom = MediaAction(icon, null, "custom 1"), + endCustom = MediaAction(icon, null, "custom 2") + ) + val state = mediaData.copy(semanticActions = semanticActions) + + player.attachPlayer(holder) + player.bindPlayer(state, PACKAGE) + + verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE) + assertThat(action0.contentDescription).isEqualTo("custom 1") + assertThat(action0.isEnabled()).isFalse() + + verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE) + assertThat(action1.isEnabled()).isFalse() + + verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE) + assertThat(action2.isEnabled()).isTrue() + assertThat(action2.contentDescription).isEqualTo("play") + + verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.VISIBLE) + assertThat(action3.isEnabled()).isTrue() + assertThat(action3.contentDescription).isEqualTo("next") + + verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE) + assertThat(action4.contentDescription).isEqualTo("custom 2") + assertThat(action4.isEnabled()).isFalse() + } + @Test fun bindText() { player.attachPlayer(holder) - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) - player.bindPlayer(state, PACKAGE) + player.bindPlayer(mediaData, PACKAGE) assertThat(titleText.getText()).isEqualTo(TITLE) assertThat(artistText.getText()).isEqualTo(ARTIST) } @@ -229,9 +289,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindDevice() { player.attachPlayer(holder) - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) - player.bindPlayer(state, PACKAGE) + player.bindPlayer(mediaData, PACKAGE) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.contentDescription).isEqualTo(DEVICE_NAME) assertThat(seamless.isEnabled()).isTrue() @@ -242,8 +300,7 @@ public class MediaControlPanelTest : SysuiTestCase() { seamless.id = 1 val fallbackString = context.getString(R.string.media_seamless_other_device) player.attachPlayer(holder) - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null) + val state = mediaData.copy(device = disabledDevice) player.bindPlayer(state, PACKAGE) assertThat(seamless.isEnabled()).isFalse() assertThat(seamlessText.getText()).isEqualTo(fallbackString) @@ -254,8 +311,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindNullDevice() { val fallbackString = context.getResources().getString(R.string.media_seamless_other_device) player.attachPlayer(holder) - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null) + val state = mediaData.copy(device = null) player.bindPlayer(state, PACKAGE) assertThat(seamless.isEnabled()).isTrue() assertThat(seamlessText.getText()).isEqualTo(fallbackString) @@ -265,9 +321,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindDeviceResumptionPlayer() { player.attachPlayer(holder) - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null, - resumption = true) + val state = mediaData.copy(resumption = true) player.bindPlayer(state, PACKAGE) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.isEnabled()).isFalse() @@ -323,9 +377,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun dismissButtonClick() { val mediaKey = "key for dismissal" player.attachPlayer(holder) - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null, - notificationKey = KEY) + val state = mediaData.copy(notificationKey = KEY) player.bindPlayer(state, mediaKey) assertThat(dismiss.isEnabled).isEqualTo(true) @@ -337,9 +389,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun dismissButtonDisabled() { val mediaKey = "key for dismissal" player.attachPlayer(holder) - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null, - isClearable = false, notificationKey = KEY) + val state = mediaData.copy(isClearable = false, notificationKey = KEY) player.bindPlayer(state, mediaKey) assertThat(dismiss.isEnabled).isEqualTo(false) @@ -351,9 +401,7 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false) player.attachPlayer(holder) - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null, - notificationKey = KEY) + val state = mediaData.copy(notificationKey = KEY) player.bindPlayer(state, mediaKey) assertThat(dismiss.isEnabled).isEqualTo(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index 09c83e566ee88..7a487b871d9d8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -74,8 +74,9 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager = new MediaDataCombineLatest(); mManager.addListener(mListener); - mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, - new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, + mMediaData = new MediaData( + USER_ID, true, BG_COLOR, 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); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt index 5a3c43c6cc151..6b203bcf68281 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt @@ -96,11 +96,24 @@ class MediaDataFilterTest : SysuiTestCase() { setUser(USER_MAIN) // Set up test media data - dataMain = MediaData(USER_MAIN, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, null, null, device, true, null) - - dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null, - emptyList(), emptyList(), PACKAGE, null, null, device, true, null) + dataMain = MediaData( + userId = USER_MAIN, + initialized = true, + backgroundColor = BG_COLOR, + app = APP, + appIcon = null, + artist = ARTIST, + song = TITLE, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = PACKAGE, + token = null, + clickIntent = null, + device = device, + active = true, + resumeAction = null) + dataGuest = dataMain.copy(userId = USER_GUEST) `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY) `when`(smartspaceData.isActive).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index e2019e089329f..d0b957c6e5f92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -11,12 +11,15 @@ import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession +import android.media.session.PlaybackState import android.os.Bundle import android.provider.Settings import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.media.utils.MediaConstants import androidx.test.filters.SmallTest +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager @@ -67,6 +70,7 @@ class MediaDataManagerTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() @Mock lateinit var mediaControllerFactory: MediaControllerFactory @Mock lateinit var controller: MediaController + @Mock lateinit var transportControls: MediaController.TransportControls @Mock lateinit var playbackInfo: MediaController.PlaybackInfo lateinit var session: MediaSession lateinit var metadataBuilder: MediaMetadata.Builder @@ -87,6 +91,7 @@ class MediaDataManagerTest : SysuiTestCase() { @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget @Mock private lateinit var mediaRecommendationItem: SmartspaceAction @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction + @Mock private lateinit var mediaFlags: MediaFlags lateinit var mediaDataManager: MediaDataManager lateinit var mediaNotification: StatusBarNotification @Captor lateinit var mediaDataCaptor: ArgumentCaptor @@ -122,7 +127,8 @@ class MediaDataManagerTest : SysuiTestCase() { useMediaResumption = true, useQsMediaPlayer = true, systemClock = clock, - tunerService = tunerService + tunerService = tunerService, + mediaFlags = mediaFlags ) verify(tunerService).addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) @@ -140,6 +146,7 @@ class MediaDataManagerTest : SysuiTestCase() { putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) } whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller) + whenever(controller.transportControls).thenReturn(transportControls) whenever(controller.playbackInfo).thenReturn(playbackInfo) whenever(playbackInfo.playbackType).thenReturn( MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) @@ -161,6 +168,7 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA) whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem)) whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L) + whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false) } @After @@ -583,4 +591,157 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo( MediaDataManager.MAX_COMPACT_ACTIONS) } + + @Test + fun testPlaybackActions_noState_usesNotification() { + val desc = "Notification Action" + whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) + whenever(controller.playbackState).thenReturn(null) + + val notifWithAction = SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + it.addAction(android.R.drawable.ic_media_play, desc, null) + } + build() + } + mediaDataManager.onNotificationAdded(KEY, notifWithAction) + + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), + eq(0)) + + assertThat(mediaDataCaptor.value!!.semanticActions).isNull() + assertThat(mediaDataCaptor.value!!.actions).hasSize(1) + assertThat(mediaDataCaptor.value!!.actions[0]!!.contentDescription).isEqualTo(desc) + } + + @Test + fun testPlaybackActions_hasPrevNext() { + val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4") + whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) + val stateActions = PlaybackState.ACTION_PLAY or + PlaybackState.ACTION_SKIP_TO_PREVIOUS or + PlaybackState.ACTION_SKIP_TO_NEXT + val stateBuilder = PlaybackState.Builder() + .setActions(stateActions) + customDesc.forEach { + stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause) + } + whenever(controller.playbackState).thenReturn(stateBuilder.build()) + + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), + eq(0)) + + assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull() + val actions = mediaDataCaptor.value!!.semanticActions!! + + assertThat(actions.playOrPause).isNotNull() + assertThat(actions.playOrPause!!.contentDescription).isEqualTo( + context.getString(R.string.controls_media_button_play)) + actions.playOrPause!!.action!!.run() + verify(transportControls).play() + + assertThat(actions.prevOrCustom).isNotNull() + assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo( + context.getString(R.string.controls_media_button_prev)) + actions.prevOrCustom!!.action!!.run() + verify(transportControls).skipToPrevious() + + assertThat(actions.nextOrCustom).isNotNull() + assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo( + context.getString(R.string.controls_media_button_next)) + actions.nextOrCustom!!.action!!.run() + verify(transportControls).skipToNext() + + assertThat(actions.startCustom).isNotNull() + assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0]) + + assertThat(actions.endCustom).isNotNull() + assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1]) + } + + @Test + fun testPlaybackActions_noPrevNext_usesCustom() { + val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4") + whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) + val stateActions = PlaybackState.ACTION_PLAY + val stateBuilder = PlaybackState.Builder() + .setActions(stateActions) + customDesc.forEach { + stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause) + } + whenever(controller.playbackState).thenReturn(stateBuilder.build()) + + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), + eq(0)) + + assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull() + val actions = mediaDataCaptor.value!!.semanticActions!! + + assertThat(actions.playOrPause).isNotNull() + assertThat(actions.playOrPause!!.contentDescription).isEqualTo( + context.getString(R.string.controls_media_button_play)) + + assertThat(actions.prevOrCustom).isNotNull() + assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(customDesc[0]) + + assertThat(actions.nextOrCustom).isNotNull() + assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1]) + + assertThat(actions.startCustom).isNotNull() + assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[2]) + + assertThat(actions.endCustom).isNotNull() + assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[3]) + } + + @Test + fun testPlaybackActions_reservedSpace() { + val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4") + whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) + val stateActions = PlaybackState.ACTION_PLAY + val stateBuilder = PlaybackState.Builder() + .setActions(stateActions) + customDesc.forEach { + stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause) + } + val extras = Bundle().apply { + putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true) + putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true) + } + whenever(controller.playbackState).thenReturn(stateBuilder.build()) + whenever(controller.extras).thenReturn(extras) + + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), + eq(0)) + + assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull() + val actions = mediaDataCaptor.value!!.semanticActions!! + + assertThat(actions.playOrPause).isNotNull() + assertThat(actions.playOrPause!!.contentDescription).isEqualTo( + context.getString(R.string.controls_media_button_play)) + + assertThat(actions.prevOrCustom).isNull() + assertThat(actions.nextOrCustom).isNull() + + assertThat(actions.startCustom).isNotNull() + assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0]) + + assertThat(actions.endCustom).isNotNull() + assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1]) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index 7dadbad8025f3..3d59497fd978e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -102,9 +102,24 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // Create a media sesssion and notification for testing. session = MediaSession(context, SESSION_KEY) - mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null, - emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null, - device = null, active = true, resumeAction = null) + mediaData = MediaData( + userId = USER_ID, + initialized = true, + backgroundColor = 0, + app = PACKAGE, + appIcon = null, + artist = null, + song = SESSION_TITLE, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = PACKAGE, + token = session.sessionToken, + clickIntent = null, + device = null, + active = true, + resumeAction = null) + whenever(controllerFactory.create(session.sessionToken)) .thenReturn(controller) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt index 421f9bee78fa1..ceeb0dbb159e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt @@ -163,8 +163,27 @@ public class MediaPlayerDataTest : SysuiTestCase() { isPlaying: Boolean?, location: Int, resumption: Boolean - ) = - MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList(), - "package:" + app, null, null, null, true, null, location, resumption, "key:" + app, - false, isPlaying) + ) = MediaData( + userId = 0, + initialized = false, + backgroundColor = 0, + app = app, + appIcon = null, + artist = null, + song = null, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = "package: $app", + token = null, + clickIntent = null, + device = null, + active = true, + resumeAction = null, + playbackLocation = location, + resumption = resumption, + notificationKey = "key: $app", + hasCheckedForResume = false, + isPlaying = isPlaying + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index de2235dd1c420..8c2fed5bd2ed2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -95,13 +95,26 @@ class MediaTimeoutListenerTest : SysuiTestCase() { setPlaybackState(playbackBuilder.build()) } session.setActive(true) - mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null, - emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null, - device = null, active = true, resumeAction = null) - resumeData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null, - emptyList(), emptyList(), PACKAGE, null, clickIntent = null, - device = null, active = false, resumeAction = null, resumption = true) + mediaData = MediaData( + userId = USER_ID, + initialized = true, + backgroundColor = 0, + app = PACKAGE, + appIcon = null, + artist = null, + song = SESSION_TITLE, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = PACKAGE, + token = session.sessionToken, + clickIntent = null, + device = null, + active = true, + resumeAction = null) + + resumeData = mediaData.copy(token = null, active = false, resumption = true) } @Test