Disable output switcher in resumption controls
Bug: 159229758 Test: manual - Play Spotify, reboot, look at player. Check that output switcher is disabled. Test: manual - Play Spotify, dismiss app, look at player. Check that output switcher is disabled Change-Id: I2ca75b7c0c0a198ca827395ea3fc9f5c586553e6
This commit is contained in:
@@ -60,6 +60,7 @@ import javax.inject.Inject;
|
||||
*/
|
||||
public class MediaControlPanel {
|
||||
private static final String TAG = "MediaControlPanel";
|
||||
private static final float DISABLED_ALPHA = 0.38f;
|
||||
|
||||
// Button IDs for QS controls
|
||||
static final int[] ACTION_IDS = {
|
||||
@@ -276,6 +277,11 @@ public class MediaControlPanel {
|
||||
mViewHolder.getSeamless().setVisibility(seamlessVisibility);
|
||||
expandedSet.setVisibility(seamlessId, seamlessVisibility);
|
||||
collapsedSet.setVisibility(seamlessId, seamlessVisibility);
|
||||
final float seamlessAlpha = data.getResumption() ? DISABLED_ALPHA : 1.0f;
|
||||
expandedSet.setAlpha(seamlessId, seamlessAlpha);
|
||||
collapsedSet.setAlpha(seamlessId, seamlessAlpha);
|
||||
// Disable clicking on output switcher for resumption controls.
|
||||
mViewHolder.getSeamless().setEnabled(!data.getResumption());
|
||||
if (showFallback) {
|
||||
iconView.setImageDrawable(null);
|
||||
deviceName.setText(null);
|
||||
|
||||
@@ -80,6 +80,11 @@ data class MediaData(
|
||||
* Action that should be performed to restart a non active session.
|
||||
*/
|
||||
var resumeAction: Runnable?,
|
||||
/**
|
||||
* Indicates that this player is a resumption player (ie. It only shows a play actions which
|
||||
* will start the app and start playing).
|
||||
*/
|
||||
var resumption: Boolean = false,
|
||||
/**
|
||||
* Notification key for cancelling a media player after a timeout (when not using resumption.)
|
||||
*/
|
||||
|
||||
@@ -323,7 +323,7 @@ class MediaDataManager(
|
||||
onMediaDataLoaded(packageName, null, MediaData(true, bgColor, appName,
|
||||
null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
|
||||
packageName, token, appIntent, device = null, active = false,
|
||||
resumeAction = resumeAction, notificationKey = packageName,
|
||||
resumeAction = resumeAction, resumption = true, notificationKey = packageName,
|
||||
hasCheckedForResume = true))
|
||||
}
|
||||
}
|
||||
@@ -542,7 +542,7 @@ class MediaDataManager(
|
||||
val data = mediaEntries.remove(key)!!
|
||||
val resumeAction = getResumeMediaAction(data.resumeAction!!)
|
||||
val updated = data.copy(token = null, actions = listOf(resumeAction),
|
||||
actionsToShowInCompact = listOf(0), active = false)
|
||||
actionsToShowInCompact = listOf(0), active = false, resumption = true)
|
||||
mediaEntries.put(data.packageName, updated)
|
||||
// Notify listeners of "new" controls
|
||||
val listenersCopy = listeners.toSet()
|
||||
|
||||
@@ -242,4 +242,15 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
|
||||
com.android.internal.R.string.ext_media_seamless_action))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bindDeviceResumptionPlayer() {
|
||||
player.attach(holder)
|
||||
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
|
||||
resumption = true)
|
||||
player.bind(state)
|
||||
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
|
||||
assertThat(seamless.isEnabled()).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
|
||||
mManager.addListener(mListener);
|
||||
|
||||
mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
|
||||
new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, KEY,
|
||||
false);
|
||||
new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, false,
|
||||
KEY, false);
|
||||
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.android.systemui.media
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.Notification.MediaStyle
|
||||
import android.app.PendingIntent
|
||||
import android.media.MediaDescription
|
||||
import android.media.MediaMetadata
|
||||
import android.media.session.MediaController
|
||||
import android.media.session.MediaSession
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
@@ -8,6 +13,9 @@ import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.statusbar.SbnBuilder
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@@ -18,12 +26,14 @@ import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import java.util.concurrent.Executor
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
|
||||
private const val KEY = "KEY"
|
||||
private const val PACKAGE_NAME = "com.android.systemui"
|
||||
private const val APP_NAME = "SystemUI"
|
||||
private const val SESSION_ARTIST = "artist"
|
||||
private const val SESSION_TITLE = "title"
|
||||
|
||||
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||
private fun <T> anyObject(): T {
|
||||
@@ -36,33 +46,47 @@ private fun <T> anyObject(): T {
|
||||
class MediaDataManagerTest : SysuiTestCase() {
|
||||
|
||||
@Mock lateinit var mediaControllerFactory: MediaControllerFactory
|
||||
@Mock lateinit var backgroundExecutor: Executor
|
||||
@Mock lateinit var foregroundExecutor: Executor
|
||||
@Mock lateinit var controller: MediaController
|
||||
lateinit var session: MediaSession
|
||||
lateinit var metadataBuilder: MediaMetadata.Builder
|
||||
lateinit var backgroundExecutor: FakeExecutor
|
||||
lateinit var foregroundExecutor: FakeExecutor
|
||||
@Mock lateinit var dumpManager: DumpManager
|
||||
@Mock lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||
@Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
|
||||
@Mock lateinit var mediaResumeListener: MediaResumeListener
|
||||
@Mock lateinit var pendingIntent: PendingIntent
|
||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||
lateinit var mediaDataManager: MediaDataManager
|
||||
lateinit var mediaNotification: StatusBarNotification
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
foregroundExecutor = FakeExecutor(FakeSystemClock())
|
||||
backgroundExecutor = FakeExecutor(FakeSystemClock())
|
||||
mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor,
|
||||
mediaControllerFactory, broadcastDispatcher, dumpManager,
|
||||
mediaTimeoutListener, mediaResumeListener, useMediaResumption = true,
|
||||
useQsMediaPlayer = true)
|
||||
val sbn = mock(StatusBarNotification::class.java)
|
||||
val notification = mock(Notification::class.java)
|
||||
whenever(notification.hasMediaSession()).thenReturn(true)
|
||||
whenever(notification.notificationStyle).thenReturn(Notification.MediaStyle::class.java)
|
||||
whenever(sbn.notification).thenReturn(notification)
|
||||
whenever(sbn.packageName).thenReturn(PACKAGE_NAME)
|
||||
mediaNotification = sbn
|
||||
session = MediaSession(context, "MediaDataManagerTestSession")
|
||||
mediaNotification = SbnBuilder().run {
|
||||
setPkg(PACKAGE_NAME)
|
||||
modifyNotification(context).also {
|
||||
it.setSmallIcon(android.R.drawable.ic_media_pause)
|
||||
it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
|
||||
}
|
||||
build()
|
||||
}
|
||||
metadataBuilder = MediaMetadata.Builder().apply {
|
||||
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
|
||||
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
|
||||
}
|
||||
whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
session.release()
|
||||
mediaDataManager.destroy()
|
||||
}
|
||||
|
||||
@@ -82,7 +106,7 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testLoadsMetadataOnBackground() {
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
verify(backgroundExecutor).execute(anyObject())
|
||||
assertThat(backgroundExecutor.numPending()).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -123,4 +147,66 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
|
||||
verify(listener).onMediaDataRemoved(eq(KEY))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationRemoved_withResumption() {
|
||||
// GIVEN that the manager has a notification with a resume action
|
||||
val listener = TestListener()
|
||||
mediaDataManager.addListener(listener)
|
||||
whenever(controller.metadata).thenReturn(metadataBuilder.build())
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||
val data = listener.data!!
|
||||
assertThat(data.resumption).isFalse()
|
||||
mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
|
||||
// WHEN the notification is removed
|
||||
mediaDataManager.onNotificationRemoved(KEY)
|
||||
// THEN the media data indicates that it is
|
||||
assertThat(listener.data!!.resumption).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddResumptionControls() {
|
||||
val listener = TestListener()
|
||||
mediaDataManager.addListener(listener)
|
||||
// WHEN resumption controls are added`
|
||||
val desc = MediaDescription.Builder().run {
|
||||
setTitle(SESSION_TITLE)
|
||||
build()
|
||||
}
|
||||
mediaDataManager.addResumptionControls(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
|
||||
val data = listener.data!!
|
||||
assertThat(data.resumption).isTrue()
|
||||
assertThat(data.song).isEqualTo(SESSION_TITLE)
|
||||
assertThat(data.app).isEqualTo(APP_NAME)
|
||||
assertThat(data.actions).hasSize(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple implementation of [MediaDataManager.Listener] for the test.
|
||||
*
|
||||
* Giving up on trying to get a mock Listener and ArgumentCaptor to work.
|
||||
*/
|
||||
private class TestListener : MediaDataManager.Listener {
|
||||
var data: MediaData? = null
|
||||
var key: String? = null
|
||||
var oldKey: String? = null
|
||||
|
||||
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
|
||||
this.key = key
|
||||
this.oldKey = oldKey
|
||||
this.data = data
|
||||
}
|
||||
|
||||
override fun onMediaDataRemoved(key: String) {
|
||||
this.key = key
|
||||
oldKey = null
|
||||
data = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user