1 /*
<lambda>null2  * Copyright (C) 2023 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.bluetooth.qsdialog
18 
19 import android.bluetooth.BluetoothAdapter
20 import android.bluetooth.BluetoothDevice
21 import android.content.Context
22 import android.media.AudioManager
23 import android.testing.TestableLooper
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.SmallTest
26 import com.android.settingslib.bluetooth.CachedBluetoothDevice
27 import com.android.settingslib.bluetooth.LocalBluetoothManager
28 import com.android.systemui.SysuiTestCase
29 import com.android.systemui.coroutines.collectLastValue
30 import com.android.systemui.testKosmos
31 import com.android.systemui.util.time.FakeSystemClock
32 import com.google.common.truth.Truth.assertThat
33 import kotlinx.coroutines.CoroutineDispatcher
34 import kotlinx.coroutines.test.TestScope
35 import kotlinx.coroutines.test.UnconfinedTestDispatcher
36 import kotlinx.coroutines.test.runTest
37 import org.junit.Before
38 import org.junit.Rule
39 import org.junit.Test
40 import org.junit.runner.RunWith
41 import org.mockito.Mock
42 import org.mockito.Mockito.`when`
43 import org.mockito.junit.MockitoJUnit
44 import org.mockito.junit.MockitoRule
45 
46 @SmallTest
47 @RunWith(AndroidJUnit4::class)
48 @TestableLooper.RunWithLooper(setAsMainLooper = true)
49 class DeviceItemInteractorTest : SysuiTestCase() {
50 
51     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
52     private val kosmos = testKosmos()
53 
54     @Mock private lateinit var bluetoothTileDialogRepository: BluetoothTileDialogRepository
55 
56     @Mock private lateinit var cachedDevice1: CachedBluetoothDevice
57 
58     @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
59 
60     @Mock private lateinit var device1: BluetoothDevice
61 
62     @Mock private lateinit var device2: BluetoothDevice
63 
64     @Mock private lateinit var deviceItem1: DeviceItem
65 
66     @Mock private lateinit var deviceItem2: DeviceItem
67 
68     @Mock private lateinit var audioManager: AudioManager
69 
70     @Mock private lateinit var adapter: BluetoothAdapter
71 
72     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
73 
74     @Mock private lateinit var logger: BluetoothTileDialogLogger
75 
76     private val fakeSystemClock = FakeSystemClock()
77 
78     private lateinit var interactor: DeviceItemInteractor
79 
80     private lateinit var dispatcher: CoroutineDispatcher
81 
82     private lateinit var testScope: TestScope
83 
84     @Before
85     fun setUp() {
86         dispatcher = UnconfinedTestDispatcher()
87         testScope = TestScope(dispatcher)
88         `when`(deviceItem1.cachedBluetoothDevice).thenReturn(cachedDevice1)
89         `when`(deviceItem2.cachedBluetoothDevice).thenReturn(cachedDevice2)
90         `when`(cachedDevice1.address).thenReturn("ADDRESS")
91         `when`(cachedDevice1.device).thenReturn(device1)
92         `when`(cachedDevice2.device).thenReturn(device2)
93         `when`(bluetoothTileDialogRepository.cachedDevices)
94             .thenReturn(listOf(cachedDevice1, cachedDevice2))
95     }
96 
97     @Test
98     fun testUpdateDeviceItems_noCachedDevice_returnEmpty() {
99         testScope.runTest {
100             `when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(emptyList())
101             interactor =
102                 DeviceItemInteractor(
103                     bluetoothTileDialogRepository,
104                     kosmos.audioSharingInteractor,
105                     audioManager,
106                     adapter,
107                     localBluetoothManager,
108                     fakeSystemClock,
109                     logger,
110                     listOf(createFactory({ true }, deviceItem1)),
111                     emptyList(),
112                     testScope.backgroundScope,
113                     dispatcher,
114                 )
115 
116             val latest by collectLastValue(interactor.deviceItemUpdate)
117             val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
118             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
119 
120             assertThat(latest).isEqualTo(emptyList<DeviceItem>())
121             assertThat(latestShowSeeAll).isFalse()
122         }
123     }
124 
125     @Test
126     fun testUpdateDeviceItems_hasCachedDevice_filterNotMatch_returnEmpty() {
127         testScope.runTest {
128             `when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(listOf(cachedDevice1))
129             interactor =
130                 DeviceItemInteractor(
131                     bluetoothTileDialogRepository,
132                     kosmos.audioSharingInteractor,
133                     audioManager,
134                     adapter,
135                     localBluetoothManager,
136                     fakeSystemClock,
137                     logger,
138                     listOf(createFactory({ false }, deviceItem1)),
139                     emptyList(),
140                     testScope.backgroundScope,
141                     dispatcher,
142                 )
143 
144             val latest by collectLastValue(interactor.deviceItemUpdate)
145             val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
146             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
147 
148             assertThat(latest).isEqualTo(emptyList<DeviceItem>())
149             assertThat(latestShowSeeAll).isFalse()
150         }
151     }
152 
153     @Test
154     fun testUpdateDeviceItems_hasCachedDevice_filterMatch_returnDeviceItem() {
155         testScope.runTest {
156             `when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(listOf(cachedDevice1))
157             interactor =
158                 DeviceItemInteractor(
159                     bluetoothTileDialogRepository,
160                     kosmos.audioSharingInteractor,
161                     audioManager,
162                     adapter,
163                     localBluetoothManager,
164                     fakeSystemClock,
165                     logger,
166                     listOf(createFactory({ true }, deviceItem1)),
167                     emptyList(),
168                     testScope.backgroundScope,
169                     dispatcher,
170                 )
171 
172             val latest by collectLastValue(interactor.deviceItemUpdate)
173             val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
174             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
175 
176             assertThat(latest).isEqualTo(listOf(deviceItem1))
177             assertThat(latestShowSeeAll).isFalse()
178         }
179     }
180 
181     @Test
182     fun testUpdateDeviceItems_hasCachedDevice_filterMatch_returnMultipleDeviceItem() {
183         testScope.runTest {
184             `when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
185             interactor =
186                 DeviceItemInteractor(
187                     bluetoothTileDialogRepository,
188                     kosmos.audioSharingInteractor,
189                     audioManager,
190                     adapter,
191                     localBluetoothManager,
192                     fakeSystemClock,
193                     logger,
194                     listOf(
195                         createFactory({ false }, deviceItem1),
196                         createFactory({ true }, deviceItem2),
197                     ),
198                     emptyList(),
199                     testScope.backgroundScope,
200                     dispatcher,
201                 )
202 
203             val latest by collectLastValue(interactor.deviceItemUpdate)
204             val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
205             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
206 
207             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2))
208             assertThat(latestShowSeeAll).isFalse()
209         }
210     }
211 
212     @Test
213     fun testUpdateDeviceItems_sortByDisplayPriority() {
214         testScope.runTest {
215             `when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
216             interactor =
217                 DeviceItemInteractor(
218                     bluetoothTileDialogRepository,
219                     kosmos.audioSharingInteractor,
220                     audioManager,
221                     adapter,
222                     localBluetoothManager,
223                     fakeSystemClock,
224                     logger,
225                     listOf(
226                         createFactory(
227                             { cachedDevice -> cachedDevice.device == device1 },
228                             deviceItem1,
229                         ),
230                         createFactory(
231                             { cachedDevice -> cachedDevice.device == device2 },
232                             deviceItem2,
233                         ),
234                     ),
235                     listOf(
236                         DeviceItemType.SAVED_BLUETOOTH_DEVICE,
237                         DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
238                     ),
239                     testScope.backgroundScope,
240                     dispatcher,
241                 )
242             `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
243             `when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
244 
245             val latest by collectLastValue(interactor.deviceItemUpdate)
246             val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
247             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
248 
249             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
250             assertThat(latestShowSeeAll).isFalse()
251         }
252     }
253 
254     @Test
255     fun testUpdateDeviceItems_sameType_sortByRecentlyConnected() {
256         testScope.runTest {
257             `when`(adapter.mostRecentlyConnectedDevices).thenReturn(listOf(device2, device1))
258             interactor =
259                 DeviceItemInteractor(
260                     bluetoothTileDialogRepository,
261                     kosmos.audioSharingInteractor,
262                     audioManager,
263                     adapter,
264                     localBluetoothManager,
265                     fakeSystemClock,
266                     logger,
267                     listOf(
268                         createFactory(
269                             { cachedDevice -> cachedDevice.device == device1 },
270                             deviceItem1,
271                         ),
272                         createFactory(
273                             { cachedDevice -> cachedDevice.device == device2 },
274                             deviceItem2,
275                         ),
276                     ),
277                     listOf(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE),
278                     testScope.backgroundScope,
279                     dispatcher,
280                 )
281             `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
282             `when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
283 
284             val latest by collectLastValue(interactor.deviceItemUpdate)
285             val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
286             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
287 
288             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
289             assertThat(latestShowSeeAll).isFalse()
290         }
291     }
292 
293     @Test
294     fun testUpdateDeviceItems_showMaxDeviceItems_showSeeAll() {
295         testScope.runTest {
296             `when`(bluetoothTileDialogRepository.cachedDevices)
297                 .thenReturn(listOf(cachedDevice2, cachedDevice2, cachedDevice2, cachedDevice2))
298             `when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
299             interactor =
300                 DeviceItemInteractor(
301                     bluetoothTileDialogRepository,
302                     kosmos.audioSharingInteractor,
303                     audioManager,
304                     adapter,
305                     localBluetoothManager,
306                     fakeSystemClock,
307                     logger,
308                     listOf(createFactory({ true }, deviceItem2)),
309                     emptyList(),
310                     testScope.backgroundScope,
311                     dispatcher,
312                 )
313             val latest by collectLastValue(interactor.deviceItemUpdate)
314             val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
315             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
316 
317             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2, deviceItem2))
318             assertThat(latestShowSeeAll).isTrue()
319         }
320     }
321 
322     private fun createFactory(
323         isFilterMatchFunc: (CachedBluetoothDevice) -> Boolean,
324         deviceItem: DeviceItem,
325     ): DeviceItemFactory {
326         return object : DeviceItemFactory() {
327             override fun isFilterMatched(
328                 context: Context,
329                 cachedDevice: CachedBluetoothDevice,
330                 audioManager: AudioManager,
331                 audioSharingAvailable: Boolean,
332             ) = isFilterMatchFunc(cachedDevice)
333 
334             override fun create(context: Context, cachedDevice: CachedBluetoothDevice) = deviceItem
335         }
336     }
337 }
338