Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fb1beb5c authored by Robert Snoeberger's avatar Robert Snoeberger
Browse files

Update device name on audio info changed

Bug: 168125184
Test: manual - Cast Spotify. Dismiss app in overview. Open Spotify while
still casting. Verify cast icon appears for output switcher.

Change-Id: I134a5a916ac1347016cf56f44b89109c0f5fa2c7
parent 993254c8
Loading
Loading
Loading
Loading
+26 −8
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.systemui.media

import android.content.Context
import android.media.MediaRouter2Manager
import android.media.session.MediaController
import androidx.annotation.AnyThread
@@ -25,7 +24,6 @@ import androidx.annotation.WorkerThread
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -34,11 +32,13 @@ import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject

private const val PLAYBACK_TYPE_UNKNOWN = 0

/**
 * Provides information about the route (ie. device) where playback is occurring.
 */
class MediaDeviceManager @Inject constructor(
    private val context: Context,
    private val controllerFactory: MediaControllerFactory,
    private val localMediaManagerFactory: LocalMediaManagerFactory,
    private val mr2manager: MediaRouter2Manager,
    @Main private val fgExecutor: Executor,
@@ -72,7 +72,7 @@ class MediaDeviceManager @Inject constructor(
        if (entry == null || entry?.token != data.token) {
            entry?.stop()
            val controller = data.token?.let {
                MediaController(context, it)
                controllerFactory.create(it)
            }
            entry = Entry(key, oldKey, controller,
                    localMediaManagerFactory.create(data.packageName))
@@ -123,11 +123,12 @@ class MediaDeviceManager @Inject constructor(
        val oldKey: String?,
        val controller: MediaController?,
        val localMediaManager: LocalMediaManager
    ) : LocalMediaManager.DeviceCallback {
    ) : LocalMediaManager.DeviceCallback, MediaController.Callback() {

        val token
            get() = controller?.sessionToken
        private var started = false
        private var playbackType = PLAYBACK_TYPE_UNKNOWN
        private var current: MediaDevice? = null
            set(value) {
                if (!started || value != field) {
@@ -142,6 +143,8 @@ class MediaDeviceManager @Inject constructor(
        fun start() = bgExecutor.execute {
            localMediaManager.registerCallback(this)
            localMediaManager.startScan()
            playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
            controller?.registerCallback(this)
            updateCurrent()
            started = true
        }
@@ -149,22 +152,37 @@ class MediaDeviceManager @Inject constructor(
        @AnyThread
        fun stop() = bgExecutor.execute {
            started = false
            controller?.unregisterCallback(this)
            localMediaManager.stopScan()
            localMediaManager.unregisterCallback(this)
        }

        fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
            val route = controller?.let {
            val routingSession = controller?.let {
                mr2manager.getRoutingSessionForMediaController(it)
            }
            val selectedRoutes = routingSession?.let {
                mr2manager.getSelectedRoutes(it)
            }
            with(pw) {
                println("    current device is ${current?.name}")
                val type = controller?.playbackInfo?.playbackType
                println("    PlaybackType=$type (1 for local, 2 for remote)")
                println("    route=$route")
                println("    PlaybackType=$type (1 for local, 2 for remote) cached=$playbackType")
                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 {
            updateCurrent()
        }
+46 −28
Original line number Diff line number Diff line
@@ -16,13 +16,12 @@

package com.android.systemui.media

import android.app.Notification
import android.graphics.drawable.Drawable
import android.media.MediaMetadata
import android.media.MediaRouter2Manager
import android.media.RoutingSessionInfo
import android.media.session.MediaController
import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
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 PACKAGE = "PKG"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
private const val DEVICE_NAME = "DEVICE_NAME"
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() {

    private lateinit var manager: MediaDeviceManager
    @Mock private lateinit var controllerFactory: MediaControllerFactory
    @Mock private lateinit var lmmFactory: LocalMediaManagerFactory
    @Mock private lateinit var lmm: LocalMediaManager
    @Mock private lateinit var mr2: MediaRouter2Manager
@@ -78,10 +77,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
    @Mock private lateinit var device: MediaDevice
    @Mock private lateinit var icon: Drawable
    @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 metadataBuilder: MediaMetadata.Builder
    private lateinit var playbackBuilder: PlaybackState.Builder
    private lateinit var notifBuilder: Notification.Builder
    private lateinit var mediaData: MediaData
    @JvmField @Rule val mockito = MockitoJUnit.rule()

@@ -89,8 +87,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
    fun setUp() {
        fakeFgExecutor = FakeExecutor(FakeSystemClock())
        fakeBgExecutor = FakeExecutor(FakeSystemClock())
        manager = MediaDeviceManager(context, lmmFactory, mr2, fakeFgExecutor, fakeBgExecutor,
                dumpster)
        manager = MediaDeviceManager(controllerFactory, lmmFactory, mr2, fakeFgExecutor,
                fakeBgExecutor, dumpster)
        manager.addListener(listener)

        // Configure mocks.
@@ -101,28 +99,13 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
        whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)

        // Create a media sesssion and notification for testing.
        metadataBuilder = MediaMetadata.Builder().apply {
            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()))
        }
        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)
        whenever(controllerFactory.create(session.sessionToken))
                .thenReturn(controller)
    }

    @After
@@ -336,6 +319,41 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
        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 {
        val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
        verify(lmm).registerCallback(captor.capture())