1 package com.android.systemui.qs.tiles 2 3 import android.bluetooth.BluetoothDevice 4 import android.os.Handler 5 import android.os.Looper 6 import android.os.UserManager 7 import android.testing.AndroidTestingRunner 8 import android.testing.TestableLooper 9 import android.testing.TestableLooper.RunWithLooper 10 import androidx.test.filters.SmallTest 11 import com.android.internal.logging.MetricsLogger 12 import com.android.settingslib.Utils 13 import com.android.settingslib.bluetooth.CachedBluetoothDevice 14 import com.android.systemui.R 15 import com.android.systemui.SysuiTestCase 16 import com.android.systemui.classifier.FalsingManagerFake 17 import com.android.systemui.plugins.ActivityStarter 18 import com.android.systemui.plugins.FalsingManager 19 import com.android.systemui.plugins.qs.QSTile 20 import com.android.systemui.plugins.statusbar.StatusBarStateController 21 import com.android.systemui.qs.QSHost 22 import com.android.systemui.qs.QsEventLogger 23 import com.android.systemui.qs.logging.QSLogger 24 import com.android.systemui.qs.tileimpl.QSTileImpl 25 import com.android.systemui.statusbar.policy.BluetoothController 26 import com.android.systemui.util.mockito.any 27 import com.android.systemui.util.mockito.eq 28 import com.android.systemui.util.mockito.mock 29 import com.android.systemui.util.mockito.whenever 30 import com.google.common.truth.Truth.assertThat 31 import org.junit.After 32 import org.junit.Before 33 import org.junit.Test 34 import org.junit.runner.RunWith 35 import org.mockito.Mock 36 import org.mockito.Mockito.times 37 import org.mockito.Mockito.verify 38 import org.mockito.MockitoAnnotations 39 40 @RunWith(AndroidTestingRunner::class) 41 @RunWithLooper(setAsMainLooper = true) 42 @SmallTest 43 class BluetoothTileTest : SysuiTestCase() { 44 45 @Mock private lateinit var qsLogger: QSLogger 46 @Mock private lateinit var qsHost: QSHost 47 @Mock private lateinit var metricsLogger: MetricsLogger 48 private val falsingManager = FalsingManagerFake() 49 @Mock private lateinit var statusBarStateController: StatusBarStateController 50 @Mock private lateinit var activityStarter: ActivityStarter 51 @Mock private lateinit var bluetoothController: BluetoothController 52 @Mock private lateinit var uiEventLogger: QsEventLogger 53 54 private lateinit var testableLooper: TestableLooper 55 private lateinit var tile: FakeBluetoothTile 56 57 @Before 58 fun setUp() { 59 MockitoAnnotations.initMocks(this) 60 testableLooper = TestableLooper.get(this) 61 62 whenever(qsHost.context).thenReturn(mContext) 63 64 tile = 65 FakeBluetoothTile( 66 qsHost, 67 uiEventLogger, 68 testableLooper.looper, 69 Handler(testableLooper.looper), 70 falsingManager, 71 metricsLogger, 72 statusBarStateController, 73 activityStarter, 74 qsLogger, 75 bluetoothController, 76 ) 77 78 tile.initialize() 79 testableLooper.processAllMessages() 80 } 81 82 @After 83 fun tearDown() { 84 tile.destroy() 85 testableLooper.processAllMessages() 86 } 87 88 @Test 89 fun testRestrictionChecked() { 90 tile.refreshState() 91 testableLooper.processAllMessages() 92 93 assertThat(tile.restrictionChecked).isEqualTo(UserManager.DISALLOW_BLUETOOTH) 94 } 95 96 @Test 97 fun testIcon_whenDisabled_isOffState() { 98 val state = QSTile.BooleanState() 99 disableBluetooth() 100 101 tile.handleUpdateState(state, /* arg= */ null) 102 103 assertThat(state.icon) 104 .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) 105 } 106 107 @Test 108 fun testIcon_whenDisconnected_isOffState() { 109 val state = QSTile.BooleanState() 110 enableBluetooth() 111 setBluetoothDisconnected() 112 113 tile.handleUpdateState(state, /* arg= */ null) 114 115 assertThat(state.icon) 116 .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) 117 } 118 119 @Test 120 fun testIcon_whenConnected_isOnState() { 121 val state = QSTile.BooleanState() 122 enableBluetooth() 123 setBluetoothConnected() 124 125 tile.handleUpdateState(state, /* arg= */ null) 126 127 assertThat(state.icon) 128 .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on)) 129 } 130 131 @Test 132 fun testIcon_whenConnecting_isSearchState() { 133 val state = QSTile.BooleanState() 134 enableBluetooth() 135 setBluetoothConnecting() 136 137 tile.handleUpdateState(state, /* arg= */ null) 138 139 assertThat(state.icon) 140 .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search)) 141 } 142 143 @Test 144 fun testSecondaryLabel_whenBatteryMetadataAvailable_isMetadataBatteryLevelState() { 145 val cachedDevice = mock<CachedBluetoothDevice>() 146 val state = QSTile.BooleanState() 147 listenToDeviceMetadata(state, cachedDevice, 50) 148 149 tile.handleUpdateState(state, /* arg= */ null) 150 151 assertThat(state.secondaryLabel) 152 .isEqualTo( 153 mContext.getString( 154 R.string.quick_settings_bluetooth_secondary_label_battery_level, 155 Utils.formatPercentage(50) 156 ) 157 ) 158 verify(bluetoothController) 159 .addOnMetadataChangedListener(eq(cachedDevice), any(), any()) 160 } 161 162 @Test 163 fun testSecondaryLabel_whenBatteryMetadataUnavailable_isBluetoothBatteryLevelState() { 164 val state = QSTile.BooleanState() 165 val cachedDevice = mock<CachedBluetoothDevice>() 166 listenToDeviceMetadata(state, cachedDevice, 50) 167 val cachedDevice2 = mock<CachedBluetoothDevice>() 168 val btDevice = mock<BluetoothDevice>() 169 whenever(cachedDevice2.device).thenReturn(btDevice) 170 whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(null) 171 whenever(cachedDevice2.minBatteryLevelWithMemberDevices).thenReturn(25) 172 addConnectedDevice(cachedDevice2) 173 174 tile.handleUpdateState(state, /* arg= */ null) 175 176 assertThat(state.secondaryLabel) 177 .isEqualTo( 178 mContext.getString( 179 R.string.quick_settings_bluetooth_secondary_label_battery_level, 180 Utils.formatPercentage(25) 181 ) 182 ) 183 verify(bluetoothController, times(1)) 184 .removeOnMetadataChangedListener(eq(cachedDevice), any()) 185 } 186 187 @Test 188 fun testMetadataListener_whenDisconnected_isUnregistered() { 189 val state = QSTile.BooleanState() 190 val cachedDevice = mock<CachedBluetoothDevice>() 191 listenToDeviceMetadata(state, cachedDevice, 50) 192 disableBluetooth() 193 194 tile.handleUpdateState(state, null) 195 196 verify(bluetoothController, times(1)) 197 .removeOnMetadataChangedListener(eq(cachedDevice), any()) 198 } 199 200 @Test 201 fun testMetadataListener_whenTileNotListening_isUnregistered() { 202 val state = QSTile.BooleanState() 203 val cachedDevice = mock<CachedBluetoothDevice>() 204 listenToDeviceMetadata(state, cachedDevice, 50) 205 206 tile.handleSetListening(false) 207 208 verify(bluetoothController, times(1)) 209 .removeOnMetadataChangedListener(eq(cachedDevice), any()) 210 } 211 212 private class FakeBluetoothTile( 213 qsHost: QSHost, 214 uiEventLogger: QsEventLogger, 215 backgroundLooper: Looper, 216 mainHandler: Handler, 217 falsingManager: FalsingManager, 218 metricsLogger: MetricsLogger, 219 statusBarStateController: StatusBarStateController, 220 activityStarter: ActivityStarter, 221 qsLogger: QSLogger, 222 bluetoothController: BluetoothController, 223 ) : 224 BluetoothTile( 225 qsHost, 226 uiEventLogger, 227 backgroundLooper, 228 mainHandler, 229 falsingManager, 230 metricsLogger, 231 statusBarStateController, 232 activityStarter, 233 qsLogger, 234 bluetoothController, 235 ) { 236 var restrictionChecked: String? = null 237 238 override fun checkIfRestrictionEnforcedByAdminOnly( 239 state: QSTile.State?, 240 userRestriction: String? 241 ) { 242 restrictionChecked = userRestriction 243 } 244 } 245 246 fun enableBluetooth() { 247 whenever(bluetoothController.isBluetoothEnabled).thenReturn(true) 248 } 249 250 fun disableBluetooth() { 251 whenever(bluetoothController.isBluetoothEnabled).thenReturn(false) 252 } 253 254 fun setBluetoothDisconnected() { 255 whenever(bluetoothController.isBluetoothConnecting).thenReturn(false) 256 whenever(bluetoothController.isBluetoothConnected).thenReturn(false) 257 } 258 259 fun setBluetoothConnected() { 260 whenever(bluetoothController.isBluetoothConnecting).thenReturn(false) 261 whenever(bluetoothController.isBluetoothConnected).thenReturn(true) 262 } 263 264 fun setBluetoothConnecting() { 265 whenever(bluetoothController.isBluetoothConnected).thenReturn(false) 266 whenever(bluetoothController.isBluetoothConnecting).thenReturn(true) 267 } 268 269 fun addConnectedDevice(device: CachedBluetoothDevice) { 270 whenever(bluetoothController.connectedDevices).thenReturn(listOf(device)) 271 } 272 273 fun listenToDeviceMetadata( 274 state: QSTile.BooleanState, 275 cachedDevice: CachedBluetoothDevice, 276 batteryLevel: Int 277 ) { 278 val btDevice = mock<BluetoothDevice>() 279 whenever(cachedDevice.device).thenReturn(btDevice) 280 whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)) 281 .thenReturn(batteryLevel.toString().toByteArray()) 282 enableBluetooth() 283 setBluetoothConnected() 284 addConnectedDevice(cachedDevice) 285 tile.handleUpdateState(state, /* arg= */ null) 286 } 287 } 288