Loading packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +30 −23 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; Loading @@ -51,6 +52,34 @@ public class PhoneMediaDevice extends MediaDevice { private final DeviceIconUtil mDeviceIconUtil; /** Returns the device name for the given {@code routeInfo}. */ public static String getSystemRouteNameFromType( @NonNull Context context, @NonNull MediaRoute2Info routeInfo) { CharSequence name; switch (routeInfo.getType()) { case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: case TYPE_USB_ACCESSORY: name = context.getString(R.string.media_transfer_wired_usb_device_name); break; case TYPE_DOCK: name = context.getString(R.string.media_transfer_dock_speaker_device_name); break; case TYPE_BUILTIN_SPEAKER: name = context.getString(R.string.media_transfer_this_device_name); break; case TYPE_HDMI: name = context.getString(R.string.media_transfer_external_device_name); break; default: name = context.getString(R.string.media_transfer_default_device_name); break; } return name.toString(); } PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) { this(context, info, packageName, null); } Loading @@ -69,29 +98,7 @@ public class PhoneMediaDevice extends MediaDevice { @SuppressWarnings("NewApi") @Override public String getName() { CharSequence name; switch (mRouteInfo.getType()) { case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: case TYPE_USB_ACCESSORY: name = mContext.getString(R.string.media_transfer_wired_usb_device_name); break; case TYPE_DOCK: name = mContext.getString(R.string.media_transfer_dock_speaker_device_name); break; case TYPE_BUILTIN_SPEAKER: name = mContext.getString(R.string.media_transfer_this_device_name); break; case TYPE_HDMI: name = mContext.getString(R.string.media_transfer_external_device_name); break; default: name = mContext.getString(R.string.media_transfer_default_device_name); break; } return name.toString(); return getSystemRouteNameFromType(mContext, mRouteInfo); } @Override Loading packages/SystemUI/src/com/android/systemui/flags/Flags.kt +3 −0 Original line number Diff line number Diff line Loading @@ -473,6 +473,9 @@ object Flags { // TODO(b/270437894): Tracking Bug val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume") // TODO(b/304506662): Tracking Bug val MEDIA_DEVICE_NAME_FIX = unreleasedFlag("media_device_name_fix", teamfood = true) // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging") Loading packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt +67 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.Context import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController import android.text.TextUtils import android.util.Log Loading @@ -31,17 +32,20 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.Dumpable import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import java.io.PrintWriter import java.util.concurrent.Executor Loading @@ -64,7 +68,8 @@ constructor( private val localBluetoothManager: LocalBluetoothManager?, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, dumpManager: DumpManager dumpManager: DumpManager, private val featureFlags: FeatureFlagsClassic, ) : MediaDataManager.Listener, Dumpable { private val listeners: MutableSet<Listener> = mutableSetOf() Loading Loading @@ -215,6 +220,7 @@ constructor( println(" volumeControlId=$volumeControlId cached= $playbackVolumeControlId") println(" routingSession=$routingSession") println(" selectedRoutes=$selectedRoutes") println(" currentConnectedDevice=${localMediaManager.currentConnectedDevice}") } } Loading Loading @@ -348,15 +354,15 @@ constructor( } val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } val routingSession = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } // If we have a controller but get a null route, then don't trust the device val enabled = device != null && (controller == null || route != null) val name = if (controller == null || route != null) { route?.name?.toString() ?: device?.name } else { null val enabled = device != null && (controller == null || routingSession != null) val name = getDeviceName(device, routingSession) if (DEBUG) { Log.d(TAG, "new device name $name") } current = MediaDeviceData( Loading @@ -369,6 +375,57 @@ constructor( } } /** Return a display name for the current device / route, or null if not possible */ private fun getDeviceName( device: MediaDevice?, routingSession: RoutingSessionInfo?, ): String? { val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) } if (DEBUG) { Log.d( TAG, "device is $device, controller $controller," + " routingSession ${routingSession?.name}" + " or ${selectedRoutes?.firstOrNull()?.name}" ) } if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) { if (controller == null || routingSession != null) { return routingSession?.name?.toString() ?: device?.name } return null } if (controller == null) { // In resume state, we don't have a controller - just use the device name return device?.name } if (routingSession == null) { // This happens when casting from apps that do not support MediaRouter2 // The output switcher can't show anything useful here, so set to null return null } // If this is a user route (app / cast provided), use the provided name if (!routingSession.isSystemSession) { return routingSession.name?.toString() ?: device?.name } selectedRoutes?.firstOrNull()?.let { if (device is PhoneMediaDevice) { // Get the (localized) name for this phone device return PhoneMediaDevice.getSystemRouteNameFromType(context, it) } else { // If it's another type of device (in practice, Bluetooth), use the route name return it.name.toString() } } return null } private fun isLeAudioBroadcastEnabled(): Boolean { if (localBluetoothManager != null) { val profileManager = localBluetoothManager.profileManager Loading packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt +156 −8 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.media.MediaRoute2Info import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController Loading @@ -34,15 +35,18 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.LocalBluetoothProfileManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.systemui.res.R import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq Loading Loading @@ -95,6 +99,7 @@ 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 selectedRoute: MediaRoute2Info @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo @Mock private lateinit var configurationController: ConfigurationController Loading @@ -107,6 +112,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var session: MediaSession private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() private val featureFlags = FakeFeatureFlagsClassic() @Before fun setUp() { Loading @@ -124,7 +130,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { localBluetoothManager, fakeFgExecutor, fakeBgExecutor, dumpster dumpster, featureFlags, ) manager.addListener(listener) Loading @@ -143,6 +150,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken) whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller) setupLeAudioConfiguration(false) featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false) } @After Loading Loading @@ -454,9 +462,54 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test fun mr2ReturnsRouteWithNullName_useLocalDeviceName() { fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // When the routing session name is null, and is a system session for a PhoneMediaDevice val phoneDevice = mock(PhoneMediaDevice::class.java) whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) whenever(route.isSystemSession).thenReturn(true) whenever(route.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // Then the device name is the PhoneMediaDevice string val data = captureDeviceData(KEY) assertThat(data.name) .isEqualTo( context.getString(com.android.settingslib.R.string.media_transfer_this_device_name) ) } @Test fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // When the routing session does not have a name, and is a system session whenever(route.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) whenever(route.isSystemSession).thenReturn(true) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // Then the device name is the selected route name val data = captureDeviceData(KEY) assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) } @Test fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { // GIVEN that MR2Manager returns a routing session that does not have a name whenever(route.name).thenReturn(null) whenever(route.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() Loading Loading @@ -672,13 +725,108 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.showBroadcastButton).isFalse() } fun captureCallback(): LocalMediaManager.DeviceCallback { // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled @Test fun loadMediaDataWithNullToken_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(DEVICE_NAME) } @Test fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) manager.onMediaDataLoaded(KEY, null, mediaData) // Run and reset the executors and listeners so we only focus on new events. fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() reset(listener) // Ensure we'll get device info when using the address val fullMediaDevice = mock(MediaDevice::class.java) val address = "fakeAddress" val nameFromDevice = "nameFromDevice" val iconFromDevice = mock(Drawable::class.java) whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice) whenever(fullMediaDevice.name).thenReturn(nameFromDevice) whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice) // WHEN the about-to-connect device changes to non-null val deviceCallback = captureCallback() val nameFromParam = "nameFromParam" val iconFromParam = mock(Drawable::class.java) deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam) assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) // THEN the about-to-connect device based on the address is returned val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(nameFromDevice) assertThat(data.name).isNotEqualTo(nameFromParam) assertThat(data.icon).isEqualTo(iconFromDevice) assertThat(data.icon).isNotEqualTo(iconFromParam) } @Test fun deviceNameFromMR2RouteInfo_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // GIVEN that MR2Manager returns a valid routing session whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // THEN it uses the route name (instead of device name) val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) } @Test fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // GIVEN that MR2Manager returns null for routing session whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // THEN the device is disabled and name is set to null val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() assertThat(data.name).isNull() } @Test fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // GIVEN that MR2Manager returns a routing session that does not have a name whenever(route.name).thenReturn(null) whenever(route.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // THEN the device is enabled and uses the current connected device name val data = captureDeviceData(KEY) assertThat(data.name).isEqualTo(DEVICE_NAME) assertThat(data.enabled).isTrue() } // End duplicate tests private fun captureCallback(): LocalMediaManager.DeviceCallback { val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) verify(lmm).registerCallback(captor.capture()) return captor.getValue() } fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { private fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback { override fun onBroadcastStarted(reason: Int, broadcastId: Int) {} Loading @@ -699,7 +847,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { return callback } fun setupLeAudioConfiguration(isLeAudio: Boolean) { private fun setupLeAudioConfiguration(isLeAudio: Boolean) { whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager) whenever(localBluetoothProfileManager.leAudioBroadcastProfile) .thenReturn(localBluetoothLeBroadcast) Loading @@ -707,7 +855,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME) } fun setupBroadcastPackage(currentName: String) { private fun setupBroadcastPackage(currentName: String) { whenever(lmm.packageName).thenReturn(PACKAGE) whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) .thenReturn(applicationInfo) Loading @@ -715,7 +863,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { context.setMockPackageManager(packageManager) } fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { private fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture()) return captor.getValue() Loading Loading
packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +30 −23 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; Loading @@ -51,6 +52,34 @@ public class PhoneMediaDevice extends MediaDevice { private final DeviceIconUtil mDeviceIconUtil; /** Returns the device name for the given {@code routeInfo}. */ public static String getSystemRouteNameFromType( @NonNull Context context, @NonNull MediaRoute2Info routeInfo) { CharSequence name; switch (routeInfo.getType()) { case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: case TYPE_USB_ACCESSORY: name = context.getString(R.string.media_transfer_wired_usb_device_name); break; case TYPE_DOCK: name = context.getString(R.string.media_transfer_dock_speaker_device_name); break; case TYPE_BUILTIN_SPEAKER: name = context.getString(R.string.media_transfer_this_device_name); break; case TYPE_HDMI: name = context.getString(R.string.media_transfer_external_device_name); break; default: name = context.getString(R.string.media_transfer_default_device_name); break; } return name.toString(); } PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) { this(context, info, packageName, null); } Loading @@ -69,29 +98,7 @@ public class PhoneMediaDevice extends MediaDevice { @SuppressWarnings("NewApi") @Override public String getName() { CharSequence name; switch (mRouteInfo.getType()) { case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: case TYPE_USB_DEVICE: case TYPE_USB_HEADSET: case TYPE_USB_ACCESSORY: name = mContext.getString(R.string.media_transfer_wired_usb_device_name); break; case TYPE_DOCK: name = mContext.getString(R.string.media_transfer_dock_speaker_device_name); break; case TYPE_BUILTIN_SPEAKER: name = mContext.getString(R.string.media_transfer_this_device_name); break; case TYPE_HDMI: name = mContext.getString(R.string.media_transfer_external_device_name); break; default: name = mContext.getString(R.string.media_transfer_default_device_name); break; } return name.toString(); return getSystemRouteNameFromType(mContext, mRouteInfo); } @Override Loading
packages/SystemUI/src/com/android/systemui/flags/Flags.kt +3 −0 Original line number Diff line number Diff line Loading @@ -473,6 +473,9 @@ object Flags { // TODO(b/270437894): Tracking Bug val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume") // TODO(b/304506662): Tracking Bug val MEDIA_DEVICE_NAME_FIX = unreleasedFlag("media_device_name_fix", teamfood = true) // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging") Loading
packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt +67 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.Context import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController import android.text.TextUtils import android.util.Log Loading @@ -31,17 +32,20 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.Dumpable import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import java.io.PrintWriter import java.util.concurrent.Executor Loading @@ -64,7 +68,8 @@ constructor( private val localBluetoothManager: LocalBluetoothManager?, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, dumpManager: DumpManager dumpManager: DumpManager, private val featureFlags: FeatureFlagsClassic, ) : MediaDataManager.Listener, Dumpable { private val listeners: MutableSet<Listener> = mutableSetOf() Loading Loading @@ -215,6 +220,7 @@ constructor( println(" volumeControlId=$volumeControlId cached= $playbackVolumeControlId") println(" routingSession=$routingSession") println(" selectedRoutes=$selectedRoutes") println(" currentConnectedDevice=${localMediaManager.currentConnectedDevice}") } } Loading Loading @@ -348,15 +354,15 @@ constructor( } val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } val routingSession = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } // If we have a controller but get a null route, then don't trust the device val enabled = device != null && (controller == null || route != null) val name = if (controller == null || route != null) { route?.name?.toString() ?: device?.name } else { null val enabled = device != null && (controller == null || routingSession != null) val name = getDeviceName(device, routingSession) if (DEBUG) { Log.d(TAG, "new device name $name") } current = MediaDeviceData( Loading @@ -369,6 +375,57 @@ constructor( } } /** Return a display name for the current device / route, or null if not possible */ private fun getDeviceName( device: MediaDevice?, routingSession: RoutingSessionInfo?, ): String? { val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) } if (DEBUG) { Log.d( TAG, "device is $device, controller $controller," + " routingSession ${routingSession?.name}" + " or ${selectedRoutes?.firstOrNull()?.name}" ) } if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) { if (controller == null || routingSession != null) { return routingSession?.name?.toString() ?: device?.name } return null } if (controller == null) { // In resume state, we don't have a controller - just use the device name return device?.name } if (routingSession == null) { // This happens when casting from apps that do not support MediaRouter2 // The output switcher can't show anything useful here, so set to null return null } // If this is a user route (app / cast provided), use the provided name if (!routingSession.isSystemSession) { return routingSession.name?.toString() ?: device?.name } selectedRoutes?.firstOrNull()?.let { if (device is PhoneMediaDevice) { // Get the (localized) name for this phone device return PhoneMediaDevice.getSystemRouteNameFromType(context, it) } else { // If it's another type of device (in practice, Bluetooth), use the route name return it.name.toString() } } return null } private fun isLeAudioBroadcastEnabled(): Boolean { if (localBluetoothManager != null) { val profileManager = localBluetoothManager.profileManager Loading
packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt +156 −8 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.media.MediaRoute2Info import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController Loading @@ -34,15 +35,18 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.LocalBluetoothProfileManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.systemui.res.R import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq Loading Loading @@ -95,6 +99,7 @@ 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 selectedRoute: MediaRoute2Info @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo @Mock private lateinit var configurationController: ConfigurationController Loading @@ -107,6 +112,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var session: MediaSession private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() private val featureFlags = FakeFeatureFlagsClassic() @Before fun setUp() { Loading @@ -124,7 +130,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { localBluetoothManager, fakeFgExecutor, fakeBgExecutor, dumpster dumpster, featureFlags, ) manager.addListener(listener) Loading @@ -143,6 +150,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken) whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller) setupLeAudioConfiguration(false) featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false) } @After Loading Loading @@ -454,9 +462,54 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test fun mr2ReturnsRouteWithNullName_useLocalDeviceName() { fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // When the routing session name is null, and is a system session for a PhoneMediaDevice val phoneDevice = mock(PhoneMediaDevice::class.java) whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) whenever(route.isSystemSession).thenReturn(true) whenever(route.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // Then the device name is the PhoneMediaDevice string val data = captureDeviceData(KEY) assertThat(data.name) .isEqualTo( context.getString(com.android.settingslib.R.string.media_transfer_this_device_name) ) } @Test fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // When the routing session does not have a name, and is a system session whenever(route.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) whenever(route.isSystemSession).thenReturn(true) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // Then the device name is the selected route name val data = captureDeviceData(KEY) assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) } @Test fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { // GIVEN that MR2Manager returns a routing session that does not have a name whenever(route.name).thenReturn(null) whenever(route.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() Loading Loading @@ -672,13 +725,108 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.showBroadcastButton).isFalse() } fun captureCallback(): LocalMediaManager.DeviceCallback { // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled @Test fun loadMediaDataWithNullToken_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(DEVICE_NAME) } @Test fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) manager.onMediaDataLoaded(KEY, null, mediaData) // Run and reset the executors and listeners so we only focus on new events. fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() reset(listener) // Ensure we'll get device info when using the address val fullMediaDevice = mock(MediaDevice::class.java) val address = "fakeAddress" val nameFromDevice = "nameFromDevice" val iconFromDevice = mock(Drawable::class.java) whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice) whenever(fullMediaDevice.name).thenReturn(nameFromDevice) whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice) // WHEN the about-to-connect device changes to non-null val deviceCallback = captureCallback() val nameFromParam = "nameFromParam" val iconFromParam = mock(Drawable::class.java) deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam) assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) // THEN the about-to-connect device based on the address is returned val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(nameFromDevice) assertThat(data.name).isNotEqualTo(nameFromParam) assertThat(data.icon).isEqualTo(iconFromDevice) assertThat(data.icon).isNotEqualTo(iconFromParam) } @Test fun deviceNameFromMR2RouteInfo_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // GIVEN that MR2Manager returns a valid routing session whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // THEN it uses the route name (instead of device name) val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) } @Test fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // GIVEN that MR2Manager returns null for routing session whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // THEN the device is disabled and name is set to null val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() assertThat(data.name).isNull() } @Test fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() { featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // GIVEN that MR2Manager returns a routing session that does not have a name whenever(route.name).thenReturn(null) whenever(route.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() // THEN the device is enabled and uses the current connected device name val data = captureDeviceData(KEY) assertThat(data.name).isEqualTo(DEVICE_NAME) assertThat(data.enabled).isTrue() } // End duplicate tests private fun captureCallback(): LocalMediaManager.DeviceCallback { val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) verify(lmm).registerCallback(captor.capture()) return captor.getValue() } fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { private fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback { override fun onBroadcastStarted(reason: Int, broadcastId: Int) {} Loading @@ -699,7 +847,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { return callback } fun setupLeAudioConfiguration(isLeAudio: Boolean) { private fun setupLeAudioConfiguration(isLeAudio: Boolean) { whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager) whenever(localBluetoothProfileManager.leAudioBroadcastProfile) .thenReturn(localBluetoothLeBroadcast) Loading @@ -707,7 +855,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME) } fun setupBroadcastPackage(currentName: String) { private fun setupBroadcastPackage(currentName: String) { whenever(lmm.packageName).thenReturn(PACKAGE) whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) .thenReturn(applicationInfo) Loading @@ -715,7 +863,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { context.setMockPackageManager(packageManager) } fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { private fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture()) return captor.getValue() Loading