1 /*
<lambda>null2  * 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 package com.android.systemui.stylus
17 
18 import android.bluetooth.BluetoothAdapter
19 import android.bluetooth.BluetoothDevice
20 import android.hardware.BatteryState
21 import android.hardware.input.InputManager
22 import android.hardware.input.InputSettings
23 import android.os.Handler
24 import android.testing.AndroidTestingRunner
25 import android.view.InputDevice
26 import androidx.test.filters.SmallTest
27 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
28 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
29 import com.android.dx.mockito.inline.extended.ExtendedMockito.times
30 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
31 import com.android.dx.mockito.inline.extended.StaticMockitoSession
32 import com.android.internal.logging.InstanceId
33 import com.android.internal.logging.UiEventLogger
34 import com.android.systemui.InstanceIdSequenceFake
35 import com.android.systemui.SysuiTestCase
36 import com.android.systemui.flags.FeatureFlags
37 import com.android.systemui.flags.Flags
38 import com.android.systemui.util.mockito.any
39 import com.android.systemui.util.mockito.whenever
40 import java.util.concurrent.Executor
41 import org.junit.After
42 import org.junit.Before
43 import org.junit.Test
44 import org.junit.runner.RunWith
45 import org.mockito.Mock
46 import org.mockito.Mockito.clearInvocations
47 import org.mockito.Mockito.inOrder
48 import org.mockito.Mockito.verifyNoMoreInteractions
49 import org.mockito.Mockito.verifyZeroInteractions
50 import org.mockito.MockitoAnnotations
51 import org.mockito.quality.Strictness
52 
53 @RunWith(AndroidTestingRunner::class)
54 @SmallTest
55 class StylusManagerTest : SysuiTestCase() {
56     @Mock lateinit var inputManager: InputManager
57     @Mock lateinit var stylusDevice: InputDevice
58     @Mock lateinit var btStylusDevice: InputDevice
59     @Mock lateinit var otherDevice: InputDevice
60     @Mock lateinit var batteryState: BatteryState
61     @Mock lateinit var bluetoothAdapter: BluetoothAdapter
62     @Mock lateinit var bluetoothDevice: BluetoothDevice
63     @Mock lateinit var handler: Handler
64     @Mock lateinit var featureFlags: FeatureFlags
65     @Mock lateinit var uiEventLogger: UiEventLogger
66     @Mock lateinit var stylusCallback: StylusManager.StylusCallback
67     @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback
68 
69     private lateinit var mockitoSession: StaticMockitoSession
70     private lateinit var stylusManager: StylusManager
71     private val instanceIdSequenceFake = InstanceIdSequenceFake(10)
72 
73     @Before
74     fun setUp() {
75         MockitoAnnotations.initMocks(this)
76         mockitoSession =
77             mockitoSession()
78                 .mockStatic(InputSettings::class.java)
79                 .strictness(Strictness.LENIENT)
80                 .startMocking()
81 
82         whenever(handler.post(any())).thenAnswer {
83             (it.arguments[0] as Runnable).run()
84             true
85         }
86 
87         stylusManager =
88             StylusManager(
89                 mContext,
90                 inputManager,
91                 bluetoothAdapter,
92                 handler,
93                 EXECUTOR,
94                 featureFlags,
95                 uiEventLogger
96             )
97 
98         stylusManager.instanceIdSequence = instanceIdSequenceFake
99 
100         whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
101         whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
102         whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
103 
104         whenever(btStylusDevice.isExternal).thenReturn(true)
105 
106         whenever(stylusDevice.bluetoothAddress).thenReturn(null)
107         whenever(btStylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
108 
109         whenever(btStylusDevice.batteryState).thenReturn(batteryState)
110         whenever(stylusDevice.batteryState).thenReturn(batteryState)
111         whenever(batteryState.capacity).thenReturn(0.5f)
112 
113         whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice)
114         whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice)
115         whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice)
116         whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID))
117 
118         whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(bluetoothDevice)
119         whenever(bluetoothDevice.address).thenReturn(STYLUS_BT_ADDRESS)
120 
121         whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(true)
122 
123         whenever(InputSettings.isStylusEverUsed(mContext)).thenReturn(false)
124 
125         stylusManager.startListener()
126         stylusManager.registerCallback(stylusCallback)
127         clearInvocations(inputManager)
128     }
129 
130     @After
131     fun tearDown() {
132         mockitoSession.finishMocking()
133     }
134 
135     @Test
136     fun startListener_hasNotStarted_registersInputDeviceListener() {
137         stylusManager =
138             StylusManager(
139                 mContext,
140                 inputManager,
141                 bluetoothAdapter,
142                 handler,
143                 EXECUTOR,
144                 featureFlags,
145                 uiEventLogger
146             )
147 
148         stylusManager.startListener()
149 
150         verify(inputManager, times(1)).registerInputDeviceListener(any(), any())
151     }
152 
153     @Test
154     fun startListener_hasNotStarted_registersExistingBluetoothDevice() {
155         whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(BT_STYLUS_DEVICE_ID))
156 
157         stylusManager =
158             StylusManager(
159                 mContext,
160                 inputManager,
161                 bluetoothAdapter,
162                 handler,
163                 EXECUTOR,
164                 featureFlags,
165                 uiEventLogger
166             )
167 
168         stylusManager.startListener()
169 
170         verify(bluetoothAdapter, times(1))
171             .addOnMetadataChangedListener(bluetoothDevice, EXECUTOR, stylusManager)
172     }
173 
174     @Test
175     fun startListener_hasStarted_doesNothing() {
176         stylusManager.startListener()
177 
178         verifyZeroInteractions(inputManager)
179     }
180 
181     @Test
182     fun onInputDeviceAdded_hasNotStarted_doesNothing() {
183         stylusManager =
184             StylusManager(
185                 mContext,
186                 inputManager,
187                 bluetoothAdapter,
188                 handler,
189                 EXECUTOR,
190                 featureFlags,
191                 uiEventLogger
192             )
193 
194         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
195 
196         verifyZeroInteractions(stylusCallback)
197     }
198 
199     @Test
200     fun onInputDeviceAdded_multipleRegisteredCallbacks_callsAll() {
201         stylusManager.registerCallback(otherStylusCallback)
202 
203         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
204 
205         verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
206         verifyNoMoreInteractions(stylusCallback)
207         verify(otherStylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
208         verifyNoMoreInteractions(otherStylusCallback)
209     }
210 
211     @Test
212     fun onInputDeviceAdded_internalStylus_registersBatteryListener() {
213         whenever(stylusDevice.isExternal).thenReturn(false)
214 
215         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
216 
217         verify(inputManager, times(1))
218             .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, EXECUTOR, stylusManager)
219     }
220 
221     @Test
222     fun onInputDeviceAdded_externalStylus_doesNotRegisterbatteryListener() {
223         whenever(stylusDevice.isExternal).thenReturn(true)
224 
225         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
226 
227         verify(inputManager, never())
228             .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, EXECUTOR, stylusManager)
229     }
230 
231     @Test
232     fun onInputDeviceAdded_stylus_callsCallbacksOnStylusAdded() {
233         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
234 
235         verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
236         verifyNoMoreInteractions(stylusCallback)
237     }
238 
239     @Test
240     fun onInputDeviceAdded_btStylus_firstUsed_callsCallbacksOnStylusFirstUsed() {
241         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
242 
243         verify(stylusCallback, times(1)).onStylusFirstUsed()
244     }
245 
246     @Test
247     fun onInputDeviceAdded_btStylus_firstUsed_setsFlag() {
248         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
249         verify({ InputSettings.setStylusEverUsed(mContext, true) }, times(1))
250     }
251 
252     @Test
253     fun onInputDeviceAdded_btStylus_callsCallbacksWithAddress() {
254         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
255 
256         inOrder(stylusCallback).let {
257             it.verify(stylusCallback, times(1)).onStylusAdded(BT_STYLUS_DEVICE_ID)
258             it.verify(stylusCallback, times(1))
259                 .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
260         }
261     }
262 
263     @Test
264     fun onInputDeviceAdded_notStylus_doesNotCallCallbacks() {
265         stylusManager.onInputDeviceAdded(OTHER_DEVICE_ID)
266 
267         verifyNoMoreInteractions(stylusCallback)
268     }
269 
270     @Test
271     fun onInputDeviceChanged_hasNotStarted_doesNothing() {
272         stylusManager =
273             StylusManager(
274                 mContext,
275                 inputManager,
276                 bluetoothAdapter,
277                 handler,
278                 EXECUTOR,
279                 featureFlags,
280                 uiEventLogger
281             )
282 
283         stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
284 
285         verifyZeroInteractions(stylusCallback)
286     }
287 
288     @Test
289     fun onInputDeviceChanged_multipleRegisteredCallbacks_callsAll() {
290         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
291         whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
292         stylusManager.registerCallback(otherStylusCallback)
293 
294         stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
295 
296         verify(stylusCallback, times(1))
297             .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
298         verify(otherStylusCallback, times(1))
299             .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
300     }
301 
302     @Test
303     fun onInputDeviceChanged_stylusNewBtConnection_callsCallbacks() {
304         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
305         whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
306 
307         stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
308 
309         verify(stylusCallback, times(1))
310             .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
311     }
312 
313     @Test
314     fun onInputDeviceChanged_stylusLostBtConnection_callsCallbacks() {
315         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
316         whenever(btStylusDevice.bluetoothAddress).thenReturn(null)
317 
318         stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID)
319 
320         verify(stylusCallback, times(1))
321             .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
322     }
323 
324     @Test
325     fun onInputDeviceChanged_btConnection_stylusAlreadyBtConnected_onlyCallsListenersOnce() {
326         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
327 
328         stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID)
329 
330         verify(stylusCallback, times(1))
331             .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
332     }
333 
334     @Test
335     fun onInputDeviceChanged_noBtConnection_stylusNeverBtConnected_doesNotCallCallbacks() {
336         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
337 
338         stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
339 
340         verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any())
341     }
342 
343     @Test
344     fun onInputDeviceRemoved_hasNotStarted_doesNothing() {
345         stylusManager =
346             StylusManager(
347                 mContext,
348                 inputManager,
349                 bluetoothAdapter,
350                 handler,
351                 EXECUTOR,
352                 featureFlags,
353                 uiEventLogger
354             )
355         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
356 
357         stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
358 
359         verifyZeroInteractions(stylusCallback)
360     }
361 
362     @Test
363     fun onInputDeviceRemoved_multipleRegisteredCallbacks_callsAll() {
364         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
365         stylusManager.registerCallback(otherStylusCallback)
366 
367         stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
368 
369         verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
370         verify(otherStylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
371     }
372 
373     @Test
374     fun onInputDeviceRemoved_stylus_callsCallbacks() {
375         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
376 
377         stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
378 
379         verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
380         verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any())
381     }
382 
383     @Test
384     fun onInputDeviceRemoved_unregistersBatteryListener() {
385         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
386 
387         stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
388 
389         verify(inputManager, times(1))
390             .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, stylusManager)
391     }
392 
393     @Test
394     fun onInputDeviceRemoved_btStylus_callsCallbacks() {
395         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
396 
397         stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)
398 
399         inOrder(stylusCallback).let {
400             it.verify(stylusCallback, times(1))
401                 .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
402             it.verify(stylusCallback, times(1)).onStylusRemoved(BT_STYLUS_DEVICE_ID)
403         }
404     }
405 
406     @Test
407     fun onStylusBluetoothConnected_registersMetadataListener() {
408         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
409 
410         verify(bluetoothAdapter, times(1)).addOnMetadataChangedListener(any(), any(), any())
411     }
412 
413     @Test
414     fun onStylusBluetoothConnected_noBluetoothDevice_doesNotRegisterMetadataListener() {
415         whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(null)
416 
417         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
418 
419         verify(bluetoothAdapter, never()).addOnMetadataChangedListener(any(), any(), any())
420     }
421 
422     @Test
423     fun onStylusBluetoothConnected_logsEvent() {
424         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
425 
426         verify(uiEventLogger, times(1))
427             .logWithInstanceId(
428                 StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED,
429                 0,
430                 null,
431                 InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)
432             )
433     }
434 
435     @Test
436     fun onStylusBluetoothDisconnected_unregistersMetadataListener() {
437         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
438 
439         stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)
440 
441         verify(bluetoothAdapter, times(1)).removeOnMetadataChangedListener(any(), any())
442     }
443 
444     @Test
445     fun onStylusBluetoothDisconnected_logsEventInSameSession() {
446         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
447         val instanceId = InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)
448 
449         stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)
450 
451         verify(uiEventLogger, times(1))
452             .logWithInstanceId(StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED, 0, null, instanceId)
453         verify(uiEventLogger, times(1))
454             .logWithInstanceId(StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED, 0, null, instanceId)
455     }
456 
457     @Test
458     fun onMetadataChanged_chargingStateTrue_executesBatteryCallbacks() {
459         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
460 
461         stylusManager.onMetadataChanged(
462             bluetoothDevice,
463             BluetoothDevice.METADATA_MAIN_CHARGING,
464             "true".toByteArray()
465         )
466 
467         verify(stylusCallback, times(1))
468             .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
469     }
470 
471     @Test
472     fun onMetadataChanged_chargingStateFalse_executesBatteryCallbacks() {
473         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
474 
475         stylusManager.onMetadataChanged(
476             bluetoothDevice,
477             BluetoothDevice.METADATA_MAIN_CHARGING,
478             "false".toByteArray()
479         )
480 
481         verify(stylusCallback, times(1))
482             .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, false)
483     }
484 
485     @Test
486     fun onMetadataChanged_chargingStateNoDevice_doesNotExecuteBatteryCallbacks() {
487         stylusManager.onMetadataChanged(
488             bluetoothDevice,
489             BluetoothDevice.METADATA_MAIN_CHARGING,
490             "true".toByteArray()
491         )
492 
493         verifyNoMoreInteractions(stylusCallback)
494     }
495 
496     @Test
497     fun onMetadataChanged_notChargingState_doesNotExecuteBatteryCallbacks() {
498         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
499 
500         stylusManager.onMetadataChanged(
501             bluetoothDevice,
502             BluetoothDevice.METADATA_DEVICE_TYPE,
503             "true".toByteArray()
504         )
505 
506         verify(stylusCallback, never()).onStylusBluetoothChargingStateChanged(any(), any(), any())
507     }
508 
509     @Test
510     fun onBatteryStateChanged_batteryPresent_stylusNeverUsed_updateEverUsedFlag() {
511         whenever(batteryState.isPresent).thenReturn(true)
512 
513         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
514 
515         verify({ InputSettings.setStylusEverUsed(mContext, true) }, times(1))
516     }
517 
518     @Test
519     fun onBatteryStateChanged_batteryPresent_stylusNeverUsed_executesStylusFirstUsed() {
520         whenever(batteryState.isPresent).thenReturn(true)
521 
522         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
523 
524         verify(stylusCallback, times(1)).onStylusFirstUsed()
525     }
526 
527     @Test
528     fun onBatteryStateChanged_batteryPresent_notInUsiSession_logsSessionStart() {
529         whenever(batteryState.isPresent).thenReturn(true)
530 
531         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
532 
533         verify(uiEventLogger, times(1))
534             .logWithInstanceIdAndPosition(
535                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
536                 0,
537                 null,
538                 InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
539                 0,
540             )
541     }
542 
543     @Test
544     fun onBatteryStateChanged_batteryPresent_btStylusPresent_logsSessionStart() {
545         whenever(batteryState.isPresent).thenReturn(true)
546         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
547 
548         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
549 
550         verify(uiEventLogger, times(1))
551             .logWithInstanceIdAndPosition(
552                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
553                 0,
554                 null,
555                 InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
556                 1,
557             )
558     }
559 
560     @Test
561     fun onBatteryStateChanged_batteryPresent_inUsiSession_doesNotLogSessionStart() {
562         whenever(batteryState.isPresent).thenReturn(true)
563         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
564         clearInvocations(uiEventLogger)
565 
566         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
567 
568         verifyZeroInteractions(uiEventLogger)
569     }
570 
571     @Test
572     fun onBatteryStateChanged_batteryAbsent_notInUsiSession_doesNotLogSessionEnd() {
573         whenever(batteryState.isPresent).thenReturn(false)
574 
575         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
576 
577         verifyZeroInteractions(uiEventLogger)
578     }
579 
580     @Test
581     fun onBatteryStateChanged_batteryAbsent_inUsiSession_logSessionEnd() {
582         whenever(batteryState.isPresent).thenReturn(true)
583         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
584         val instanceId = InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)
585         whenever(batteryState.isPresent).thenReturn(false)
586 
587         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
588 
589         verify(uiEventLogger, times(1))
590             .logWithInstanceIdAndPosition(
591                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
592                 0,
593                 null,
594                 instanceId,
595                 0
596             )
597 
598         verify(uiEventLogger, times(1))
599             .logWithInstanceIdAndPosition(
600                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
601                 0,
602                 null,
603                 instanceId,
604                 0
605             )
606     }
607 
608     @Test
609     fun onBatteryStateChanged_batteryPresent_stylusUsed_doesNotUpdateEverUsedFlag() {
610         whenever(InputSettings.isStylusEverUsed(mContext)).thenReturn(true)
611 
612         whenever(batteryState.isPresent).thenReturn(true)
613 
614         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
615 
616         verify({ InputSettings.setStylusEverUsed(mContext, true) }, never())
617     }
618 
619     @Test
620     fun onBatteryStateChanged_batteryNotPresent_doesNotUpdateEverUsedFlag() {
621         whenever(batteryState.isPresent).thenReturn(false)
622 
623         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
624 
625         verify(inputManager, never())
626             .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, stylusManager)
627     }
628 
629     @Test
630     fun onBatteryStateChanged_hasNotStarted_doesNothing() {
631         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
632 
633         verifyZeroInteractions(inputManager)
634     }
635 
636     @Test
637     fun onBatteryStateChanged_executesBatteryCallbacks() {
638         stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
639 
640         verify(stylusCallback, times(1))
641             .onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
642     }
643 
644     companion object {
645         private val EXECUTOR = Executor { r -> r.run() }
646 
647         private const val OTHER_DEVICE_ID = 0
648         private const val STYLUS_DEVICE_ID = 1
649         private const val BT_STYLUS_DEVICE_ID = 2
650 
651         private const val STYLUS_BT_ADDRESS = "SOME:ADDRESS"
652     }
653 }
654