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