Merge "Disable output switcher in resumption controls" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-06-22 23:36:28 +00:00
committed by Android (Google) Code Review
6 changed files with 126 additions and 18 deletions

View File

@@ -57,6 +57,7 @@ import javax.inject.Inject;
*/ */
public class MediaControlPanel { public class MediaControlPanel {
private static final String TAG = "MediaControlPanel"; private static final String TAG = "MediaControlPanel";
private static final float DISABLED_ALPHA = 0.38f;
// Button IDs for QS controls // Button IDs for QS controls
static final int[] ACTION_IDS = { static final int[] ACTION_IDS = {
@@ -267,6 +268,11 @@ public class MediaControlPanel {
mViewHolder.getSeamless().setVisibility(seamlessVisibility); mViewHolder.getSeamless().setVisibility(seamlessVisibility);
expandedSet.setVisibility(seamlessId, seamlessVisibility); expandedSet.setVisibility(seamlessId, seamlessVisibility);
collapsedSet.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) { if (showFallback) {
iconView.setImageDrawable(null); iconView.setImageDrawable(null);
deviceName.setText(null); deviceName.setText(null);

View File

@@ -80,6 +80,11 @@ data class MediaData(
* Action that should be performed to restart a non active session. * Action that should be performed to restart a non active session.
*/ */
var resumeAction: Runnable?, 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.) * Notification key for cancelling a media player after a timeout (when not using resumption.)
*/ */

View File

@@ -323,7 +323,7 @@ class MediaDataManager(
onMediaDataLoaded(packageName, null, MediaData(true, bgColor, appName, onMediaDataLoaded(packageName, null, MediaData(true, bgColor, appName,
null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
packageName, token, appIntent, device = null, active = false, packageName, token, appIntent, device = null, active = false,
resumeAction = resumeAction, notificationKey = packageName, resumeAction = resumeAction, resumption = true, notificationKey = packageName,
hasCheckedForResume = true)) hasCheckedForResume = true))
} }
} }
@@ -542,7 +542,7 @@ class MediaDataManager(
val data = mediaEntries.remove(key)!! val data = mediaEntries.remove(key)!!
val resumeAction = getResumeMediaAction(data.resumeAction!!) val resumeAction = getResumeMediaAction(data.resumeAction!!)
val updated = data.copy(token = null, actions = listOf(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) mediaEntries.put(data.packageName, updated)
// Notify listeners of "new" controls // Notify listeners of "new" controls
val listenersCopy = listeners.toSet() val listenersCopy = listeners.toSet()

View File

@@ -242,4 +242,15 @@ public class MediaControlPanelTest : SysuiTestCase() {
assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
com.android.internal.R.string.ext_media_seamless_action)) 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()
}
} }

View File

@@ -79,8 +79,8 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
mManager.addListener(mListener); mManager.addListener(mListener);
mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, KEY, new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, false,
false); KEY, false);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
} }

View File

@@ -1,6 +1,11 @@
package com.android.systemui.media 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.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper import android.testing.TestableLooper.RunWithLooper
@@ -8,6 +13,9 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager 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 com.google.common.truth.Truth.assertThat
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
@@ -18,12 +26,14 @@ import org.mockito.Mock
import org.mockito.Mockito import org.mockito.Mockito
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
import org.mockito.Mockito.verify 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.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
private const val KEY = "KEY" private const val KEY = "KEY"
private const val PACKAGE_NAME = "com.android.systemui" 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> eq(value: T): T = Mockito.eq(value) ?: value
private fun <T> anyObject(): T { private fun <T> anyObject(): T {
@@ -36,33 +46,47 @@ private fun <T> anyObject(): T {
class MediaDataManagerTest : SysuiTestCase() { class MediaDataManagerTest : SysuiTestCase() {
@Mock lateinit var mediaControllerFactory: MediaControllerFactory @Mock lateinit var mediaControllerFactory: MediaControllerFactory
@Mock lateinit var backgroundExecutor: Executor @Mock lateinit var controller: MediaController
@Mock lateinit var foregroundExecutor: Executor 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 dumpManager: DumpManager
@Mock lateinit var broadcastDispatcher: BroadcastDispatcher @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock lateinit var mediaTimeoutListener: MediaTimeoutListener @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
@Mock lateinit var mediaResumeListener: MediaResumeListener @Mock lateinit var mediaResumeListener: MediaResumeListener
@Mock lateinit var pendingIntent: PendingIntent
@JvmField @Rule val mockito = MockitoJUnit.rule() @JvmField @Rule val mockito = MockitoJUnit.rule()
lateinit var mediaDataManager: MediaDataManager lateinit var mediaDataManager: MediaDataManager
lateinit var mediaNotification: StatusBarNotification lateinit var mediaNotification: StatusBarNotification
@Before @Before
fun setup() { fun setup() {
foregroundExecutor = FakeExecutor(FakeSystemClock())
backgroundExecutor = FakeExecutor(FakeSystemClock())
mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor, mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor,
mediaControllerFactory, broadcastDispatcher, dumpManager, mediaControllerFactory, broadcastDispatcher, dumpManager,
mediaTimeoutListener, mediaResumeListener, useMediaResumption = true, mediaTimeoutListener, mediaResumeListener, useMediaResumption = true,
useQsMediaPlayer = true) useQsMediaPlayer = true)
val sbn = mock(StatusBarNotification::class.java) session = MediaSession(context, "MediaDataManagerTestSession")
val notification = mock(Notification::class.java) mediaNotification = SbnBuilder().run {
whenever(notification.hasMediaSession()).thenReturn(true) setPkg(PACKAGE_NAME)
whenever(notification.notificationStyle).thenReturn(Notification.MediaStyle::class.java) modifyNotification(context).also {
whenever(sbn.notification).thenReturn(notification) it.setSmallIcon(android.R.drawable.ic_media_pause)
whenever(sbn.packageName).thenReturn(PACKAGE_NAME) it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
mediaNotification = sbn }
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 @After
fun tearDown() { fun tearDown() {
session.release()
mediaDataManager.destroy() mediaDataManager.destroy()
} }
@@ -82,7 +106,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test @Test
fun testLoadsMetadataOnBackground() { fun testLoadsMetadataOnBackground() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification) mediaDataManager.onNotificationAdded(KEY, mediaNotification)
verify(backgroundExecutor).execute(anyObject()) assertThat(backgroundExecutor.numPending()).isEqualTo(1)
} }
@Test @Test
@@ -123,4 +147,66 @@ class MediaDataManagerTest : SysuiTestCase() {
verify(listener).onMediaDataRemoved(eq(KEY)) 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
}
}
}