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.util.time.FakeSystemClock
31 import com.google.common.truth.Truth.assertThat
32 import kotlinx.coroutines.CoroutineDispatcher
33 import kotlinx.coroutines.test.TestScope
34 import kotlinx.coroutines.test.UnconfinedTestDispatcher
35 import kotlinx.coroutines.test.runTest
36 import org.junit.Before
37 import org.junit.Rule
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 import org.mockito.Mock
41 import org.mockito.Mockito.`when`
42 import org.mockito.junit.MockitoJUnit
43 import org.mockito.junit.MockitoRule
44 
45 @SmallTest
46 @RunWith(AndroidJUnit4::class)
47 @TestableLooper.RunWithLooper(setAsMainLooper = true)
48 class DeviceItemInteractorTest : SysuiTestCase() {
49 
50     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
51 
52     @Mock private lateinit var bluetoothTileDialogRepository: BluetoothTileDialogRepository
53 
54     @Mock private lateinit var cachedDevice1: CachedBluetoothDevice
55 
56     @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
57 
58     @Mock private lateinit var device1: BluetoothDevice
59 
60     @Mock private lateinit var device2: BluetoothDevice
61 
62     @Mock private lateinit var deviceItem1: DeviceItem
63 
64     @Mock private lateinit var deviceItem2: DeviceItem
65 
66     @Mock private lateinit var audioManager: AudioManager
67 
68     @Mock private lateinit var adapter: BluetoothAdapter
69 
70     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
71 
72     @Mock private lateinit var logger: BluetoothTileDialogLogger
73 
74     private val fakeSystemClock = FakeSystemClock()
75 
76     private lateinit var interactor: DeviceItemInteractor
77 
78     private lateinit var dispatcher: CoroutineDispatcher
79 
80     private lateinit var testScope: TestScope
81 
82     @Before
83     fun setUp() {
84         dispatcher = UnconfinedTestDispatcher()
85         testScope = TestScope(dispatcher)
86         interactor =
87             DeviceItemInteractor(
88                 bluetoothTileDialogRepository,
89                 audioManager,
90                 adapter,
91                 localBluetoothManager,
92                 fakeSystemClock,
93                 logger,
94                 testScope.backgroundScope,
95                 dispatcher
96             )
97 
98         `when`(deviceItem1.cachedBluetoothDevice).thenReturn(cachedDevice1)
99         `when`(deviceItem2.cachedBluetoothDevice).thenReturn(cachedDevice2)
100         `when`(cachedDevice1.address).thenReturn("ADDRESS")
101         `when`(cachedDevice1.device).thenReturn(device1)
102         `when`(cachedDevice2.device).thenReturn(device2)
103         `when`(bluetoothTileDialogRepository.cachedDevices)
104             .thenReturn(listOf(cachedDevice1, cachedDevice2))
105     }
106 
107     @Test
108     fun testUpdateDeviceItems_noCachedDevice_returnEmpty() {
109         testScope.runTest {
110             `when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(emptyList())
111             interactor.setDeviceItemFactoryListForTesting(
112                 listOf(createFactory({ true }, deviceItem1))
113             )
114 
115             val latest by collectLastValue(interactor.deviceItemUpdate)
116             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
117 
118             assertThat(latest).isEqualTo(emptyList<DeviceItem>())
119         }
120     }
121 
122     @Test
123     fun testUpdateDeviceItems_hasCachedDevice_filterNotMatch_returnEmpty() {
124         testScope.runTest {
125             `when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(listOf(cachedDevice1))
126             interactor.setDeviceItemFactoryListForTesting(
127                 listOf(createFactory({ false }, deviceItem1))
128             )
129 
130             val latest by collectLastValue(interactor.deviceItemUpdate)
131             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
132 
133             assertThat(latest).isEqualTo(emptyList<DeviceItem>())
134         }
135     }
136 
137     @Test
138     fun testUpdateDeviceItems_hasCachedDevice_filterMatch_returnDeviceItem() {
139         testScope.runTest {
140             `when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(listOf(cachedDevice1))
141             interactor.setDeviceItemFactoryListForTesting(
142                 listOf(createFactory({ true }, deviceItem1))
143             )
144 
145             val latest by collectLastValue(interactor.deviceItemUpdate)
146             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
147 
148             assertThat(latest).isEqualTo(listOf(deviceItem1))
149         }
150     }
151 
152     @Test
153     fun testUpdateDeviceItems_hasCachedDevice_filterMatch_returnMultipleDeviceItem() {
154         testScope.runTest {
155             `when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
156             interactor.setDeviceItemFactoryListForTesting(
157                 listOf(createFactory({ false }, deviceItem1), createFactory({ true }, deviceItem2))
158             )
159 
160             val latest by collectLastValue(interactor.deviceItemUpdate)
161             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
162 
163             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2))
164         }
165     }
166 
167     @Test
168     fun testUpdateDeviceItems_sortByDisplayPriority() {
169         testScope.runTest {
170             `when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
171             interactor.setDeviceItemFactoryListForTesting(
172                 listOf(
173                     createFactory({ cachedDevice -> cachedDevice.device == device1 }, deviceItem1),
174                     createFactory({ cachedDevice -> cachedDevice.device == device2 }, deviceItem2)
175                 )
176             )
177             interactor.setDisplayPriorityForTesting(
178                 listOf(
179                     DeviceItemType.SAVED_BLUETOOTH_DEVICE,
180                     DeviceItemType.CONNECTED_BLUETOOTH_DEVICE
181                 )
182             )
183             `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
184             `when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
185 
186             val latest by collectLastValue(interactor.deviceItemUpdate)
187             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
188 
189             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
190         }
191     }
192 
193     @Test
194     fun testUpdateDeviceItems_sameType_sortByRecentlyConnected() {
195         testScope.runTest {
196             `when`(adapter.mostRecentlyConnectedDevices).thenReturn(listOf(device2, device1))
197             interactor.setDeviceItemFactoryListForTesting(
198                 listOf(
199                     createFactory({ cachedDevice -> cachedDevice.device == device1 }, deviceItem1),
200                     createFactory({ cachedDevice -> cachedDevice.device == device2 }, deviceItem2)
201                 )
202             )
203             interactor.setDisplayPriorityForTesting(
204                 listOf(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
205             )
206             `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
207             `when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
208 
209             val latest by collectLastValue(interactor.deviceItemUpdate)
210             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
211 
212             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
213         }
214     }
215 
216     private fun createFactory(
217         isFilterMatchFunc: (CachedBluetoothDevice) -> Boolean,
218         deviceItem: DeviceItem
219     ): DeviceItemFactory {
220         return object : DeviceItemFactory() {
221             override fun isFilterMatched(
222                 context: Context,
223                 cachedDevice: CachedBluetoothDevice,
224                 audioManager: AudioManager
225             ) = isFilterMatchFunc(cachedDevice)
226 
227             override fun create(context: Context, cachedDevice: CachedBluetoothDevice) = deviceItem
228         }
229     }
230 }
231