1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.media.controls.domain.pipeline 18 19 import android.bluetooth.BluetoothLeBroadcast 20 import android.bluetooth.BluetoothLeBroadcastMetadata 21 import android.content.pm.ApplicationInfo 22 import android.content.pm.PackageManager 23 import android.graphics.drawable.Drawable 24 import android.graphics.drawable.TestStubDrawable 25 import android.media.MediaRoute2Info 26 import android.media.MediaRouter2Manager 27 import android.media.RoutingSessionInfo 28 import android.media.session.MediaController 29 import android.media.session.MediaController.PlaybackInfo 30 import android.media.session.MediaSession 31 import android.platform.test.annotations.DisableFlags 32 import android.platform.test.annotations.EnableFlags 33 import android.platform.test.annotations.RequiresFlagsDisabled 34 import android.platform.test.annotations.RequiresFlagsEnabled 35 import android.platform.test.flag.junit.DeviceFlagsValueProvider 36 import android.testing.TestableLooper 37 import androidx.test.ext.junit.runners.AndroidJUnit4 38 import androidx.test.filters.SmallTest 39 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast 40 import com.android.settingslib.bluetooth.LocalBluetoothManager 41 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager 42 import com.android.settingslib.flags.Flags 43 import com.android.settingslib.media.LocalMediaManager 44 import com.android.settingslib.media.MediaDevice 45 import com.android.settingslib.media.PhoneMediaDevice 46 import com.android.settingslib.media.flags.Flags.FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS 47 import com.android.systemui.SysuiTestCase 48 import com.android.systemui.media.controls.MediaTestUtils 49 import com.android.systemui.media.controls.shared.model.MediaData 50 import com.android.systemui.media.controls.shared.model.MediaDeviceData 51 import com.android.systemui.media.controls.util.LocalMediaManagerFactory 52 import com.android.systemui.media.controls.util.MediaControllerFactory 53 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager 54 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory 55 import com.android.systemui.res.R 56 import com.android.systemui.statusbar.policy.ConfigurationController 57 import com.android.systemui.testKosmos 58 import com.android.systemui.util.concurrency.FakeExecutor 59 import com.android.systemui.util.time.FakeSystemClock 60 import com.google.common.truth.Truth.assertThat 61 import org.junit.After 62 import org.junit.Before 63 import org.junit.Rule 64 import org.junit.Test 65 import org.junit.runner.RunWith 66 import org.mockito.ArgumentCaptor 67 import org.mockito.ArgumentMatchers.anyBoolean 68 import org.mockito.ArgumentMatchers.anyInt 69 import org.mockito.Mock 70 import org.mockito.Mockito.any 71 import org.mockito.Mockito.mock 72 import org.mockito.Mockito.never 73 import org.mockito.Mockito.reset 74 import org.mockito.Mockito.times 75 import org.mockito.Mockito.verify 76 import org.mockito.Mockito.verifyNoMoreInteractions 77 import org.mockito.Mockito.`when` as whenever 78 import org.mockito.junit.MockitoJUnit 79 import org.mockito.kotlin.eq 80 81 private const val KEY = "TEST_KEY" 82 private const val KEY_OLD = "TEST_KEY_OLD" 83 private const val PACKAGE = "PKG" 84 private const val SESSION_KEY = "SESSION_KEY" 85 private const val DEVICE_ID = "DEVICE_ID" 86 private const val DEVICE_NAME = "DEVICE_NAME" 87 private const val REMOTE_DEVICE_NAME = "REMOTE_DEVICE_NAME" 88 private const val BROADCAST_APP_NAME = "BROADCAST_APP_NAME" 89 private const val NORMAL_APP_NAME = "NORMAL_APP_NAME" 90 91 @SmallTest 92 @RunWith(AndroidJUnit4::class) 93 @TestableLooper.RunWithLooper 94 public class MediaDeviceManagerTest : SysuiTestCase() { 95 96 private companion object { 97 val OTHER_DEVICE_ICON_STUB = TestStubDrawable() 98 } 99 100 @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() 101 102 private lateinit var manager: MediaDeviceManager 103 @Mock private lateinit var controllerFactory: MediaControllerFactory 104 @Mock private lateinit var lmmFactory: LocalMediaManagerFactory 105 @Mock private lateinit var lmm: LocalMediaManager 106 @Mock private lateinit var mr2: MediaRouter2Manager 107 @Mock private lateinit var muteAwaitFactory: MediaMuteAwaitConnectionManagerFactory 108 @Mock private lateinit var muteAwaitManager: MediaMuteAwaitConnectionManager 109 private lateinit var fakeFgExecutor: FakeExecutor 110 private lateinit var fakeBgExecutor: FakeExecutor 111 @Mock private lateinit var listener: MediaDeviceManager.Listener 112 @Mock private lateinit var device: MediaDevice 113 @Mock private lateinit var icon: Drawable 114 @Mock private lateinit var routingSession: RoutingSessionInfo 115 @Mock private lateinit var selectedRoute: MediaRoute2Info 116 @Mock private lateinit var controller: MediaController 117 @Mock private lateinit var playbackInfo: PlaybackInfo 118 @Mock private lateinit var configurationController: ConfigurationController 119 @Mock private lateinit var bluetoothLeBroadcast: BluetoothLeBroadcast 120 @Mock private lateinit var localBluetoothProfileManager: LocalBluetoothProfileManager 121 @Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast 122 @Mock private lateinit var packageManager: PackageManager 123 @Mock private lateinit var applicationInfo: ApplicationInfo 124 private lateinit var localBluetoothManager: LocalBluetoothManager 125 private lateinit var session: MediaSession 126 private lateinit var mediaData: MediaData 127 @JvmField @Rule val mockito = MockitoJUnit.rule() 128 129 private val kosmos = testKosmos() 130 131 @Before setUpnull132 fun setUp() { 133 fakeFgExecutor = FakeExecutor(FakeSystemClock()) 134 fakeBgExecutor = FakeExecutor(FakeSystemClock()) 135 localBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager::class.java) 136 manager = 137 MediaDeviceManager( 138 context, 139 controllerFactory, 140 lmmFactory, 141 { mr2 }, 142 muteAwaitFactory, 143 configurationController, 144 { localBluetoothManager }, 145 fakeFgExecutor, 146 fakeBgExecutor, 147 kosmos.mediaDeviceLogger, 148 ) 149 manager.addListener(listener) 150 151 // Configure mocks. 152 whenever(device.name).thenReturn(DEVICE_NAME) 153 whenever(device.iconWithoutBackground).thenReturn(icon) 154 whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm) 155 whenever(muteAwaitFactory.create(lmm)).thenReturn(muteAwaitManager) 156 whenever(lmm.getCurrentConnectedDevice()).thenReturn(device) 157 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(routingSession) 158 159 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) 160 whenever(controller.playbackInfo).thenReturn(playbackInfo) 161 162 // Create a media sesssion and notification for testing. 163 session = MediaSession(context, SESSION_KEY) 164 165 mediaData = 166 MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken) 167 whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller) 168 setupLeAudioConfiguration(false) 169 170 context.orCreateTestableResources.addOverride( 171 R.drawable.ic_media_home_devices, 172 OTHER_DEVICE_ICON_STUB 173 ) 174 } 175 176 @After tearDownnull177 fun tearDown() { 178 session.release() 179 } 180 181 @Test removeUnknownnull182 fun removeUnknown() { 183 manager.onMediaDataRemoved("unknown", false) 184 verify(listener, never()).onKeyRemoved(eq(KEY), anyBoolean()) 185 } 186 187 @Test loadMediaDatanull188 fun loadMediaData() { 189 manager.onMediaDataLoaded(KEY, null, mediaData) 190 verify(lmmFactory).create(PACKAGE) 191 } 192 193 @Test loadAndRemoveMediaDatanull194 fun loadAndRemoveMediaData() { 195 manager.onMediaDataLoaded(KEY, null, mediaData) 196 manager.onMediaDataRemoved(KEY, false) 197 fakeBgExecutor.runAllReady() 198 verify(lmm).unregisterCallback(any()) 199 verify(muteAwaitManager).stopListening() 200 } 201 202 @Test loadMediaDataWithNullTokennull203 fun loadMediaDataWithNullToken() { 204 manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) 205 fakeBgExecutor.runAllReady() 206 fakeFgExecutor.runAllReady() 207 val data = captureDeviceData(KEY) 208 assertThat(data.enabled).isTrue() 209 assertThat(data.name).isEqualTo(DEVICE_NAME) 210 } 211 212 @Test loadWithNewKeynull213 fun loadWithNewKey() { 214 // GIVEN that media data has been loaded with an old key 215 manager.onMediaDataLoaded(KEY_OLD, null, mediaData) 216 reset(listener) 217 // WHEN data is loaded with a new key 218 manager.onMediaDataLoaded(KEY, KEY_OLD, mediaData) 219 fakeBgExecutor.runAllReady() 220 fakeFgExecutor.runAllReady() 221 // THEN the listener for the old key should removed. 222 verify(lmm).unregisterCallback(any()) 223 verify(muteAwaitManager).stopListening() 224 // AND a new device event emitted 225 val data = captureDeviceData(KEY, KEY_OLD) 226 assertThat(data.enabled).isTrue() 227 assertThat(data.name).isEqualTo(DEVICE_NAME) 228 } 229 230 @Test newKeySameAsOldKeynull231 fun newKeySameAsOldKey() { 232 // GIVEN that media data has been loaded 233 manager.onMediaDataLoaded(KEY, null, mediaData) 234 reset(listener) 235 // WHEN the new key is the same as the old key 236 manager.onMediaDataLoaded(KEY, KEY, mediaData) 237 // THEN no event should be emitted 238 verify(listener, never()).onMediaDeviceChanged(eq(KEY), eq(null), any()) 239 } 240 241 @Test unknownOldKeynull242 fun unknownOldKey() { 243 val oldKey = "unknown" 244 manager.onMediaDataLoaded(KEY, oldKey, mediaData) 245 fakeBgExecutor.runAllReady() 246 fakeFgExecutor.runAllReady() 247 verify(listener).onMediaDeviceChanged(eq(KEY), eq(oldKey), any()) 248 } 249 250 @Test updateToSessionTokenWithNullRoutenull251 fun updateToSessionTokenWithNullRoute() { 252 // GIVEN that media data has been loaded with a null token 253 manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) 254 fakeBgExecutor.runAllReady() 255 fakeFgExecutor.runAllReady() 256 reset(listener) 257 // WHEN media data is loaded with a different token 258 // AND that token results in a null route 259 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 260 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) 261 val data = loadMediaAndCaptureDeviceData() 262 263 // THEN the device should be disabled 264 assertThat(data.enabled).isFalse() 265 } 266 267 @Test deviceEventOnAddNotificationnull268 fun deviceEventOnAddNotification() { 269 // WHEN a notification is added 270 // THEN the update is dispatched to the listener 271 val data = loadMediaAndCaptureDeviceData() 272 assertThat(data.enabled).isTrue() 273 assertThat(data.name).isEqualTo(DEVICE_NAME) 274 assertThat(data.icon).isEqualTo(icon) 275 } 276 277 @Test removeListenernull278 fun removeListener() { 279 // WHEN a listener is removed 280 manager.removeListener(listener) 281 // THEN it doesn't receive device events 282 manager.onMediaDataLoaded(KEY, null, mediaData) 283 verify(listener, never()).onMediaDeviceChanged(eq(KEY), eq(null), any()) 284 } 285 286 @Test deviceListUpdatenull287 fun deviceListUpdate() { 288 manager.onMediaDataLoaded(KEY, null, mediaData) 289 fakeBgExecutor.runAllReady() 290 val deviceCallback = captureCallback() 291 verify(muteAwaitManager).startListening() 292 // WHEN the device list changes 293 deviceCallback.onDeviceListUpdate(mutableListOf(device)) 294 assertThat(fakeBgExecutor.runAllReady()).isEqualTo(1) 295 assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) 296 // THEN the update is dispatched to the listener 297 val data = captureDeviceData(KEY) 298 assertThat(data.enabled).isTrue() 299 assertThat(data.name).isEqualTo(DEVICE_NAME) 300 assertThat(data.icon).isEqualTo(icon) 301 } 302 303 @Test selectedDeviceStateChangednull304 fun selectedDeviceStateChanged() { 305 manager.onMediaDataLoaded(KEY, null, mediaData) 306 fakeBgExecutor.runAllReady() 307 val deviceCallback = captureCallback() 308 // WHEN the selected device changes state 309 deviceCallback.onSelectedDeviceStateChanged(device, 1) 310 assertThat(fakeBgExecutor.runAllReady()).isEqualTo(1) 311 assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) 312 // THEN the update is dispatched to the listener 313 val data = captureDeviceData(KEY) 314 assertThat(data.enabled).isTrue() 315 assertThat(data.name).isEqualTo(DEVICE_NAME) 316 assertThat(data.icon).isEqualTo(icon) 317 } 318 319 @Test onAboutToConnectDeviceAdded_findsDeviceInfoFromAddressnull320 fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress() { 321 manager.onMediaDataLoaded(KEY, null, mediaData) 322 // Run and reset the executors and listeners so we only focus on new events. 323 fakeBgExecutor.runAllReady() 324 fakeFgExecutor.runAllReady() 325 reset(listener) 326 327 // Ensure we'll get device info when using the address 328 val fullMediaDevice = mock(MediaDevice::class.java) 329 val address = "fakeAddress" 330 val nameFromDevice = "nameFromDevice" 331 val iconFromDevice = mock(Drawable::class.java) 332 whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice) 333 whenever(fullMediaDevice.name).thenReturn(nameFromDevice) 334 whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice) 335 336 // WHEN the about-to-connect device changes to non-null 337 val deviceCallback = captureCallback() 338 val nameFromParam = "nameFromParam" 339 val iconFromParam = mock(Drawable::class.java) 340 deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam) 341 assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) 342 343 // THEN the about-to-connect device based on the address is returned 344 val data = captureDeviceData(KEY) 345 assertThat(data.enabled).isTrue() 346 assertThat(data.name).isEqualTo(nameFromDevice) 347 assertThat(data.name).isNotEqualTo(nameFromParam) 348 assertThat(data.icon).isEqualTo(iconFromDevice) 349 assertThat(data.icon).isNotEqualTo(iconFromParam) 350 } 351 352 @Test onAboutToConnectDeviceAdded_cantFindDeviceInfoFromAddressnull353 fun onAboutToConnectDeviceAdded_cantFindDeviceInfoFromAddress() { 354 manager.onMediaDataLoaded(KEY, null, mediaData) 355 // Run and reset the executors and listeners so we only focus on new events. 356 fakeBgExecutor.runAllReady() 357 fakeFgExecutor.runAllReady() 358 reset(listener) 359 360 // Ensure we can't get device info based on the address 361 val address = "fakeAddress" 362 whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(null) 363 364 // WHEN the about-to-connect device changes to non-null 365 val deviceCallback = captureCallback() 366 val name = "AboutToConnectDeviceName" 367 val mockIcon = mock(Drawable::class.java) 368 deviceCallback.onAboutToConnectDeviceAdded(address, name, mockIcon) 369 assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) 370 371 // THEN the about-to-connect device based on the parameters is returned 372 val data = captureDeviceData(KEY) 373 assertThat(data.enabled).isTrue() 374 assertThat(data.name).isEqualTo(name) 375 assertThat(data.icon).isEqualTo(mockIcon) 376 } 377 378 @Test onAboutToConnectDeviceAddedThenRemoved_usesNormalDevicenull379 fun onAboutToConnectDeviceAddedThenRemoved_usesNormalDevice() { 380 manager.onMediaDataLoaded(KEY, null, mediaData) 381 fakeBgExecutor.runAllReady() 382 val deviceCallback = captureCallback() 383 // First set a non-null about-to-connect device 384 deviceCallback.onAboutToConnectDeviceAdded( 385 "fakeAddress", 386 "AboutToConnectDeviceName", 387 mock(Drawable::class.java) 388 ) 389 // Run and reset the executors and listeners so we only focus on new events. 390 fakeBgExecutor.runAllReady() 391 fakeFgExecutor.runAllReady() 392 reset(listener) 393 394 // WHEN hasDevice switches to false 395 deviceCallback.onAboutToConnectDeviceRemoved() 396 assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) 397 // THEN the normal device is returned 398 val data = captureDeviceData(KEY) 399 assertThat(data.enabled).isTrue() 400 assertThat(data.name).isEqualTo(DEVICE_NAME) 401 assertThat(data.icon).isEqualTo(icon) 402 } 403 404 @Test listenerReceivesKeyRemovednull405 fun listenerReceivesKeyRemoved() { 406 manager.onMediaDataLoaded(KEY, null, mediaData) 407 // WHEN the notification is removed 408 manager.onMediaDataRemoved(KEY, true) 409 // THEN the listener receives key removed event 410 verify(listener).onKeyRemoved(eq(KEY), eq(true)) 411 } 412 413 @Test onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionNamenull414 fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName() { 415 // GIVEN that MR2Manager returns a valid routing session 416 whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME) 417 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 418 // WHEN a notification is added 419 // THEN it uses the route name (instead of device name) 420 val data = loadMediaAndCaptureDeviceData() 421 assertThat(data.enabled).isTrue() 422 assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) 423 } 424 425 @Test 426 @EnableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE) onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableReusednull427 fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableReused() { 428 whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME) 429 whenever(routingSession.selectedRoutes).thenReturn(listOf("selectedRoute", "selectedRoute")) 430 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 431 432 val firstData = loadMediaAndCaptureDeviceData() 433 reset(listener) 434 val secondData = loadMediaAndCaptureDeviceData() 435 436 assertThat(secondData.icon).isEqualTo(firstData.icon) 437 } 438 439 @Test 440 @DisableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE) onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableNotReusednull441 fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableNotReused() { 442 whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME) 443 whenever(routingSession.selectedRoutes).thenReturn(listOf("selectedRoute", "selectedRoute")) 444 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 445 446 val firstData = loadMediaAndCaptureDeviceData() 447 reset(listener) 448 val secondData = loadMediaAndCaptureDeviceData() 449 450 assertThat(secondData.icon).isNotEqualTo(firstData.icon) 451 } 452 453 @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 454 @Test onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevicenull455 fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { 456 // GIVEN that MR2Manager returns null for routing session 457 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 458 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) 459 // WHEN a notification is added 460 // THEN the device is disabled and name is set to null 461 val data = loadMediaAndCaptureDeviceData() 462 assertThat(data.enabled).isFalse() 463 assertThat(data.name).isNull() 464 } 465 466 @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 467 @Test onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_returnsOtherDevicenull468 fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_returnsOtherDevice() { 469 // GIVEN that MR2Manager returns null for routing session 470 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 471 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) 472 // WHEN a notification is added 473 // THEN the device is disabled and name and icon are set to "OTHER DEVICE". 474 val data = loadMediaAndCaptureDeviceData() 475 assertThat(data.enabled).isFalse() 476 assertThat(data.name).isEqualTo(context.getString(R.string.media_seamless_other_device)) 477 assertThat(data.icon).isEqualTo(OTHER_DEVICE_ICON_STUB) 478 } 479 480 @Test 481 @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 482 @EnableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE) onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableReusednull483 fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableReused() { 484 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 485 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) 486 context.orCreateTestableResources.removeOverride(R.drawable.ic_media_home_devices) 487 488 val firstData = loadMediaAndCaptureDeviceData() 489 reset(listener) 490 val secondData = loadMediaAndCaptureDeviceData() 491 492 assertThat(secondData.icon).isEqualTo(firstData.icon) 493 } 494 495 @Test 496 @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 497 @DisableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE) onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableNotReusednull498 fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableNotReused() { 499 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 500 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) 501 context.orCreateTestableResources.removeOverride(R.drawable.ic_media_home_devices) 502 503 val firstData = loadMediaAndCaptureDeviceData() 504 reset(listener) 505 val secondData = loadMediaAndCaptureDeviceData() 506 507 assertThat(secondData.icon).isNotEqualTo(firstData.icon) 508 } 509 510 @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 511 @Test onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevicenull512 fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { 513 // GIVEN a notif is added 514 loadMediaAndCaptureDeviceData() 515 reset(listener) 516 // AND MR2Manager returns null for routing session 517 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 518 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) 519 // WHEN the selected device changes state 520 val deviceCallback = captureCallback() 521 deviceCallback.onSelectedDeviceStateChanged(device, 1) 522 fakeBgExecutor.runAllReady() 523 fakeFgExecutor.runAllReady() 524 // THEN the device is disabled and name is set to null 525 val data = captureDeviceData(KEY) 526 assertThat(data.enabled).isFalse() 527 assertThat(data.name).isNull() 528 } 529 530 @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 531 @Test onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_returnOtherDevicenull532 fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_returnOtherDevice() { 533 // GIVEN a notif is added 534 loadMediaAndCaptureDeviceData() 535 reset(listener) 536 // AND MR2Manager returns null for routing session 537 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 538 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) 539 // WHEN the selected device changes state 540 val deviceCallback = captureCallback() 541 deviceCallback.onSelectedDeviceStateChanged(device, 1) 542 fakeBgExecutor.runAllReady() 543 fakeFgExecutor.runAllReady() 544 // THEN the device is disabled and name and icon are set to "OTHER DEVICE". 545 val data = captureDeviceData(KEY) 546 assertThat(data.enabled).isFalse() 547 assertThat(data.name).isEqualTo(context.getString(R.string.media_seamless_other_device)) 548 assertThat(data.icon).isEqualTo(OTHER_DEVICE_ICON_STUB) 549 } 550 551 @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 552 @Test onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevicenull553 fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { 554 // GIVEN a notif is added 555 loadMediaAndCaptureDeviceData() 556 reset(listener) 557 // GIVEN that MR2Manager returns null for routing session 558 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 559 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) 560 // WHEN the selected device changes state 561 val deviceCallback = captureCallback() 562 deviceCallback.onDeviceListUpdate(mutableListOf(device)) 563 fakeBgExecutor.runAllReady() 564 fakeFgExecutor.runAllReady() 565 // THEN the device is disabled and name is set to null 566 val data = captureDeviceData(KEY) 567 assertThat(data.enabled).isFalse() 568 assertThat(data.name).isNull() 569 } 570 571 @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 572 @Test onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_returnsOtherDevicenull573 fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_returnsOtherDevice() { 574 // GIVEN a notif is added 575 loadMediaAndCaptureDeviceData() 576 reset(listener) 577 // GIVEN that MR2Manager returns null for routing session 578 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 579 whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) 580 // WHEN the selected device changes state 581 val deviceCallback = captureCallback() 582 deviceCallback.onDeviceListUpdate(mutableListOf(device)) 583 fakeBgExecutor.runAllReady() 584 fakeFgExecutor.runAllReady() 585 // THEN device is disabled and name and icon are set to "OTHER DEVICE". 586 val data = captureDeviceData(KEY) 587 assertThat(data.enabled).isFalse() 588 assertThat(data.name).isEqualTo(context.getString(R.string.media_seamless_other_device)) 589 assertThat(data.icon).isEqualTo(OTHER_DEVICE_ICON_STUB) 590 } 591 592 // With the flag enabled, MediaDeviceManager no longer gathers device name information directly. 593 @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 594 @Test mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneNamenull595 fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { 596 // When the routing session name is null, and is a system session for a PhoneMediaDevice 597 val phoneDevice = mock(PhoneMediaDevice::class.java) 598 whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) 599 whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) 600 whenever(routingSession.isSystemSession).thenReturn(true) 601 602 whenever(routingSession.name).thenReturn(null) 603 whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) 604 whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) 605 whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) 606 607 // Then the device name is the PhoneMediaDevice string 608 val data = loadMediaAndCaptureDeviceData() 609 assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context)) 610 } 611 612 // With the flag enabled, MediaDeviceManager no longer gathers device name information directly. 613 @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) 614 @Test mr2ReturnsSystemRouteWithNullName_useSelectedRouteNamenull615 fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { 616 // When the routing session does not have a name, and is a system session 617 whenever(routingSession.name).thenReturn(null) 618 whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) 619 whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) 620 whenever(routingSession.isSystemSession).thenReturn(true) 621 622 // Then the device name is the selected route name 623 val data = loadMediaAndCaptureDeviceData() 624 assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) 625 } 626 627 @Test mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceNamenull628 fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { 629 // GIVEN that MR2Manager returns a routing session that does not have a name 630 whenever(routingSession.name).thenReturn(null) 631 whenever(routingSession.isSystemSession).thenReturn(false) 632 // WHEN a notification is added 633 // THEN the device is enabled and uses the current connected device name 634 val data = loadMediaAndCaptureDeviceData() 635 assertThat(data.name).isEqualTo(DEVICE_NAME) 636 assertThat(data.enabled).isTrue() 637 } 638 639 @Test audioInfoPlaybackTypeChangednull640 fun audioInfoPlaybackTypeChanged() { 641 whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) 642 whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo) 643 // GIVEN a controller with local playback type 644 loadMediaAndCaptureDeviceData() 645 reset(mr2) 646 // WHEN onAudioInfoChanged fires with remote playback type 647 whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 648 val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java) 649 verify(controller).registerCallback(captor.capture()) 650 captor.value.onAudioInfoChanged(playbackInfo) 651 // THEN the route is checked 652 verify(mr2).getRoutingSessionForMediaController(eq(controller)) 653 } 654 655 @Test onAudioInfoChanged_withRemotePlaybackInfo_queriesRoutingSessionnull656 fun onAudioInfoChanged_withRemotePlaybackInfo_queriesRoutingSession() { 657 whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) 658 whenever(playbackInfo.getVolumeControlId()).thenReturn(null) 659 whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo) 660 // GIVEN a controller with local playback type 661 loadMediaAndCaptureDeviceData() 662 reset(mr2) 663 // WHEN onAudioInfoChanged fires with a volume control id change 664 whenever(playbackInfo.getVolumeControlId()).thenReturn("placeholder id") 665 whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 666 val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java) 667 verify(controller).registerCallback(captor.capture()) 668 captor.value.onAudioInfoChanged(playbackInfo) 669 // THEN the routing session is checked 670 verify(mr2).getRoutingSessionForMediaController(eq(controller)) 671 } 672 673 @Test audioInfoHasntChangednull674 fun audioInfoHasntChanged() { 675 whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) 676 whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo) 677 // GIVEN a controller with remote playback type 678 loadMediaAndCaptureDeviceData() 679 reset(mr2) 680 // WHEN onAudioInfoChanged fires with remote playback type 681 val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java) 682 verify(controller).registerCallback(captor.capture()) 683 captor.value.onAudioInfoChanged(playbackInfo) 684 // THEN the route is not checked 685 verify(mr2, never()).getRoutingSessionForMediaController(eq(controller)) 686 } 687 688 @Test deviceIdChanged_informListenernull689 fun deviceIdChanged_informListener() { 690 // GIVEN a notification is added, with a particular device connected 691 whenever(device.id).thenReturn(DEVICE_ID) 692 loadMediaAndCaptureDeviceData() 693 694 // and later the manager gets a new device ID 695 val deviceCallback = captureCallback() 696 val updatedId = DEVICE_ID + "_new" 697 whenever(device.id).thenReturn(updatedId) 698 deviceCallback.onDeviceListUpdate(mutableListOf(device)) 699 700 // THEN the listener gets the updated info 701 fakeBgExecutor.runAllReady() 702 fakeFgExecutor.runAllReady() 703 704 val dataCaptor = ArgumentCaptor.forClass(MediaDeviceData::class.java) 705 verify(listener, times(2)).onMediaDeviceChanged(eq(KEY), any(), dataCaptor.capture()) 706 707 val firstDevice = dataCaptor.allValues.get(0) 708 assertThat(firstDevice.id).isEqualTo(DEVICE_ID) 709 710 val secondDevice = dataCaptor.allValues.get(1) 711 assertThat(secondDevice.id).isEqualTo(updatedId) 712 } 713 714 @Test deviceNameChanged_informListenernull715 fun deviceNameChanged_informListener() { 716 // GIVEN a notification is added, with a particular device connected 717 whenever(device.id).thenReturn(DEVICE_ID) 718 whenever(device.name).thenReturn(DEVICE_NAME) 719 loadMediaAndCaptureDeviceData() 720 721 // and later the manager gets a new device name 722 val deviceCallback = captureCallback() 723 val updatedName = DEVICE_NAME + "_new" 724 whenever(device.name).thenReturn(updatedName) 725 deviceCallback.onDeviceListUpdate(mutableListOf(device)) 726 727 // THEN the listener gets the updated info 728 fakeBgExecutor.runAllReady() 729 fakeFgExecutor.runAllReady() 730 731 val dataCaptor = ArgumentCaptor.forClass(MediaDeviceData::class.java) 732 verify(listener, times(2)).onMediaDeviceChanged(eq(KEY), any(), dataCaptor.capture()) 733 734 val firstDevice = dataCaptor.allValues.get(0) 735 assertThat(firstDevice.name).isEqualTo(DEVICE_NAME) 736 737 val secondDevice = dataCaptor.allValues.get(1) 738 assertThat(secondDevice.name).isEqualTo(updatedName) 739 } 740 741 @Test deviceIconChanged_doesNotCallListenernull742 fun deviceIconChanged_doesNotCallListener() { 743 // GIVEN a notification is added, with a particular device connected 744 whenever(device.id).thenReturn(DEVICE_ID) 745 whenever(device.name).thenReturn(DEVICE_NAME) 746 val firstIcon = mock(Drawable::class.java) 747 whenever(device.icon).thenReturn(firstIcon) 748 749 loadMediaAndCaptureDeviceData() 750 751 // and later the manager gets a callback with only the icon changed 752 val deviceCallback = captureCallback() 753 val secondIcon = mock(Drawable::class.java) 754 whenever(device.icon).thenReturn(secondIcon) 755 deviceCallback.onDeviceListUpdate(mutableListOf(device)) 756 757 // THEN the listener is not called again 758 fakeBgExecutor.runAllReady() 759 fakeFgExecutor.runAllReady() 760 verifyNoMoreInteractions(listener) 761 } 762 763 @Test testRemotePlaybackDeviceOverridenull764 fun testRemotePlaybackDeviceOverride() { 765 whenever(routingSession.name).thenReturn(DEVICE_NAME) 766 val deviceData = 767 MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null, showBroadcastButton = false) 768 val mediaDataWithDevice = mediaData.copy(device = deviceData) 769 770 // GIVEN media data that already has a device set 771 manager.onMediaDataLoaded(KEY, null, mediaDataWithDevice) 772 fakeBgExecutor.runAllReady() 773 fakeFgExecutor.runAllReady() 774 775 // THEN we keep the device info, and don't register a listener 776 val data = captureDeviceData(KEY) 777 assertThat(data.enabled).isFalse() 778 assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) 779 verify(lmm, never()).registerCallback(any()) 780 } 781 782 @Test onBroadcastStarted_flagOff_currentMediaDeviceDataIsBroadcastingnull783 fun onBroadcastStarted_flagOff_currentMediaDeviceDataIsBroadcasting() { 784 mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) 785 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) 786 val broadcastCallback = setupBroadcastCallback() 787 setupLeAudioConfiguration(true) 788 setupBroadcastPackage(BROADCAST_APP_NAME) 789 broadcastCallback.onBroadcastStarted(1, 1) 790 791 val data = loadMediaAndCaptureDeviceData() 792 assertThat(data.showBroadcastButton).isFalse() 793 assertThat(data.enabled).isTrue() 794 assertThat(data.name).isEqualTo(DEVICE_NAME) 795 } 796 797 @Test 798 @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) 799 @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) onBroadcastStarted_legacy_currentMediaDeviceDataIsBroadcastingnull800 fun onBroadcastStarted_legacy_currentMediaDeviceDataIsBroadcasting() { 801 val broadcastCallback = setupBroadcastCallback() 802 setupLeAudioConfiguration(true) 803 setupBroadcastPackage(BROADCAST_APP_NAME) 804 broadcastCallback.onBroadcastStarted(1, 1) 805 806 val data = loadMediaAndCaptureDeviceData() 807 assertThat(data.showBroadcastButton).isTrue() 808 assertThat(data.enabled).isTrue() 809 assertThat(data.name) 810 .isEqualTo(context.getString(R.string.broadcasting_description_is_broadcasting)) 811 } 812 813 @Test 814 @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) 815 @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcastingnull816 fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting() { 817 val broadcastCallback = setupBroadcastCallback() 818 setupLeAudioConfiguration(true) 819 setupBroadcastPackage(NORMAL_APP_NAME) 820 broadcastCallback.onBroadcastStarted(1, 1) 821 822 val data = loadMediaAndCaptureDeviceData() 823 assertThat(data.showBroadcastButton).isTrue() 824 assertThat(data.enabled).isTrue() 825 assertThat(data.name).isEqualTo(BROADCAST_APP_NAME) 826 } 827 828 @Test 829 @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) 830 @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) onBroadcastStopped_legacy_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGonenull831 fun onBroadcastStopped_legacy_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGone() { 832 val broadcastCallback = setupBroadcastCallback() 833 setupLeAudioConfiguration(false) 834 broadcastCallback.onBroadcastStopped(1, 1) 835 836 val data = loadMediaAndCaptureDeviceData() 837 assertThat(data.showBroadcastButton).isFalse() 838 } 839 840 @Test 841 @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) 842 @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) onBroadcastStarted_currentMediaDeviceDataIsBroadcastingnull843 fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting() { 844 val broadcastCallback = setupBroadcastCallback() 845 setupLeAudioConfiguration(true) 846 setupBroadcastPackage(BROADCAST_APP_NAME) 847 broadcastCallback.onBroadcastStarted(1, 1) 848 849 val data = loadMediaAndCaptureDeviceData() 850 assertThat(data.showBroadcastButton).isFalse() 851 assertThat(data.enabled).isFalse() 852 assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description)) 853 } 854 855 @Test 856 @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) 857 @EnableFlags( 858 Flags.FLAG_ENABLE_LE_AUDIO_SHARING, 859 com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE 860 ) onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesReusednull861 fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesReused() { 862 val broadcastCallback = setupBroadcastCallback() 863 setupLeAudioConfiguration(true) 864 setupBroadcastPackage(BROADCAST_APP_NAME) 865 broadcastCallback.onBroadcastStarted(1, 1) 866 867 val firstDeviceData = loadMediaAndCaptureDeviceData() 868 reset(listener) 869 val secondDeviceData = loadMediaAndCaptureDeviceData() 870 871 assertThat(firstDeviceData.icon).isEqualTo(secondDeviceData.icon) 872 } 873 874 @Test 875 @DisableFlags( 876 Flags.FLAG_LEGACY_LE_AUDIO_SHARING, 877 com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE 878 ) 879 @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesNotReusednull880 fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesNotReused() { 881 val broadcastCallback = setupBroadcastCallback() 882 setupLeAudioConfiguration(true) 883 setupBroadcastPackage(BROADCAST_APP_NAME) 884 broadcastCallback.onBroadcastStarted(1, 1) 885 886 val firstDeviceData = loadMediaAndCaptureDeviceData() 887 reset(listener) 888 val secondDeviceData = loadMediaAndCaptureDeviceData() 889 890 assertThat(firstDeviceData.icon).isNotEqualTo(secondDeviceData.icon) 891 } 892 893 @Test 894 @EnableFlags( 895 Flags.FLAG_LEGACY_LE_AUDIO_SHARING, 896 com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE 897 ) 898 @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableReusednull899 fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableReused() { 900 val broadcastCallback = setupBroadcastCallback() 901 setupLeAudioConfiguration(true) 902 setupBroadcastPackage(NORMAL_APP_NAME) 903 broadcastCallback.onBroadcastStarted(1, 1) 904 905 val firstDeviceData = loadMediaAndCaptureDeviceData() 906 reset(listener) 907 val secondDeviceData = loadMediaAndCaptureDeviceData() 908 909 assertThat(firstDeviceData.icon).isEqualTo(secondDeviceData.icon) 910 } 911 912 @Test 913 @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) 914 @DisableFlags( 915 Flags.FLAG_ENABLE_LE_AUDIO_SHARING, 916 com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE 917 ) onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableNotReusednull918 fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableNotReused() { 919 val broadcastCallback = setupBroadcastCallback() 920 setupLeAudioConfiguration(true) 921 setupBroadcastPackage(NORMAL_APP_NAME) 922 broadcastCallback.onBroadcastStarted(1, 1) 923 924 val firstDeviceData = loadMediaAndCaptureDeviceData() 925 reset(listener) 926 val secondDeviceData = loadMediaAndCaptureDeviceData() 927 928 assertThat(firstDeviceData.icon).isNotEqualTo(secondDeviceData.icon) 929 } 930 931 @Test 932 @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) 933 @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) onBroadcastStarted_currentMediaDeviceDataIsNotBroadcastingnull934 fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() { 935 val broadcastCallback = setupBroadcastCallback() 936 setupLeAudioConfiguration(true) 937 setupBroadcastPackage(NORMAL_APP_NAME) 938 broadcastCallback.onBroadcastStarted(1, 1) 939 940 val data = loadMediaAndCaptureDeviceData() 941 assertThat(data.showBroadcastButton).isFalse() 942 assertThat(data.enabled).isFalse() 943 assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description)) 944 } 945 946 @Test 947 @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) 948 @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) onBroadcastStopped_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGonenull949 fun onBroadcastStopped_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGone() { 950 val broadcastCallback = setupBroadcastCallback() 951 setupLeAudioConfiguration(false) 952 broadcastCallback.onBroadcastStopped(1, 1) 953 954 val data = loadMediaAndCaptureDeviceData() 955 assertThat(data.showBroadcastButton).isFalse() 956 assertThat(data.name?.equals(context.getString(R.string.audio_sharing_description))) 957 .isFalse() 958 } 959 captureCallbacknull960 private fun captureCallback(): LocalMediaManager.DeviceCallback { 961 val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) 962 verify(lmm).registerCallback(captor.capture()) 963 return captor.getValue() 964 } 965 setupBroadcastCallbacknull966 private fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { 967 val callback: BluetoothLeBroadcast.Callback = 968 object : BluetoothLeBroadcast.Callback { 969 override fun onBroadcastStarted(reason: Int, broadcastId: Int) {} 970 971 override fun onBroadcastStartFailed(reason: Int) {} 972 973 override fun onBroadcastStopped(reason: Int, broadcastId: Int) {} 974 975 override fun onBroadcastStopFailed(reason: Int) {} 976 977 override fun onPlaybackStarted(reason: Int, broadcastId: Int) {} 978 979 override fun onPlaybackStopped(reason: Int, broadcastId: Int) {} 980 981 override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {} 982 983 override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {} 984 985 override fun onBroadcastMetadataChanged( 986 broadcastId: Int, 987 metadata: BluetoothLeBroadcastMetadata 988 ) {} 989 } 990 991 bluetoothLeBroadcast.registerCallback(fakeFgExecutor, callback) 992 return callback 993 } 994 setupLeAudioConfigurationnull995 private fun setupLeAudioConfiguration(isLeAudio: Boolean) { 996 whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager) 997 whenever(localBluetoothProfileManager.leAudioBroadcastProfile) 998 .thenReturn(localBluetoothLeBroadcast) 999 whenever(localBluetoothLeBroadcast.isEnabled(any())).thenReturn(isLeAudio) 1000 whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME) 1001 } 1002 setupBroadcastPackagenull1003 private fun setupBroadcastPackage(currentName: String) { 1004 whenever(lmm.packageName).thenReturn(PACKAGE) 1005 whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) 1006 .thenReturn(applicationInfo) 1007 whenever(packageManager.getApplicationLabel(applicationInfo)).thenReturn(currentName) 1008 context.setMockPackageManager(packageManager) 1009 } 1010 captureDeviceDatanull1011 private fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { 1012 val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) 1013 verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture()) 1014 return captor.getValue() 1015 } 1016 loadMediaAndCaptureDeviceDatanull1017 private fun loadMediaAndCaptureDeviceData(): MediaDeviceData { 1018 manager.onMediaDataLoaded(KEY, null, mediaData) 1019 fakeBgExecutor.runAllReady() 1020 fakeFgExecutor.runAllReady() 1021 1022 return captureDeviceData(KEY) 1023 } 1024 } 1025