Merge "Update device name on audio info changed" into rvc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
a51344f792
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.android.systemui.media
|
package com.android.systemui.media
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.media.MediaRouter2Manager
|
import android.media.MediaRouter2Manager
|
||||||
import android.media.session.MediaController
|
import android.media.session.MediaController
|
||||||
import androidx.annotation.AnyThread
|
import androidx.annotation.AnyThread
|
||||||
@@ -24,20 +23,22 @@ import androidx.annotation.MainThread
|
|||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.android.settingslib.media.LocalMediaManager
|
import com.android.settingslib.media.LocalMediaManager
|
||||||
import com.android.settingslib.media.MediaDevice
|
import com.android.settingslib.media.MediaDevice
|
||||||
|
import com.android.systemui.Dumpable
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.Dumpable
|
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
import java.io.FileDescriptor
|
import java.io.FileDescriptor
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val PLAYBACK_TYPE_UNKNOWN = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides information about the route (ie. device) where playback is occurring.
|
* Provides information about the route (ie. device) where playback is occurring.
|
||||||
*/
|
*/
|
||||||
class MediaDeviceManager @Inject constructor(
|
class MediaDeviceManager @Inject constructor(
|
||||||
private val context: Context,
|
private val controllerFactory: MediaControllerFactory,
|
||||||
private val localMediaManagerFactory: LocalMediaManagerFactory,
|
private val localMediaManagerFactory: LocalMediaManagerFactory,
|
||||||
private val mr2manager: MediaRouter2Manager,
|
private val mr2manager: MediaRouter2Manager,
|
||||||
@Main private val fgExecutor: Executor,
|
@Main private val fgExecutor: Executor,
|
||||||
@@ -71,7 +72,7 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
if (entry == null || entry?.token != data.token) {
|
if (entry == null || entry?.token != data.token) {
|
||||||
entry?.stop()
|
entry?.stop()
|
||||||
val controller = data.token?.let {
|
val controller = data.token?.let {
|
||||||
MediaController(context, it)
|
controllerFactory.create(it)
|
||||||
}
|
}
|
||||||
entry = Entry(key, oldKey, controller,
|
entry = Entry(key, oldKey, controller,
|
||||||
localMediaManagerFactory.create(data.packageName))
|
localMediaManagerFactory.create(data.packageName))
|
||||||
@@ -122,11 +123,12 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
val oldKey: String?,
|
val oldKey: String?,
|
||||||
val controller: MediaController?,
|
val controller: MediaController?,
|
||||||
val localMediaManager: LocalMediaManager
|
val localMediaManager: LocalMediaManager
|
||||||
) : LocalMediaManager.DeviceCallback {
|
) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
|
||||||
|
|
||||||
val token
|
val token
|
||||||
get() = controller?.sessionToken
|
get() = controller?.sessionToken
|
||||||
private var started = false
|
private var started = false
|
||||||
|
private var playbackType = PLAYBACK_TYPE_UNKNOWN
|
||||||
private var current: MediaDevice? = null
|
private var current: MediaDevice? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
if (!started || value != field) {
|
if (!started || value != field) {
|
||||||
@@ -141,6 +143,8 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
fun start() = bgExecutor.execute {
|
fun start() = bgExecutor.execute {
|
||||||
localMediaManager.registerCallback(this)
|
localMediaManager.registerCallback(this)
|
||||||
localMediaManager.startScan()
|
localMediaManager.startScan()
|
||||||
|
playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
|
||||||
|
controller?.registerCallback(this)
|
||||||
updateCurrent()
|
updateCurrent()
|
||||||
started = true
|
started = true
|
||||||
}
|
}
|
||||||
@@ -148,22 +152,37 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
@AnyThread
|
@AnyThread
|
||||||
fun stop() = bgExecutor.execute {
|
fun stop() = bgExecutor.execute {
|
||||||
started = false
|
started = false
|
||||||
|
controller?.unregisterCallback(this)
|
||||||
localMediaManager.stopScan()
|
localMediaManager.stopScan()
|
||||||
localMediaManager.unregisterCallback(this)
|
localMediaManager.unregisterCallback(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
|
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
|
||||||
val route = controller?.let {
|
val routingSession = controller?.let {
|
||||||
mr2manager.getRoutingSessionForMediaController(it)
|
mr2manager.getRoutingSessionForMediaController(it)
|
||||||
}
|
}
|
||||||
|
val selectedRoutes = routingSession?.let {
|
||||||
|
mr2manager.getSelectedRoutes(it)
|
||||||
|
}
|
||||||
with(pw) {
|
with(pw) {
|
||||||
println(" current device is ${current?.name}")
|
println(" current device is ${current?.name}")
|
||||||
val type = controller?.playbackInfo?.playbackType
|
val type = controller?.playbackInfo?.playbackType
|
||||||
println(" PlaybackType=$type (1 for local, 2 for remote)")
|
println(" PlaybackType=$type (1 for local, 2 for remote) cached=$playbackType")
|
||||||
println(" route=$route")
|
println(" routingSession=$routingSession")
|
||||||
|
println(" selectedRoutes=$selectedRoutes")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
|
||||||
|
val newPlaybackType = info?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
|
||||||
|
if (newPlaybackType == playbackType) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
playbackType = newPlaybackType
|
||||||
|
updateCurrent()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDeviceListUpdate(devices: List<MediaDevice>?) = bgExecutor.execute {
|
override fun onDeviceListUpdate(devices: List<MediaDevice>?) = bgExecutor.execute {
|
||||||
updateCurrent()
|
updateCurrent()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
package com.android.systemui.media
|
package com.android.systemui.media
|
||||||
|
|
||||||
import android.app.Notification
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.media.MediaMetadata
|
|
||||||
import android.media.MediaRouter2Manager
|
import android.media.MediaRouter2Manager
|
||||||
import android.media.RoutingSessionInfo
|
import android.media.RoutingSessionInfo
|
||||||
|
import android.media.session.MediaController
|
||||||
|
import android.media.session.MediaController.PlaybackInfo
|
||||||
import android.media.session.MediaSession
|
import android.media.session.MediaSession
|
||||||
import android.media.session.PlaybackState
|
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
@@ -55,7 +54,6 @@ private const val KEY = "TEST_KEY"
|
|||||||
private const val KEY_OLD = "TEST_KEY_OLD"
|
private const val KEY_OLD = "TEST_KEY_OLD"
|
||||||
private const val PACKAGE = "PKG"
|
private const val PACKAGE = "PKG"
|
||||||
private const val SESSION_KEY = "SESSION_KEY"
|
private const val SESSION_KEY = "SESSION_KEY"
|
||||||
private const val SESSION_ARTIST = "SESSION_ARTIST"
|
|
||||||
private const val SESSION_TITLE = "SESSION_TITLE"
|
private const val SESSION_TITLE = "SESSION_TITLE"
|
||||||
private const val DEVICE_NAME = "DEVICE_NAME"
|
private const val DEVICE_NAME = "DEVICE_NAME"
|
||||||
private const val USER_ID = 0
|
private const val USER_ID = 0
|
||||||
@@ -68,6 +66,7 @@ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
|||||||
public class MediaDeviceManagerTest : SysuiTestCase() {
|
public class MediaDeviceManagerTest : SysuiTestCase() {
|
||||||
|
|
||||||
private lateinit var manager: MediaDeviceManager
|
private lateinit var manager: MediaDeviceManager
|
||||||
|
@Mock private lateinit var controllerFactory: MediaControllerFactory
|
||||||
@Mock private lateinit var lmmFactory: LocalMediaManagerFactory
|
@Mock private lateinit var lmmFactory: LocalMediaManagerFactory
|
||||||
@Mock private lateinit var lmm: LocalMediaManager
|
@Mock private lateinit var lmm: LocalMediaManager
|
||||||
@Mock private lateinit var mr2: MediaRouter2Manager
|
@Mock private lateinit var mr2: MediaRouter2Manager
|
||||||
@@ -78,10 +77,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var device: MediaDevice
|
@Mock private lateinit var device: MediaDevice
|
||||||
@Mock private lateinit var icon: Drawable
|
@Mock private lateinit var icon: Drawable
|
||||||
@Mock private lateinit var route: RoutingSessionInfo
|
@Mock private lateinit var route: RoutingSessionInfo
|
||||||
|
@Mock private lateinit var controller: MediaController
|
||||||
|
@Mock private lateinit var playbackInfo: PlaybackInfo
|
||||||
private lateinit var session: MediaSession
|
private lateinit var session: MediaSession
|
||||||
private lateinit var metadataBuilder: MediaMetadata.Builder
|
|
||||||
private lateinit var playbackBuilder: PlaybackState.Builder
|
|
||||||
private lateinit var notifBuilder: Notification.Builder
|
|
||||||
private lateinit var mediaData: MediaData
|
private lateinit var mediaData: MediaData
|
||||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||||
|
|
||||||
@@ -89,8 +87,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
fakeFgExecutor = FakeExecutor(FakeSystemClock())
|
fakeFgExecutor = FakeExecutor(FakeSystemClock())
|
||||||
fakeBgExecutor = FakeExecutor(FakeSystemClock())
|
fakeBgExecutor = FakeExecutor(FakeSystemClock())
|
||||||
manager = MediaDeviceManager(context, lmmFactory, mr2, fakeFgExecutor, fakeBgExecutor,
|
manager = MediaDeviceManager(controllerFactory, lmmFactory, mr2, fakeFgExecutor,
|
||||||
dumpster)
|
fakeBgExecutor, dumpster)
|
||||||
manager.addListener(listener)
|
manager.addListener(listener)
|
||||||
|
|
||||||
// Configure mocks.
|
// Configure mocks.
|
||||||
@@ -101,28 +99,13 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)
|
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)
|
||||||
|
|
||||||
// Create a media sesssion and notification for testing.
|
// Create a media sesssion and notification for testing.
|
||||||
metadataBuilder = MediaMetadata.Builder().apply {
|
session = MediaSession(context, SESSION_KEY)
|
||||||
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
|
|
||||||
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
|
|
||||||
}
|
|
||||||
playbackBuilder = PlaybackState.Builder().apply {
|
|
||||||
setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
|
|
||||||
setActions(PlaybackState.ACTION_PLAY)
|
|
||||||
}
|
|
||||||
session = MediaSession(context, SESSION_KEY).apply {
|
|
||||||
setMetadata(metadataBuilder.build())
|
|
||||||
setPlaybackState(playbackBuilder.build())
|
|
||||||
}
|
|
||||||
session.setActive(true)
|
|
||||||
notifBuilder = Notification.Builder(context, "NONE").apply {
|
|
||||||
setContentTitle(SESSION_TITLE)
|
|
||||||
setContentText(SESSION_ARTIST)
|
|
||||||
setSmallIcon(android.R.drawable.ic_media_pause)
|
|
||||||
setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
|
|
||||||
}
|
|
||||||
mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
|
mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
|
||||||
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
|
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
|
||||||
device = null, active = true, resumeAction = null)
|
device = null, active = true, resumeAction = null)
|
||||||
|
whenever(controllerFactory.create(session.sessionToken))
|
||||||
|
.thenReturn(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -336,6 +319,41 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
assertThat(data.icon).isNull()
|
assertThat(data.icon).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun audioInfoChanged() {
|
||||||
|
whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
|
||||||
|
whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
|
||||||
|
// GIVEN a controller with local playback type
|
||||||
|
manager.onMediaDataLoaded(KEY, null, mediaData)
|
||||||
|
fakeBgExecutor.runAllReady()
|
||||||
|
fakeFgExecutor.runAllReady()
|
||||||
|
reset(mr2)
|
||||||
|
// WHEN onAudioInfoChanged fires with remote playback type
|
||||||
|
whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
|
||||||
|
val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
|
||||||
|
verify(controller).registerCallback(captor.capture())
|
||||||
|
captor.value.onAudioInfoChanged(playbackInfo)
|
||||||
|
// THEN the route is checked
|
||||||
|
verify(mr2).getRoutingSessionForMediaController(eq(controller))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun audioInfoHasntChanged() {
|
||||||
|
whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
|
||||||
|
whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
|
||||||
|
// GIVEN a controller with remote playback type
|
||||||
|
manager.onMediaDataLoaded(KEY, null, mediaData)
|
||||||
|
fakeBgExecutor.runAllReady()
|
||||||
|
fakeFgExecutor.runAllReady()
|
||||||
|
reset(mr2)
|
||||||
|
// WHEN onAudioInfoChanged fires with remote playback type
|
||||||
|
val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
|
||||||
|
verify(controller).registerCallback(captor.capture())
|
||||||
|
captor.value.onAudioInfoChanged(playbackInfo)
|
||||||
|
// THEN the route is not checked
|
||||||
|
verify(mr2, never()).getRoutingSessionForMediaController(eq(controller))
|
||||||
|
}
|
||||||
|
|
||||||
fun captureCallback(): LocalMediaManager.DeviceCallback {
|
fun captureCallback(): LocalMediaManager.DeviceCallback {
|
||||||
val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
|
val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
|
||||||
verify(lmm).registerCallback(captor.capture())
|
verify(lmm).registerCallback(captor.capture())
|
||||||
|
|||||||
Reference in New Issue
Block a user