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