1 /* 2 * Copyright (C) 2022 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.server.input 18 19 import android.bluetooth.BluetoothAdapter 20 import android.bluetooth.BluetoothDevice 21 import android.bluetooth.BluetoothManager 22 import android.hardware.BatteryState.STATUS_CHARGING 23 import android.hardware.BatteryState.STATUS_DISCHARGING 24 import android.hardware.BatteryState.STATUS_FULL 25 import android.hardware.BatteryState.STATUS_UNKNOWN 26 import android.hardware.input.HostUsiVersion 27 import android.hardware.input.IInputDeviceBatteryListener 28 import android.hardware.input.IInputDeviceBatteryState 29 import android.hardware.input.IInputDevicesChangedListener 30 import android.hardware.input.IInputManager 31 import android.hardware.input.InputManager 32 import android.hardware.input.InputManagerGlobal 33 import android.os.Binder 34 import android.os.IBinder 35 import android.os.test.TestLooper 36 import android.platform.test.annotations.Presubmit 37 import android.testing.TestableContext 38 import android.view.InputDevice 39 import androidx.test.core.app.ApplicationProvider 40 import com.android.server.input.BatteryController.BluetoothBatteryManager 41 import com.android.server.input.BatteryController.BluetoothBatteryManager.BluetoothBatteryListener 42 import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS 43 import com.android.server.input.BatteryController.UEventBatteryListener 44 import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS 45 import org.hamcrest.Description 46 import org.hamcrest.Matcher 47 import org.hamcrest.MatcherAssert.assertThat 48 import org.hamcrest.Matchers 49 import org.hamcrest.TypeSafeMatcher 50 import org.hamcrest.core.IsEqual.equalTo 51 import org.junit.After 52 import org.junit.Assert.assertEquals 53 import org.junit.Assert.assertFalse 54 import org.junit.Assert.assertTrue 55 import org.junit.Assert.fail 56 import org.junit.Before 57 import org.junit.Rule 58 import org.junit.Test 59 import org.mockito.ArgumentCaptor 60 import org.mockito.ArgumentMatchers.any 61 import org.mockito.ArgumentMatchers.notNull 62 import org.mockito.Mock 63 import org.mockito.Mockito.anyInt 64 import org.mockito.Mockito.clearInvocations 65 import org.mockito.Mockito.eq 66 import org.mockito.Mockito.mock 67 import org.mockito.Mockito.never 68 import org.mockito.Mockito.times 69 import org.mockito.Mockito.verify 70 import org.mockito.Mockito.verifyNoMoreInteractions 71 import org.mockito.Mockito.`when` 72 import org.mockito.hamcrest.MockitoHamcrest 73 import org.mockito.junit.MockitoJUnit 74 import org.mockito.verification.VerificationMode 75 76 private fun createInputDevice( 77 deviceId: Int, 78 hasBattery: Boolean = true, 79 supportsUsi: Boolean = false, 80 generation: Int = -1, 81 ): InputDevice = 82 InputDevice.Builder() 83 .setId(deviceId) 84 .setName("Device $deviceId") 85 .setDescriptor("descriptor $deviceId") 86 .setExternal(true) 87 .setHasBattery(hasBattery) 88 .setUsiVersion(if (supportsUsi) HostUsiVersion(1, 0) else null) 89 .setGeneration(generation) 90 .build() 91 92 // Returns a matcher that helps match member variables of a class. 93 private fun <T, U> memberMatcher( 94 member: String, 95 memberProvider: (T) -> U, 96 match: Matcher<U> 97 ): TypeSafeMatcher<T> = 98 object : TypeSafeMatcher<T>() { 99 100 override fun matchesSafely(item: T?): Boolean { 101 return match.matches(memberProvider(item!!)) 102 } 103 104 override fun describeMismatchSafely(item: T?, mismatchDescription: Description?) { 105 match.describeMismatch(item, mismatchDescription) 106 } 107 108 override fun describeTo(description: Description?) { 109 match.describeTo(description?.appendText("matches member $member")) 110 } 111 } 112 113 // Returns a matcher for IInputDeviceBatteryState that optionally matches some arguments. 114 private fun matchesState( 115 deviceId: Int, 116 isPresent: Boolean = true, 117 status: Int? = null, 118 capacity: Float? = null, 119 eventTime: Long? = null 120 ): Matcher<IInputDeviceBatteryState> { 121 val batteryStateMatchers = mutableListOf<Matcher<IInputDeviceBatteryState>>( 122 memberMatcher("deviceId", { it.deviceId }, equalTo(deviceId)), 123 memberMatcher("isPresent", { it.isPresent }, equalTo(isPresent)) 124 ) 125 if (eventTime != null) { 126 batteryStateMatchers.add(memberMatcher("updateTime", { it.updateTime }, equalTo(eventTime))) 127 } 128 if (status != null) { 129 batteryStateMatchers.add(memberMatcher("status", { it.status }, equalTo(status))) 130 } 131 if (capacity != null) { 132 batteryStateMatchers.add(memberMatcher("capacity", { it.capacity }, equalTo(capacity))) 133 } 134 return Matchers.allOf(batteryStateMatchers) 135 } 136 137 private fun isInvalidBatteryState(deviceId: Int): Matcher<IInputDeviceBatteryState> = 138 matchesState(deviceId, isPresent = false, status = STATUS_UNKNOWN, capacity = Float.NaN) 139 140 // Helpers used to verify interactions with a mocked battery listener. 141 private fun IInputDeviceBatteryListener.verifyNotified( 142 deviceId: Int, 143 mode: VerificationMode = times(1), 144 isPresent: Boolean = true, 145 status: Int? = null, 146 capacity: Float? = null, 147 eventTime: Long? = null 148 ) { 149 verifyNotified(matchesState(deviceId, isPresent, status, capacity, eventTime), mode) 150 } 151 152 private fun IInputDeviceBatteryListener.verifyNotified( 153 matcher: Matcher<IInputDeviceBatteryState>, 154 mode: VerificationMode = times(1) 155 ) { 156 verify(this, mode).onBatteryStateChanged(MockitoHamcrest.argThat(matcher)) 157 } 158 159 private fun createMockListener(): IInputDeviceBatteryListener { 160 val listener = mock(IInputDeviceBatteryListener::class.java) 161 val binder = mock(Binder::class.java) 162 `when`(listener.asBinder()).thenReturn(binder) 163 return listener 164 } 165 166 /** 167 * Tests for {@link InputDeviceBatteryController}. 168 * 169 * Build/Install/Run: 170 * atest FrameworksServicesTests:InputDeviceBatteryControllerTests 171 */ 172 @Presubmit 173 class BatteryControllerTests { 174 companion object { 175 const val PID = 42 176 const val DEVICE_ID = 13 177 const val SECOND_DEVICE_ID = 11 178 const val USI_DEVICE_ID = 101 179 const val SECOND_USI_DEVICE_ID = 102 180 const val BT_DEVICE_ID = 100001 181 const val SECOND_BT_DEVICE_ID = 100002 182 const val TIMESTAMP = 123456789L 183 } 184 185 @get:Rule 186 val rule = MockitoJUnit.rule()!! 187 188 @Mock 189 private lateinit var native: NativeInputManagerService 190 @Mock 191 private lateinit var iInputManager: IInputManager 192 @Mock 193 private lateinit var uEventManager: UEventManager 194 @Mock 195 private lateinit var bluetoothBatteryManager: BluetoothBatteryManager 196 197 private lateinit var batteryController: BatteryController 198 private lateinit var context: TestableContext 199 private lateinit var testLooper: TestLooper 200 private lateinit var devicesChangedListener: IInputDevicesChangedListener 201 private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>() 202 203 @Before 204 fun setup() { 205 context = TestableContext(ApplicationProvider.getApplicationContext()) 206 testLooper = TestLooper() 207 InputManagerGlobal.resetInstance(iInputManager) 208 val inputManager = InputManager(context) 209 context.addMockSystemService(InputManager::class.java, inputManager) 210 `when`(iInputManager.inputDeviceIds).then { 211 deviceGenerationMap.keys.toIntArray() 212 } 213 addInputDevice(DEVICE_ID) 214 addInputDevice(SECOND_DEVICE_ID) 215 216 batteryController = BatteryController(context, native, testLooper.looper, uEventManager, 217 bluetoothBatteryManager) 218 batteryController.systemRunning() 219 val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java) 220 verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture()) 221 devicesChangedListener = listenerCaptor.value 222 testLooper.dispatchAll() 223 } 224 225 private fun notifyDeviceChanged( 226 deviceId: Int, 227 hasBattery: Boolean = true, 228 supportsUsi: Boolean = false 229 ) { 230 val generation = deviceGenerationMap[deviceId]?.plus(1) 231 ?: throw IllegalArgumentException("Device $deviceId was never added!") 232 deviceGenerationMap[deviceId] = generation 233 234 `when`(iInputManager.getInputDevice(deviceId)) 235 .thenReturn(createInputDevice(deviceId, hasBattery, supportsUsi, generation)) 236 val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) } 237 if (::devicesChangedListener.isInitialized) { 238 devicesChangedListener.onInputDevicesChanged(list.toIntArray()) 239 } 240 } 241 242 private fun addInputDevice( 243 deviceId: Int, 244 hasBattery: Boolean = true, 245 supportsUsi: Boolean = false, 246 ) { 247 deviceGenerationMap[deviceId] = 0 248 notifyDeviceChanged(deviceId, hasBattery, supportsUsi) 249 } 250 251 private fun createBluetoothDevice(address: String): BluetoothDevice { 252 return context.getSystemService(BluetoothManager::class.java)!! 253 .adapter.getRemoteDevice(address) 254 } 255 256 @After 257 fun tearDown() { 258 InputManagerGlobal.clearInstance() 259 } 260 261 @Test 262 fun testRegisterAndUnregisterBinderLifecycle() { 263 val listener = createMockListener() 264 // Ensure the binder lifecycle is tracked when registering a listener. 265 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 266 verify(listener.asBinder()).linkToDeath(notNull(), anyInt()) 267 batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID) 268 verify(listener.asBinder(), times(1)).linkToDeath(notNull(), anyInt()) 269 270 // Ensure the binder lifecycle stops being tracked when all devices stopped being monitored. 271 batteryController.unregisterBatteryListener(SECOND_DEVICE_ID, listener, PID) 272 verify(listener.asBinder(), never()).unlinkToDeath(notNull(), anyInt()) 273 batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID) 274 verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt()) 275 } 276 277 @Test 278 fun testOneListenerPerProcess() { 279 val listener1 = createMockListener() 280 batteryController.registerBatteryListener(DEVICE_ID, listener1, PID) 281 verify(listener1.asBinder()).linkToDeath(notNull(), anyInt()) 282 283 // If a second listener is added for the same process, a security exception is thrown. 284 val listener2 = createMockListener() 285 try { 286 batteryController.registerBatteryListener(DEVICE_ID, listener2, PID) 287 fail("Expected security exception when registering more than one listener per process") 288 } catch (ignored: SecurityException) { 289 } 290 } 291 292 @Test 293 fun testProcessDeathRemovesListener() { 294 val deathRecipient = ArgumentCaptor.forClass(IBinder.DeathRecipient::class.java) 295 val listener = createMockListener() 296 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 297 verify(listener.asBinder()).linkToDeath(deathRecipient.capture(), anyInt()) 298 299 // When the binder dies, the callback is unregistered. 300 deathRecipient.value!!.binderDied(listener.asBinder()) 301 verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt()) 302 303 // It is now possible to register the same listener again. 304 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 305 verify(listener.asBinder(), times(2)).linkToDeath(notNull(), anyInt()) 306 } 307 308 @Test 309 fun testRegisteringListenerNotifiesStateImmediately() { 310 `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_FULL) 311 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(100) 312 val listener = createMockListener() 313 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 314 listener.verifyNotified(DEVICE_ID, status = STATUS_FULL, capacity = 1.0f) 315 316 `when`(native.getBatteryStatus(SECOND_DEVICE_ID)).thenReturn(STATUS_CHARGING) 317 `when`(native.getBatteryCapacity(SECOND_DEVICE_ID)).thenReturn(78) 318 batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID) 319 listener.verifyNotified(SECOND_DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f) 320 } 321 322 @Test 323 fun testListenersNotifiedOnUEventNotification() { 324 `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/sys/dev/test/device1") 325 `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING) 326 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) 327 val listener = createMockListener() 328 val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) 329 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 330 // The device paths for UEvent notifications do not include the "/sys" prefix, so verify 331 // that the added listener is configured to match the path without that prefix. 332 verify(uEventManager) 333 .addListener(uEventListener.capture(), eq("DEVPATH=/dev/test/device1")) 334 listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f) 335 336 // If the battery state has changed when an UEvent is sent, the listeners are notified. 337 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80) 338 uEventListener.value!!.onBatteryUEvent(TIMESTAMP) 339 listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f, 340 eventTime = TIMESTAMP) 341 342 // If the battery state has not changed when an UEvent is sent, the listeners are not 343 // notified. 344 clearInvocations(listener) 345 uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1) 346 verifyNoMoreInteractions(listener) 347 348 batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID) 349 verify(uEventManager).removeListener(uEventListener.capture()) 350 assertEquals("The same observer must be registered and unregistered", 351 uEventListener.allValues[0], uEventListener.allValues[1]) 352 } 353 354 @Test 355 fun testBatteryPresenceChanged() { 356 `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1") 357 `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING) 358 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) 359 val listener = createMockListener() 360 val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) 361 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 362 verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1")) 363 listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f) 364 365 // If the battery presence for the InputDevice changes, the listener is notified. 366 notifyDeviceChanged(DEVICE_ID, hasBattery = false) 367 testLooper.dispatchNext() 368 listener.verifyNotified(isInvalidBatteryState(DEVICE_ID)) 369 // Since the battery is no longer present, the UEventListener should be removed. 370 verify(uEventManager).removeListener(uEventListener.value) 371 372 // If the battery becomes present again, the listener is notified. 373 notifyDeviceChanged(DEVICE_ID, hasBattery = true) 374 testLooper.dispatchNext() 375 listener.verifyNotified(DEVICE_ID, mode = times(2), status = STATUS_CHARGING, 376 capacity = 0.78f) 377 // Ensure that a new UEventListener was added. 378 verify(uEventManager, times(2)) 379 .addListener(uEventListener.capture(), eq("DEVPATH=/test/device1")) 380 } 381 382 @Test 383 fun testStartPollingWhenListenerIsRegistered() { 384 val listener = createMockListener() 385 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) 386 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 387 listener.verifyNotified(DEVICE_ID, capacity = 0.78f) 388 389 // Assume there is a change in the battery state. Ensure the listener is not notified 390 // while the polling period has not elapsed. 391 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80) 392 testLooper.moveTimeForward(1) 393 testLooper.dispatchAll() 394 listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f) 395 396 // Move the time forward so that the polling period has elapsed. 397 // The listener should be notified. 398 testLooper.moveTimeForward(POLLING_PERIOD_MILLIS - 1) 399 assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle) 400 testLooper.dispatchNext() 401 listener.verifyNotified(DEVICE_ID, capacity = 0.80f) 402 403 // Move the time forward so that another polling period has elapsed. 404 // The battery should still be polled, but there is no change so listeners are not notified. 405 testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) 406 assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle) 407 testLooper.dispatchNext() 408 listener.verifyNotified(DEVICE_ID, mode = times(1), capacity = 0.80f) 409 } 410 411 @Test 412 fun testNoPollingWhenTheDeviceIsNotInteractive() { 413 batteryController.onInteractiveChanged(false /*interactive*/) 414 415 val listener = createMockListener() 416 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) 417 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 418 listener.verifyNotified(DEVICE_ID, capacity = 0.78f) 419 420 // The battery state changed, but we should not be polling for battery changes when the 421 // device is not interactive. 422 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80) 423 testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) 424 assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle) 425 testLooper.dispatchAll() 426 listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f) 427 428 // The device is now interactive. Battery state polling begins immediately. 429 batteryController.onInteractiveChanged(true /*interactive*/) 430 testLooper.dispatchNext() 431 listener.verifyNotified(DEVICE_ID, capacity = 0.80f) 432 433 // Ensure that we continue to poll for battery changes. 434 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(90) 435 testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) 436 assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle) 437 testLooper.dispatchNext() 438 listener.verifyNotified(DEVICE_ID, capacity = 0.90f) 439 } 440 441 @Test 442 fun testGetBatteryState() { 443 `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING) 444 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) 445 val batteryState = batteryController.getBatteryState(DEVICE_ID) 446 assertThat("battery state matches", batteryState, 447 matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)) 448 } 449 450 @Test 451 fun testGetBatteryStateWithListener() { 452 val listener = createMockListener() 453 `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING) 454 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) 455 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 456 listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f) 457 458 // If getBatteryState() is called when a listener is monitoring the device and there is a 459 // change in the battery state, the listener is also notified. 460 `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80) 461 val batteryState = batteryController.getBatteryState(DEVICE_ID) 462 assertThat("battery matches state", batteryState, 463 matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f)) 464 listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f) 465 } 466 467 @Test 468 fun testUsiDeviceIsMonitoredPersistently() { 469 `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") 470 addInputDevice(USI_DEVICE_ID, supportsUsi = true) 471 testLooper.dispatchNext() 472 473 // Even though there is no listener added for this device, it is being monitored. 474 val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) 475 verify(uEventManager) 476 .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device")) 477 478 // Add and remove a listener for the device. 479 val listener = createMockListener() 480 batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) 481 batteryController.unregisterBatteryListener(USI_DEVICE_ID, listener, PID) 482 483 // The device is still being monitored. 484 verify(uEventManager, never()).removeListener(uEventListener.value) 485 } 486 487 @Test 488 fun testNoPollingWhenUsiDevicesAreMonitored() { 489 `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") 490 addInputDevice(USI_DEVICE_ID, supportsUsi = true) 491 testLooper.dispatchNext() 492 `when`(native.getBatteryDevicePath(SECOND_USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device2") 493 addInputDevice(SECOND_USI_DEVICE_ID, supportsUsi = true) 494 testLooper.dispatchNext() 495 496 testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) 497 assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle) 498 499 // Add a listener. 500 val listener = createMockListener() 501 batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) 502 503 testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) 504 assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle) 505 } 506 507 @Test 508 fun testExpectedFlowForUsiBattery() { 509 `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") 510 `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING) 511 `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78) 512 513 addInputDevice(USI_DEVICE_ID, supportsUsi = true) 514 testLooper.dispatchNext() 515 val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) 516 verify(uEventManager) 517 .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device")) 518 519 // A USI device's battery state is not valid until the first UEvent notification. 520 // Add a listener, and ensure it is notified that the battery state is not present. 521 val listener = createMockListener() 522 batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) 523 listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID)) 524 525 // Ensure that querying for battery state also returns the same invalid result. 526 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 527 isInvalidBatteryState(USI_DEVICE_ID)) 528 529 // There is a UEvent signaling a battery change. The battery state is now valid. 530 uEventListener.value!!.onBatteryUEvent(TIMESTAMP) 531 listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f) 532 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 533 matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)) 534 535 // There is another UEvent notification. The battery state is now updated. 536 `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(64) 537 uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1) 538 listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f) 539 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 540 matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f)) 541 542 // The battery state is still valid after a millisecond. 543 testLooper.moveTimeForward(1) 544 testLooper.dispatchAll() 545 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 546 matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f)) 547 548 // The battery is no longer present after the timeout expires. 549 testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 1) 550 testLooper.dispatchNext() 551 listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID), times(2)) 552 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 553 isInvalidBatteryState(USI_DEVICE_ID)) 554 } 555 556 @Test 557 fun testStylusPresenceExtendsValidUsiBatteryState() { 558 `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") 559 `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING) 560 `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78) 561 562 addInputDevice(USI_DEVICE_ID, supportsUsi = true) 563 testLooper.dispatchNext() 564 val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) 565 verify(uEventManager) 566 .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device")) 567 568 // There is a UEvent signaling a battery change. The battery state is now valid. 569 uEventListener.value!!.onBatteryUEvent(TIMESTAMP) 570 val listener = createMockListener() 571 batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) 572 listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f) 573 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 574 matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)) 575 576 // Stylus presence is detected before the validity timeout expires. 577 testLooper.moveTimeForward(100) 578 testLooper.dispatchAll() 579 batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP) 580 581 // Ensure that timeout was extended, and the battery state is now valid for longer. 582 testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 100) 583 testLooper.dispatchAll() 584 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 585 matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)) 586 587 // Ensure the validity period expires after the expected amount of time. 588 testLooper.moveTimeForward(100) 589 testLooper.dispatchNext() 590 listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID)) 591 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 592 isInvalidBatteryState(USI_DEVICE_ID)) 593 } 594 595 @Test 596 fun testStylusPresenceMakesUsiBatteryStateValid() { 597 `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") 598 `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING) 599 `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78) 600 601 addInputDevice(USI_DEVICE_ID, supportsUsi = true) 602 testLooper.dispatchNext() 603 val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) 604 verify(uEventManager) 605 .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device")) 606 607 // The USI battery state is initially invalid. 608 val listener = createMockListener() 609 batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) 610 listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID)) 611 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 612 isInvalidBatteryState(USI_DEVICE_ID)) 613 614 // A stylus presence is detected. This validates the battery state. 615 batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP) 616 617 listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f) 618 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 619 matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)) 620 } 621 622 @Test 623 fun testStylusPresenceDoesNotMakeUsiBatteryStateValidAtBoot() { 624 `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") 625 // At boot, the USI device always reports a capacity value of 0. 626 `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_UNKNOWN) 627 `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(0) 628 629 addInputDevice(USI_DEVICE_ID, supportsUsi = true) 630 testLooper.dispatchNext() 631 val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) 632 verify(uEventManager) 633 .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device")) 634 635 // The USI battery state is initially invalid. 636 val listener = createMockListener() 637 batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) 638 listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID)) 639 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 640 isInvalidBatteryState(USI_DEVICE_ID)) 641 642 // Since the capacity reported is 0, stylus presence does not validate the battery state. 643 batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP) 644 645 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 646 isInvalidBatteryState(USI_DEVICE_ID)) 647 648 // However, if a UEvent reports a battery capacity of 0, the battery state is now valid. 649 uEventListener.value!!.onBatteryUEvent(TIMESTAMP) 650 listener.verifyNotified(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f) 651 assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID), 652 matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f)) 653 } 654 655 @Test 656 fun testRegisterBluetoothListenerForMonitoredBluetoothDevices() { 657 `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) 658 .thenReturn("AA:BB:CC:DD:EE:FF") 659 `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) 660 .thenReturn("11:22:33:44:55:66") 661 addInputDevice(BT_DEVICE_ID) 662 testLooper.dispatchNext() 663 addInputDevice(SECOND_BT_DEVICE_ID) 664 testLooper.dispatchNext() 665 666 // Listen to a non-Bluetooth device and ensure that the BT battery listener is not added 667 // when there are no monitored BT devices. 668 val listener = createMockListener() 669 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 670 verify(bluetoothBatteryManager, never()).addBatteryListener(any()) 671 672 val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java) 673 674 // The BT battery listener is added when the first BT input device is monitored. 675 batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID) 676 verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture()) 677 678 // The BT listener is only added once for all BT devices. 679 batteryController.registerBatteryListener(SECOND_BT_DEVICE_ID, listener, PID) 680 verify(bluetoothBatteryManager, times(1)).addBatteryListener(any()) 681 682 // The BT listener is only removed when there are no monitored BT devices. 683 batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID) 684 verify(bluetoothBatteryManager, never()).removeBatteryListener(any()) 685 686 `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) 687 .thenReturn(null) 688 notifyDeviceChanged(SECOND_BT_DEVICE_ID) 689 testLooper.dispatchNext() 690 verify(bluetoothBatteryManager).removeBatteryListener(bluetoothListener.value) 691 } 692 693 @Test 694 fun testNotifiesBluetoothBatteryChanges() { 695 `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) 696 .thenReturn("AA:BB:CC:DD:EE:FF") 697 `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) 698 addInputDevice(BT_DEVICE_ID) 699 val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java) 700 val listener = createMockListener() 701 batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID) 702 verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture()) 703 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f) 704 705 // When the state has not changed, the listener is not notified again. 706 bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 21) 707 listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f) 708 709 bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 25) 710 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f) 711 } 712 713 @Test 714 fun testBluetoothBatteryIsPrioritized() { 715 `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device") 716 `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) 717 .thenReturn("AA:BB:CC:DD:EE:FF") 718 `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) 719 `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98) 720 addInputDevice(BT_DEVICE_ID) 721 val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java) 722 val listener = createMockListener() 723 val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) 724 725 // When the device is first monitored and both native and BT battery is available, 726 // the latter is used. 727 batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID) 728 verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture()) 729 verify(uEventManager).addListener(uEventListener.capture(), any()) 730 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f) 731 assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID), 732 matchesState(BT_DEVICE_ID, capacity = 0.21f)) 733 734 // If only the native battery state changes the listener is not notified. 735 `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(97) 736 uEventListener.value!!.onBatteryUEvent(TIMESTAMP) 737 listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f) 738 assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID), 739 matchesState(BT_DEVICE_ID, capacity = 0.21f)) 740 } 741 742 @Test 743 fun testFallBackToNativeBatteryStateWhenBluetoothStateInvalid() { 744 `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device") 745 `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) 746 .thenReturn("AA:BB:CC:DD:EE:FF") 747 `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) 748 `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98) 749 addInputDevice(BT_DEVICE_ID) 750 val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java) 751 val listener = createMockListener() 752 val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) 753 754 batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID) 755 verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture()) 756 verify(uEventManager).addListener(uEventListener.capture(), any()) 757 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f) 758 759 // Fall back to the native state when BT is off. 760 bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 761 BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF) 762 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.98f) 763 764 bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 22) 765 verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture()) 766 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.22f) 767 768 // Fall back to the native state when BT battery is unknown. 769 bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 770 BluetoothDevice.BATTERY_LEVEL_UNKNOWN) 771 listener.verifyNotified(BT_DEVICE_ID, mode = times(2), capacity = 0.98f) 772 } 773 774 @Test 775 fun testRegisterBluetoothMetadataListenerForMonitoredBluetoothDevices() { 776 `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) 777 .thenReturn("AA:BB:CC:DD:EE:FF") 778 `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) 779 .thenReturn("11:22:33:44:55:66") 780 addInputDevice(BT_DEVICE_ID) 781 testLooper.dispatchNext() 782 addInputDevice(SECOND_BT_DEVICE_ID) 783 testLooper.dispatchNext() 784 785 // Listen to a non-Bluetooth device and ensure that the metadata listener is not added when 786 // there are no monitored BT devices. 787 val listener = createMockListener() 788 batteryController.registerBatteryListener(DEVICE_ID, listener, PID) 789 verify(bluetoothBatteryManager, never()).addMetadataListener(any(), any()) 790 791 val metadataListener1 = ArgumentCaptor.forClass( 792 BluetoothAdapter.OnMetadataChangedListener::class.java) 793 val metadataListener2 = ArgumentCaptor.forClass( 794 BluetoothAdapter.OnMetadataChangedListener::class.java) 795 796 // The metadata listener is added when the first BT input device is monitored. 797 batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID) 798 verify(bluetoothBatteryManager) 799 .addMetadataListener(eq("AA:BB:CC:DD:EE:FF"), metadataListener1.capture()) 800 801 // There is one metadata listener added for each BT device. 802 batteryController.registerBatteryListener(SECOND_BT_DEVICE_ID, listener, PID) 803 verify(bluetoothBatteryManager) 804 .addMetadataListener(eq("11:22:33:44:55:66"), metadataListener2.capture()) 805 806 // The metadata listener is removed when the device is no longer monitored. 807 batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID) 808 verify(bluetoothBatteryManager) 809 .removeMetadataListener("AA:BB:CC:DD:EE:FF", metadataListener1.value) 810 811 `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) 812 .thenReturn(null) 813 notifyDeviceChanged(SECOND_BT_DEVICE_ID) 814 testLooper.dispatchNext() 815 verify(bluetoothBatteryManager) 816 .removeMetadataListener("11:22:33:44:55:66", metadataListener2.value) 817 } 818 819 @Test 820 fun testNotifiesBluetoothMetadataBatteryChanges() { 821 `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) 822 .thenReturn("AA:BB:CC:DD:EE:FF") 823 `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF", 824 BluetoothDevice.METADATA_MAIN_BATTERY)) 825 .thenReturn("21".toByteArray()) 826 addInputDevice(BT_DEVICE_ID) 827 val metadataListener = ArgumentCaptor.forClass( 828 BluetoothAdapter.OnMetadataChangedListener::class.java) 829 val listener = createMockListener() 830 val bluetoothDevice = createBluetoothDevice("AA:BB:CC:DD:EE:FF") 831 batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID) 832 verify(bluetoothBatteryManager) 833 .addMetadataListener(eq("AA:BB:CC:DD:EE:FF"), metadataListener.capture()) 834 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f, status = STATUS_UNKNOWN) 835 836 // When the state has not changed, the listener is not notified again. 837 metadataListener.value!!.onMetadataChanged( 838 bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, "21".toByteArray()) 839 listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f) 840 841 metadataListener.value!!.onMetadataChanged( 842 bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, "25".toByteArray()) 843 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f, status = STATUS_UNKNOWN) 844 845 metadataListener.value!!.onMetadataChanged( 846 bluetoothDevice, BluetoothDevice.METADATA_MAIN_CHARGING, "true".toByteArray()) 847 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f, status = STATUS_CHARGING) 848 849 metadataListener.value!!.onMetadataChanged( 850 bluetoothDevice, BluetoothDevice.METADATA_MAIN_CHARGING, "false".toByteArray()) 851 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f, status = STATUS_DISCHARGING) 852 853 metadataListener.value!!.onMetadataChanged( 854 bluetoothDevice, BluetoothDevice.METADATA_MAIN_CHARGING, null) 855 listener.verifyNotified(BT_DEVICE_ID, mode = times(2), capacity = 0.25f, 856 status = STATUS_UNKNOWN) 857 } 858 859 @Test 860 fun testBluetoothMetadataBatteryIsPrioritized() { 861 `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) 862 .thenReturn("AA:BB:CC:DD:EE:FF") 863 `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) 864 `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF", 865 BluetoothDevice.METADATA_MAIN_BATTERY)) 866 .thenReturn("22".toByteArray()) 867 addInputDevice(BT_DEVICE_ID) 868 val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java) 869 val metadataListener = ArgumentCaptor.forClass( 870 BluetoothAdapter.OnMetadataChangedListener::class.java) 871 val listener = createMockListener() 872 val bluetoothDevice = createBluetoothDevice("AA:BB:CC:DD:EE:FF") 873 batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID) 874 875 verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture()) 876 verify(bluetoothBatteryManager) 877 .addMetadataListener(eq("AA:BB:CC:DD:EE:FF"), metadataListener.capture()) 878 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.22f) 879 880 // A change in the Bluetooth battery level has no effect while there is a valid battery 881 // level obtained through the metadata. 882 bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 23) 883 listener.verifyNotified(BT_DEVICE_ID, mode = never(), capacity = 0.23f) 884 885 metadataListener.value!!.onMetadataChanged( 886 bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, "24".toByteArray()) 887 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.24f) 888 889 // When the battery level from the metadata is no longer valid, we fall back to using the 890 // Bluetooth battery level. 891 metadataListener.value!!.onMetadataChanged( 892 bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, null) 893 listener.verifyNotified(BT_DEVICE_ID, capacity = 0.23f) 894 } 895 } 896