diff options
5 files changed, 138 insertions, 41 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index c2e36b79b753..e1a2e8daf971 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -28,6 +28,7 @@ import android.text.TextUtils; import android.util.Log; import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -239,13 +240,24 @@ public class LocalMediaManager implements BluetoothCallback { /** * Dispatch a change in the about-to-connect device. See - * {@link DeviceCallback#onAboutToConnectDeviceChanged} for more information. + * {@link DeviceCallback#onAboutToConnectDeviceAdded} for more information. */ - public void dispatchAboutToConnectDeviceChanged( - @Nullable String deviceName, + public void dispatchAboutToConnectDeviceAdded( + @NonNull String deviceAddress, + @NonNull String deviceName, @Nullable Drawable deviceIcon) { for (DeviceCallback callback : getCallbacks()) { - callback.onAboutToConnectDeviceChanged(deviceName, deviceIcon); + callback.onAboutToConnectDeviceAdded(deviceAddress, deviceName, deviceIcon); + } + } + + /** + * Dispatch a change in the about-to-connect device. See + * {@link DeviceCallback#onAboutToConnectDeviceRemoved} for more information. + */ + public void dispatchAboutToConnectDeviceRemoved() { + for (DeviceCallback callback : getCallbacks()) { + callback.onAboutToConnectDeviceRemoved(); } } @@ -705,13 +717,27 @@ public class LocalMediaManager implements BluetoothCallback { * connect imminently and should be displayed as the current device in the media player. * See [AudioManager.muteAwaitConnection] for more details. * - * @param deviceName the name of the device (displayed to the user). - * @param deviceIcon the icon that should be used with the device. + * The information in the most recent callback should override information from any previous + * callbacks. + * + * @param deviceAddress the address of the device. {@see AudioDeviceAttributes.address}. + * If present, we'll use this address to fetch the full information + * about the device (if we can find that information). + * @param deviceName the name of the device (displayed to the user). Used as a backup in + * case using deviceAddress doesn't work. + * @param deviceIcon the icon that should be used with the device. Used as a backup in case + * using deviceAddress doesn't work. */ - default void onAboutToConnectDeviceChanged( - @Nullable String deviceName, + default void onAboutToConnectDeviceAdded( + @NonNull String deviceAddress, + @NonNull String deviceName, @Nullable Drawable deviceIcon ) {} + + /** + * Callback for notifying that we no longer have an about-to-connect device. + */ + default void onAboutToConnectDeviceRemoved() {} } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index 824a6fd9d96e..d6231911244f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -164,7 +164,7 @@ class MediaDeviceManager @Inject constructor( } // A device that is not yet connected but is expected to connect imminently. Because it's // expected to connect imminently, it should be displayed as the current device. - private var aboutToConnectDeviceOverride: MediaDeviceData? = null + private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null @AnyThread fun start() = bgExecutor.execute { @@ -222,22 +222,34 @@ class MediaDeviceManager @Inject constructor( } } - override fun onAboutToConnectDeviceChanged(deviceName: String?, deviceIcon: Drawable?) { - aboutToConnectDeviceOverride = if (deviceName == null || deviceIcon == null) { - null - } else { - MediaDeviceData(enabled = true, deviceIcon, deviceName) - } + override fun onAboutToConnectDeviceAdded( + deviceAddress: String, + deviceName: String, + deviceIcon: Drawable? + ) { + aboutToConnectDeviceOverride = AboutToConnectDevice( + fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress), + backupMediaDeviceData = MediaDeviceData(enabled = true, deviceIcon, deviceName) + ) + updateCurrent() + } + + override fun onAboutToConnectDeviceRemoved() { + aboutToConnectDeviceOverride = null updateCurrent() } @WorkerThread private fun updateCurrent() { - if (aboutToConnectDeviceOverride != null) { - current = aboutToConnectDeviceOverride - return + val aboutToConnect = aboutToConnectDeviceOverride + if (aboutToConnect != null && + aboutToConnect.fullMediaDevice == null && + aboutToConnect.backupMediaDeviceData != null) { + // Only use [backupMediaDeviceData] when we don't have [fullMediaDevice]. + current = aboutToConnect.backupMediaDeviceData + return } - val device = localMediaManager.currentConnectedDevice + val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } // If we have a controller but get a null route, then don't trust the device @@ -247,3 +259,17 @@ class MediaDeviceManager @Inject constructor( } } } + +/** + * A class storing information for the about-to-connect device. See + * [LocalMediaManager.DeviceCallback.onAboutToConnectDeviceAdded] for more information. + * + * @property fullMediaDevice a full-fledged [MediaDevice] object representing the device. If + * non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData]. + * @property backupMediaDeviceData a backup [MediaDeviceData] object containing the minimum + * information required to display the device. Only use if [fullMediaDevice] is null. + */ +private data class AboutToConnectDevice( + val fullMediaDevice: MediaDevice? = null, + val backupMediaDeviceData: MediaDeviceData? = null +) diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt index 22bc5572f5a5..2783532aff97 100644 --- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt @@ -52,7 +52,9 @@ class MediaMuteAwaitConnectionManager constructor( // There should only be one device that's mutedUntilConnection at a time, so we can // safely override any previous value. currentMutedDevice = device - localMediaManager.dispatchAboutToConnectDeviceChanged(device.name, device.getIcon()) + localMediaManager.dispatchAboutToConnectDeviceAdded( + device.address, device.name, device.getIcon() + ) } } @@ -63,7 +65,7 @@ class MediaMuteAwaitConnectionManager constructor( ) { if (currentMutedDevice == device && USAGE_MEDIA in mutedUsages) { currentMutedDevice = null - localMediaManager.dispatchAboutToConnectDeviceChanged(null, null) + localMediaManager.dispatchAboutToConnectDeviceRemoved() } } } @@ -76,8 +78,8 @@ class MediaMuteAwaitConnectionManager constructor( val currentDevice = audioManager.mutingExpectedDevice if (currentDevice != null) { currentMutedDevice = currentDevice - localMediaManager.dispatchAboutToConnectDeviceChanged( - currentDevice.name, currentDevice.getIcon() + localMediaManager.dispatchAboutToConnectDeviceAdded( + currentDevice.address, currentDevice.name, currentDevice.getIcon() ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index e6f48ecd37de..10eeb11faa05 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -265,20 +265,58 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun onAboutToConnectDeviceChangedWithNonNullParams() { + fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress() { 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 onAboutToConnectDeviceAdded_cantFindDeviceInfoFromAddress() { + 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 can't get device info based on the address + val address = "fakeAddress" + whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(null) + // WHEN the about-to-connect device changes to non-null + val deviceCallback = captureCallback() val name = "AboutToConnectDeviceName" val mockIcon = mock(Drawable::class.java) - deviceCallback.onAboutToConnectDeviceChanged(name, mockIcon) + deviceCallback.onAboutToConnectDeviceAdded(address, name, mockIcon) assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) - // THEN the about-to-connect device is returned + + // THEN the about-to-connect device based on the parameters is returned val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(name) @@ -286,21 +324,21 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun onAboutToConnectDeviceChangedWithNullParams() { + fun onAboutToConnectDeviceAddedThenRemoved_usesNormalDevice() { manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() val deviceCallback = captureCallback() // First set a non-null about-to-connect device - deviceCallback.onAboutToConnectDeviceChanged( - "AboutToConnectDeviceName", mock(Drawable::class.java) + deviceCallback.onAboutToConnectDeviceAdded( + "fakeAddress", "AboutToConnectDeviceName", mock(Drawable::class.java) ) // Run and reset the executors and listeners so we only focus on new events. fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() reset(listener) - // WHEN the about-to-connect device changes to null - deviceCallback.onAboutToConnectDeviceChanged(null, null) + // WHEN hasDevice switches to false + deviceCallback.onAboutToConnectDeviceRemoved() assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) // THEN the normal device is returned val data = captureDeviceData(KEY) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt index 88c451499d21..27c039dcf29c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt @@ -24,7 +24,7 @@ import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo import android.media.AudioManager import android.media.AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION -import android.test.suitebuilder.annotation.SmallTest +import androidx.test.filters.SmallTest import com.android.settingslib.media.DeviceIconUtil import com.android.settingslib.media.LocalMediaManager import com.android.systemui.R @@ -95,7 +95,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { muteAwaitConnectionManager.startListening() - verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any()) } @Test @@ -104,7 +104,9 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { muteAwaitConnectionManager.startListening() - verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon)) + verify(localMediaManager).dispatchAboutToConnectDeviceAdded( + eq(DEVICE_ADDRESS), eq(DEVICE_NAME), eq(icon) + ) } @Test @@ -114,7 +116,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_UNKNOWN)) - verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any()) } @Test @@ -125,7 +127,9 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA)) - verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon)) + verify(localMediaManager).dispatchAboutToConnectDeviceAdded( + eq(DEVICE_ADDRESS), eq(DEVICE_NAME), eq(icon) + ) } @Test @@ -135,7 +139,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA)) - verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any()) } @Test @@ -155,7 +159,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { ) muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, otherDevice, intArrayOf(USAGE_MEDIA)) - verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any()) } @Test @@ -167,7 +171,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_UNKNOWN)) - verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any()) } @Test @@ -179,7 +183,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA)) - verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(null), eq(null)) + verify(localMediaManager).dispatchAboutToConnectDeviceRemoved() } private fun getMuteAwaitListener(): AudioManager.MuteAwaitConnectionCallback { @@ -191,11 +195,12 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { } } +private const val DEVICE_ADDRESS = "DeviceAddress" private const val DEVICE_NAME = "DeviceName" private val DEVICE = AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_USB_HEADSET, - "address", + DEVICE_ADDRESS, DEVICE_NAME, listOf(), listOf(), |