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

Commit 23e36895 authored by Robert Snoeberger's avatar Robert Snoeberger
Browse files

Disable output switcher based on available routing

The MediaRouter2Manager will tell us if there is available route
information. Disable the output switcher when
MediaRouter2Manager#getRoutingSessionForMediaController returns null.

Fixes: 155266917
Test: atest src/com/android/systemui/media/MediaDeviceManager.kt
Test: manual - Player for YouTube cast session still has disabled output
switcher because route information isn't available.

Change-Id: I95af24cf466a83c158350e9b9702d12d5db4861c
parent c83fbdea
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.media.MediaRouter2Manager;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
@@ -210,6 +211,11 @@ public class SystemServicesModule {
        return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
    }

    @Provides
    static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
        return MediaRouter2Manager.getInstance(context);
    }

    @Provides
    @Singleton
    static NetworkScoreManager provideNetworkScoreManager(Context context) {
+5 −14
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.service.media.MediaBrowserService;
@@ -294,14 +293,6 @@ public class MediaControlPanel {
            mActivityStarter.startActivity(intent, false, true /* dismissShade */,
                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        });
        final boolean isRemotePlayback;
        PlaybackInfo playbackInfo = mController.getPlaybackInfo();
        if (playbackInfo != null) {
            isRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
        } else {
            Log.d(TAG, "PlaybackInfo was null. Defaulting to local playback.");
            isRemotePlayback = false;
        }

        ImageView iconView = mViewHolder.getSeamlessIcon();
        TextView deviceName = mViewHolder.getSeamlessText();
@@ -312,18 +303,18 @@ public class MediaControlPanel {
        rect.setStroke(2, deviceName.getCurrentTextColor());
        rect.setColor(Color.TRANSPARENT);

        if (isRemotePlayback) {
        final MediaDeviceData device = data.getDevice();
        if (device != null && !device.getEnabled()) {
            mViewHolder.getSeamless().setEnabled(false);
            // TODO(b/156875717): setEnabled should cause the alpha to change.
            mViewHolder.getSeamless().setAlpha(0.38f);
            iconView.setImageResource(R.drawable.ic_hardware_speaker);
            iconView.setVisibility(View.VISIBLE);
            deviceName.setText(R.string.media_seamless_remote_device);
        } else if (data.getDevice() != null && data.getDevice().getIcon() != null
                && data.getDevice().getName() != null) {
        } else if (device != null) {
            mViewHolder.getSeamless().setEnabled(true);
            mViewHolder.getSeamless().setAlpha(1f);
            Drawable icon = data.getDevice().getIcon();
            Drawable icon = device.getIcon();
            iconView.setVisibility(View.VISIBLE);

            if (icon instanceof AdaptiveIcon) {
@@ -333,7 +324,7 @@ public class MediaControlPanel {
            } else {
                iconView.setImageDrawable(icon);
            }
            deviceName.setText(data.getDevice().getName());
            deviceName.setText(device.getName());
        } else {
            // Reset to default
            Log.w(TAG, "device is null. Not binding output chip.");
+1 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ data class MediaAction(

/** State of the media device. */
data class MediaDeviceData(
    val enabled: Boolean,
    val icon: Drawable?,
    val name: String?
)
+26 −6
Original line number Diff line number Diff line
@@ -16,8 +16,12 @@

package com.android.systemui.media

import android.app.Notification
import android.content.Context
import android.service.notification.StatusBarNotification
import android.media.MediaRouter2Manager
import android.media.session.MediaSession
import android.media.session.MediaController
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.dagger.qualifiers.Main
@@ -32,6 +36,7 @@ import javax.inject.Singleton
class MediaDeviceManager @Inject constructor(
    private val context: Context,
    private val localMediaManagerFactory: LocalMediaManagerFactory,
    private val mr2manager: MediaRouter2Manager,
    private val featureFlag: MediaFeatureFlag,
    @Main private val fgExecutor: Executor
) {
@@ -52,7 +57,10 @@ class MediaDeviceManager @Inject constructor(
        if (featureFlag.enabled && isMediaNotification(sbn)) {
            var tok = entries[key]
            if (tok == null) {
                tok = Token(key, localMediaManagerFactory.create(sbn.packageName))
                val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
                        as MediaSession.Token?
                val controller = MediaController(context, token)
                tok = Token(key, controller, localMediaManagerFactory.create(sbn.packageName))
                entries[key] = tok
                tok.start()
            }
@@ -72,7 +80,8 @@ class MediaDeviceManager @Inject constructor(
    }

    private fun processDevice(key: String, device: MediaDevice?) {
        val data = MediaDeviceData(device?.icon, device?.name)
        val enabled = device != null
        val data = MediaDeviceData(enabled, device?.icon, device?.name)
        listeners.forEach {
            it.onMediaDeviceChanged(key, data)
        }
@@ -87,11 +96,13 @@ class MediaDeviceManager @Inject constructor(

    private inner class Token(
        val key: String,
        val controller: MediaController,
        val localMediaManager: LocalMediaManager
    ) : LocalMediaManager.DeviceCallback {
        private var started = false
        private var current: MediaDevice? = null
            set(value) {
                if (value != field) {
                if (!started || value != field) {
                    field = value
                    processDevice(key, value)
                }
@@ -99,19 +110,28 @@ class MediaDeviceManager @Inject constructor(
        fun start() {
            localMediaManager.registerCallback(this)
            localMediaManager.startScan()
            current = localMediaManager.getCurrentConnectedDevice()
            updateCurrent()
            started = true
        }
        fun stop() {
            started = false
            localMediaManager.stopScan()
            localMediaManager.unregisterCallback(this)
        }
        override fun onDeviceListUpdate(devices: List<MediaDevice>?) = fgExecutor.execute {
            current = localMediaManager.getCurrentConnectedDevice()
            updateCurrent()
        }
        override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) {
            fgExecutor.execute {
                current = device
                updateCurrent()
            }
        }
        private fun updateCurrent() {
            val device = localMediaManager.getCurrentConnectedDevice()
            val route = mr2manager.getRoutingSessionForMediaController(controller)
            // If we get a null route, then don't trust the device. Just set to null to disable the
            // output switcher chip.
            current = if (route != null) device else null
        }
    }
}
+37 −5
Original line number Diff line number Diff line
@@ -98,6 +98,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
    private lateinit var action4: ImageButton

    private lateinit var session: MediaSession
    private val device = MediaDeviceData(true, null, DEVICE_NAME)
    private val disabledDevice = MediaDeviceData(false, null, null)

    @Before
    fun setUp() {
@@ -181,7 +183,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
    @Test
    fun bindWhenUnattached() {
        val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                emptyList(), PACKAGE, null, null, MediaDeviceData(null, DEVICE_NAME))
                emptyList(), PACKAGE, null, null, device)
        player.bind(state)
        assertThat(player.isPlaying()).isFalse()
    }
@@ -190,8 +192,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
    fun bindText() {
        player.attach(holder)
        val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                emptyList(), PACKAGE, session.getSessionToken(), null,
                MediaDeviceData(null, DEVICE_NAME))
                emptyList(), PACKAGE, session.getSessionToken(), null, device)
        player.bind(state)
        assertThat(appName.getText()).isEqualTo(APP)
        assertThat(titleText.getText()).isEqualTo(TITLE)
@@ -202,9 +203,40 @@ public class MediaControlPanelTest : SysuiTestCase() {
    fun bindBackgroundColor() {
        player.attach(holder)
        val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                emptyList(), PACKAGE, session.getSessionToken(), null,
                MediaDeviceData(null, DEVICE_NAME))
                emptyList(), PACKAGE, session.getSessionToken(), null, device)
        player.bind(state)
        assertThat(background.getBackgroundTintList()).isEqualTo(ColorStateList.valueOf(BG_COLOR))
    }

    @Test
    fun bindDevice() {
        player.attach(holder)
        val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                emptyList(), PACKAGE, session.getSessionToken(), null, device)
        player.bind(state)
        assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
        assertThat(seamless.isEnabled()).isTrue()
    }

    @Test
    fun bindDisabledDevice() {
        player.attach(holder)
        val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice)
        player.bind(state)
        assertThat(seamless.isEnabled()).isFalse()
        assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
                R.string.media_seamless_remote_device))
    }

    @Test
    fun bindNullDevice() {
        player.attach(holder)
        val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                emptyList(), PACKAGE, session.getSessionToken(), null, null)
        player.bind(state)
        assertThat(seamless.isEnabled()).isTrue()
        assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
                com.android.internal.R.string.ext_media_seamless_action))
    }
}
Loading