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