1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.statusbar.policy.bluetooth 16 17 import android.bluetooth.BluetoothProfile 18 import androidx.test.filters.SmallTest 19 import com.android.settingslib.bluetooth.CachedBluetoothDevice 20 import com.android.settingslib.bluetooth.LocalBluetoothAdapter 21 import com.android.settingslib.bluetooth.LocalBluetoothManager 22 import com.android.systemui.SysuiTestCase 23 import com.android.systemui.util.mockito.mock 24 import com.android.systemui.util.mockito.whenever 25 import com.google.common.truth.Truth.assertThat 26 import kotlinx.coroutines.ExperimentalCoroutinesApi 27 import kotlinx.coroutines.test.StandardTestDispatcher 28 import kotlinx.coroutines.test.TestCoroutineScheduler 29 import kotlinx.coroutines.test.TestDispatcher 30 import kotlinx.coroutines.test.TestScope 31 import org.junit.Before 32 import org.junit.Test 33 import org.mockito.Mock 34 import org.mockito.MockitoAnnotations 35 36 @OptIn(ExperimentalCoroutinesApi::class) 37 @SmallTest 38 class BluetoothRepositoryImplTest : SysuiTestCase() { 39 40 private lateinit var underTest: BluetoothRepositoryImpl 41 42 private lateinit var scheduler: TestCoroutineScheduler 43 private lateinit var dispatcher: TestDispatcher 44 private lateinit var testScope: TestScope 45 46 @Mock private lateinit var localBluetoothManager: LocalBluetoothManager 47 @Mock private lateinit var bluetoothAdapter: LocalBluetoothAdapter 48 49 @Before 50 fun setUp() { 51 MockitoAnnotations.initMocks(this) 52 whenever(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter) 53 54 scheduler = TestCoroutineScheduler() 55 dispatcher = StandardTestDispatcher(scheduler) 56 testScope = TestScope(dispatcher) 57 58 underTest = 59 BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager) 60 } 61 62 @Test 63 fun fetchConnectionStatusInBackground_currentDevicesEmpty_maxStateIsManagerState() { 64 whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) 65 66 val status = fetchConnectionStatus(currentDevices = emptyList()) 67 68 assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING) 69 } 70 71 @Test 72 fun fetchConnectionStatusInBackground_currentDevicesEmpty_nullManager_maxStateIsDisconnected() { 73 // This CONNECTING state should be unused because localBluetoothManager is null 74 whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) 75 underTest = 76 BluetoothRepositoryImpl( 77 testScope.backgroundScope, 78 dispatcher, 79 localBluetoothManager = null, 80 ) 81 82 val status = fetchConnectionStatus(currentDevices = emptyList()) 83 84 assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED) 85 } 86 87 @Test 88 fun fetchConnectionStatusInBackground_managerStateLargerThanDeviceStates_maxStateIsManager() { 89 whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) 90 val device1 = 91 mock<CachedBluetoothDevice>().also { 92 whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) 93 } 94 val device2 = 95 mock<CachedBluetoothDevice>().also { 96 whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) 97 } 98 99 val status = fetchConnectionStatus(currentDevices = listOf(device1, device2)) 100 101 assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING) 102 } 103 104 @Test 105 fun fetchConnectionStatusInBackground_oneCurrentDevice_maxStateIsDeviceState() { 106 whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) 107 val device = 108 mock<CachedBluetoothDevice>().also { 109 whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) 110 } 111 112 val status = fetchConnectionStatus(currentDevices = listOf(device)) 113 114 assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING) 115 } 116 117 @Test 118 fun fetchConnectionStatusInBackground_multipleDevices_maxStateIsHighestState() { 119 whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED) 120 121 val device1 = 122 mock<CachedBluetoothDevice>().also { 123 whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) 124 whenever(it.isConnected).thenReturn(false) 125 } 126 val device2 = 127 mock<CachedBluetoothDevice>().also { 128 whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED) 129 whenever(it.isConnected).thenReturn(true) 130 } 131 132 val status = fetchConnectionStatus(currentDevices = listOf(device1, device2)) 133 134 assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTED) 135 } 136 137 @Test 138 fun fetchConnectionStatusInBackground_devicesNotConnected_maxStateIsDisconnected() { 139 whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING) 140 141 // WHEN the devices say their state is CONNECTED but [isConnected] is false 142 val device1 = 143 mock<CachedBluetoothDevice>().also { 144 whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED) 145 whenever(it.isConnected).thenReturn(false) 146 } 147 val device2 = 148 mock<CachedBluetoothDevice>().also { 149 whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED) 150 whenever(it.isConnected).thenReturn(false) 151 } 152 153 val status = fetchConnectionStatus(currentDevices = listOf(device1, device2)) 154 155 // THEN the max state is DISCONNECTED 156 assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED) 157 } 158 159 @Test 160 fun fetchConnectionStatusInBackground_currentDevicesEmpty_connectedDevicesEmpty() { 161 val status = fetchConnectionStatus(currentDevices = emptyList()) 162 163 assertThat(status.connectedDevices).isEmpty() 164 } 165 166 @Test 167 fun fetchConnectionStatusInBackground_oneCurrentDeviceDisconnected_connectedDevicesEmpty() { 168 val device = 169 mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) } 170 171 val status = fetchConnectionStatus(currentDevices = listOf(device)) 172 173 assertThat(status.connectedDevices).isEmpty() 174 } 175 176 @Test 177 fun fetchConnectionStatusInBackground_oneCurrentDeviceConnected_connectedDevicesHasDevice() { 178 val device = 179 mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) } 180 181 val status = fetchConnectionStatus(currentDevices = listOf(device)) 182 183 assertThat(status.connectedDevices).isEqualTo(listOf(device)) 184 } 185 186 @Test 187 fun fetchConnectionStatusInBackground_multipleDevices_connectedDevicesHasOnlyConnected() { 188 val device1Connected = 189 mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) } 190 val device2Disconnected = 191 mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) } 192 val device3Connected = 193 mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) } 194 195 val status = 196 fetchConnectionStatus( 197 currentDevices = listOf(device1Connected, device2Disconnected, device3Connected) 198 ) 199 200 assertThat(status.connectedDevices).isEqualTo(listOf(device1Connected, device3Connected)) 201 } 202 203 private fun fetchConnectionStatus( 204 currentDevices: Collection<CachedBluetoothDevice> 205 ): ConnectionStatusModel { 206 var receivedStatus: ConnectionStatusModel? = null 207 underTest.fetchConnectionStatusInBackground(currentDevices) { status -> 208 receivedStatus = status 209 } 210 scheduler.runCurrent() 211 return receivedStatus!! 212 } 213 } 214