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.animation.ValueAnimator
20 import android.content.Context
21 import android.content.ContextWrapper
22 import android.graphics.Color
23 import android.hardware.input.IInputManager
24 import android.hardware.input.IKeyboardBacklightListener
25 import android.hardware.input.IKeyboardBacklightState
26 import android.hardware.input.InputManager
27 import android.hardware.input.InputManagerGlobal
28 import android.hardware.lights.Light
29 import android.os.UEventObserver
30 import android.os.test.TestLooper
31 import android.platform.test.annotations.Presubmit
32 import android.view.InputDevice
33 import androidx.test.annotation.UiThreadTest
34 import androidx.test.core.app.ApplicationProvider
35 import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL
36 import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS
37 import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
38 import org.junit.After
39 import org.junit.Assert.assertEquals
40 import org.junit.Assert.assertFalse
41 import org.junit.Assert.assertNotEquals
42 import org.junit.Assert.assertNotNull
43 import org.junit.Assert.assertNull
44 import org.junit.Assert.assertTrue
45 import org.junit.Before
46 import org.junit.Rule
47 import org.junit.Test
48 import org.mockito.Mock
49 import org.mockito.Mockito.any
50 import org.mockito.Mockito.anyInt
51 import org.mockito.Mockito.eq
52 import org.mockito.Mockito.spy
53 import org.mockito.Mockito.`when`
54 import org.mockito.junit.MockitoJUnit
55 import java.io.FileNotFoundException
56 import java.io.FileOutputStream
57 import java.io.IOException
58 import java.io.InputStream
59 
60 private fun createKeyboard(deviceId: Int): InputDevice =
61     InputDevice.Builder()
62         .setId(deviceId)
63         .setName("Device $deviceId")
64         .setDescriptor("descriptor $deviceId")
65         .setSources(InputDevice.SOURCE_KEYBOARD)
66         .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
67         .setExternal(true)
68         .build()
69 
70 private fun createLight(lightId: Int, lightType: Int): Light =
71     createLight(
72         lightId,
73         lightType,
74         null
75     )
76 
77 private fun createLight(lightId: Int, lightType: Int, suggestedBrightnessLevels: IntArray?): Light =
78     Light(
79         lightId,
80         "Light $lightId",
81         1,
82         lightType,
83         Light.LIGHT_CAPABILITY_BRIGHTNESS,
84         suggestedBrightnessLevels
85     )
86 /**
87  * Tests for {@link KeyboardBacklightController}.
88  *
89  * Build/Install/Run:
90  * atest FrameworksServicesTests:KeyboardBacklightControllerTests
91  */
92 @Presubmit
93 class KeyboardBacklightControllerTests {
94     companion object {
95         const val DEVICE_ID = 1
96         const val LIGHT_ID = 2
97         const val SECOND_LIGHT_ID = 3
98         const val MAX_BRIGHTNESS = 255
99     }
100 
101     @get:Rule
102     val rule = MockitoJUnit.rule()!!
103 
104     @Mock
105     private lateinit var iInputManager: IInputManager
106     @Mock
107     private lateinit var native: NativeInputManagerService
108     @Mock
109     private lateinit var uEventManager: UEventManager
110     private lateinit var keyboardBacklightController: KeyboardBacklightController
111     private lateinit var context: Context
112     private lateinit var dataStore: PersistentDataStore
113     private lateinit var testLooper: TestLooper
114     private var lightColorMap: HashMap<Int, Int> = HashMap()
115     private var lastBacklightState: KeyboardBacklightState? = null
116     private var sysfsNodeChanges = 0
117     private var lastAnimationValues = IntArray(2)
118 
119     @Before
120     fun setup() {
121         context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
122         dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
123             override fun openRead(): InputStream? {
124                 throw FileNotFoundException()
125             }
126 
127             override fun startWrite(): FileOutputStream? {
128                 throw IOException()
129             }
130 
131             override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
132         })
133         testLooper = TestLooper()
134         keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
135                 testLooper.looper, FakeAnimatorFactory(), uEventManager)
136         InputManagerGlobal.resetInstance(iInputManager)
137         val inputManager = InputManager(context)
138         `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
139         `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
140         `when`(native.setLightColor(anyInt(), anyInt(), anyInt())).then {
141             val args = it.arguments
142             lightColorMap.put(args[1] as Int, args[2] as Int)
143         }
144         `when`(native.getLightColor(anyInt(), anyInt())).thenAnswer {
145             val args = it.arguments
146             lightColorMap.getOrDefault(args[1] as Int, 0)
147         }
148         lightColorMap.clear()
149         `when`(native.sysfsNodeChanged(any())).then {
150             sysfsNodeChanges++
151         }
152     }
153 
154     @After
155     fun tearDown() {
156         InputManagerGlobal.clearInstance()
157     }
158 
159     @Test
160     fun testKeyboardBacklightIncrementDecrement() {
161         KeyboardBacklightFlags(
162             animationEnabled = false,
163             customLevelsEnabled = false,
164             ambientControlEnabled = false
165         ).use {
166             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
167             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
168             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
169             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
170             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
171 
172             assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
173                     DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
174         }
175     }
176 
177     @Test
178     fun testKeyboardWithoutBacklight() {
179         KeyboardBacklightFlags(
180             animationEnabled = false,
181             customLevelsEnabled = false,
182             ambientControlEnabled = false
183         ).use {
184             val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
185             val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
186             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
187             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
188             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
189 
190             incrementKeyboardBacklight(DEVICE_ID)
191             assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
192         }
193     }
194 
195     @Test
196     fun testKeyboardWithMultipleLight() {
197         KeyboardBacklightFlags(
198             animationEnabled = false,
199             customLevelsEnabled = false,
200             ambientControlEnabled = false
201         ).use {
202             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
203             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
204             val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
205             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
206             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(
207                 listOf(
208                     keyboardBacklight,
209                     keyboardInputLight
210                 )
211             )
212             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
213 
214             incrementKeyboardBacklight(DEVICE_ID)
215             assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
216             assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
217             assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
218         }
219     }
220 
221     @Test
222     fun testRestoreBacklightOnInputDeviceAdded() {
223         KeyboardBacklightFlags(
224             animationEnabled = false,
225             customLevelsEnabled = false,
226             ambientControlEnabled = false
227         ).use {
228             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
229             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
230             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
231             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
232 
233             for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) {
234                 dataStore.setKeyboardBacklightBrightness(
235                     keyboardWithBacklight.descriptor,
236                     LIGHT_ID,
237                     DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
238                 )
239 
240                 keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
241                 keyboardBacklightController.notifyUserActivity()
242                 testLooper.dispatchNext()
243                 assertEquals(
244                     "Keyboard backlight level should be restored to the level saved in the " +
245                             "data store",
246                     Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
247                     lightColorMap[LIGHT_ID]
248                 )
249                 keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
250             }
251         }
252     }
253 
254     @Test
255     fun testRestoreBacklightOnInputDeviceChanged() {
256         KeyboardBacklightFlags(
257             animationEnabled = false,
258             customLevelsEnabled = false,
259             ambientControlEnabled = false
260         ).use {
261             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
262             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
263             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
264             dataStore.setKeyboardBacklightBrightness(
265                 keyboardWithBacklight.descriptor,
266                 LIGHT_ID,
267                 MAX_BRIGHTNESS
268             )
269 
270             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
271             keyboardBacklightController.notifyUserActivity()
272             testLooper.dispatchNext()
273             assertTrue(
274                 "Keyboard backlight should not be changed until its added",
275                 lightColorMap.isEmpty()
276             )
277 
278             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
279             keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
280             keyboardBacklightController.notifyUserActivity()
281             testLooper.dispatchNext()
282             assertEquals(
283                 "Keyboard backlight level should be restored to the level saved in the data store",
284                 Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
285                 lightColorMap[LIGHT_ID]
286             )
287         }
288     }
289 
290     @Test
291     fun testKeyboardBacklight_registerUnregisterListener() {
292         KeyboardBacklightFlags(
293             animationEnabled = false,
294             customLevelsEnabled = false,
295             ambientControlEnabled = false
296         ).use {
297             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
298             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
299             val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1
300             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
301             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
302             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
303 
304             // Register backlight listener
305             val listener = KeyboardBacklightListener()
306             keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
307 
308             lastBacklightState = null
309             keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
310             testLooper.dispatchNext()
311 
312             assertEquals(
313                 "Backlight state device Id should be $DEVICE_ID",
314                 DEVICE_ID,
315                 lastBacklightState!!.deviceId
316             )
317             assertEquals(
318                 "Backlight state brightnessLevel should be 1",
319                 1,
320                 lastBacklightState!!.brightnessLevel
321             )
322             assertEquals(
323                 "Backlight state maxBrightnessLevel should be $maxLevel",
324                 maxLevel,
325                 lastBacklightState!!.maxBrightnessLevel
326             )
327             assertEquals(
328                 "Backlight state isTriggeredByKeyPress should be true",
329                 true,
330                 lastBacklightState!!.isTriggeredByKeyPress
331             )
332 
333             // Unregister listener
334             keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
335 
336             lastBacklightState = null
337             incrementKeyboardBacklight(DEVICE_ID)
338 
339             assertNull("Listener should not receive any updates", lastBacklightState)
340         }
341     }
342 
343     @Test
344     fun testKeyboardBacklight_userActivity() {
345         KeyboardBacklightFlags(
346             animationEnabled = false,
347             customLevelsEnabled = false,
348             ambientControlEnabled = false
349         ).use {
350             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
351             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
352             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
353             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
354             dataStore.setKeyboardBacklightBrightness(
355                 keyboardWithBacklight.descriptor,
356                 LIGHT_ID,
357                 MAX_BRIGHTNESS
358             )
359 
360             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
361             keyboardBacklightController.notifyUserActivity()
362             testLooper.dispatchNext()
363             assertEquals(
364                 "Keyboard backlight level should be restored to the level saved in the data store",
365                 Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
366                 lightColorMap[LIGHT_ID]
367             )
368 
369             testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
370             testLooper.dispatchNext()
371             assertEquals(
372                 "Keyboard backlight level should be turned off after inactivity",
373                 0,
374                 lightColorMap[LIGHT_ID]
375             )
376         }
377     }
378 
379     @Test
380     fun testKeyboardBacklight_displayOnOff() {
381         KeyboardBacklightFlags(
382             animationEnabled = false,
383             customLevelsEnabled = false,
384             ambientControlEnabled = false
385         ).use {
386             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
387             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
388             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
389             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
390             dataStore.setKeyboardBacklightBrightness(
391                 keyboardWithBacklight.descriptor,
392                 LIGHT_ID,
393                 MAX_BRIGHTNESS
394             )
395 
396             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
397             keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
398             assertEquals(
399                 "Keyboard backlight level should be restored to the level saved in the data " +
400                         "store when display turned on",
401                 Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
402                 lightColorMap[LIGHT_ID]
403             )
404 
405             keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
406             assertEquals(
407                 "Keyboard backlight level should be turned off after display is turned off",
408                 0,
409                 lightColorMap[LIGHT_ID]
410             )
411         }
412     }
413 
414     @Test
415     fun testKeyboardBacklightSysfsNodeAdded_AfterInputDeviceAdded() {
416         var counter = sysfsNodeChanges
417         keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
418             "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::no_backlight\u0000"
419         ))
420         assertEquals(
421             "Should not reload sysfs node if UEvent path doesn't contain kbd_backlight",
422             counter,
423             sysfsNodeChanges
424         )
425 
426         keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
427             "ACTION=add\u0000SUBSYSTEM=power\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000"
428         ))
429         assertEquals(
430             "Should not reload sysfs node if UEvent doesn't belong to subsystem LED",
431             counter,
432             sysfsNodeChanges
433         )
434 
435         keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
436             "ACTION=remove\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000"
437         ))
438         assertEquals(
439             "Should not reload sysfs node if UEvent doesn't have ACTION(add)",
440             counter,
441             sysfsNodeChanges
442         )
443 
444         keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
445             "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/pqr/abc::kbd_backlight\u0000"
446         ))
447         assertEquals(
448             "Should not reload sysfs node if UEvent path doesn't belong to leds/ directory",
449             counter,
450             sysfsNodeChanges
451         )
452 
453         keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
454             "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000"
455         ))
456         assertEquals(
457             "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs",
458             ++counter,
459             sysfsNodeChanges
460         )
461 
462         keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
463             "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc:kbd_backlight:red\u0000"
464         ))
465         assertEquals(
466             "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs",
467             ++counter,
468             sysfsNodeChanges
469         )
470     }
471 
472     @Test
473     @UiThreadTest
474     fun testKeyboardBacklightAnimation_onChangeLevels() {
475         KeyboardBacklightFlags(
476             animationEnabled = true,
477             customLevelsEnabled = false,
478             ambientControlEnabled = false
479         ).use {
480             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
481             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
482             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
483             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
484             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
485 
486             incrementKeyboardBacklight(DEVICE_ID)
487             assertEquals(
488                 "Should start animation from level 0",
489                 DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0],
490                 lastAnimationValues[0]
491             )
492             assertEquals(
493                 "Should start animation to level 1",
494                 DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1],
495                 lastAnimationValues[1]
496             )
497         }
498     }
499 
500     @Test
501     fun testKeyboardBacklightPreferredLevels() {
502         KeyboardBacklightFlags(
503             animationEnabled = false,
504             customLevelsEnabled = true,
505             ambientControlEnabled = false
506         ).use {
507             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
508             val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
509             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
510                     suggestedLevels)
511             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
512             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
513             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
514 
515             assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
516                     suggestedLevels)
517         }
518     }
519 
520     @Test
521     fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() {
522         KeyboardBacklightFlags(
523             animationEnabled = false,
524             customLevelsEnabled = true,
525             ambientControlEnabled = false
526         ).use {
527             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
528             val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
529             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
530                     suggestedLevels)
531             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
532             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
533             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
534 
535             assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
536                     DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
537         }
538     }
539 
540     @Test
541     fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() {
542         KeyboardBacklightFlags(
543             animationEnabled = false,
544             customLevelsEnabled = true,
545             ambientControlEnabled = false
546         ).use {
547             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
548             val suggestedLevels = intArrayOf(22, 63, 135, 196)
549             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
550                     suggestedLevels)
551             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
552             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
553             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
554 
555             // Framework will add the lowest and maximum levels if not provided via config
556             assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
557                     intArrayOf(0, 22, 63, 135, 196, 255))
558         }
559     }
560 
561     @Test
562     fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() {
563         KeyboardBacklightFlags(
564             animationEnabled = false,
565             customLevelsEnabled = true,
566             ambientControlEnabled = false
567         ).use {
568             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
569             val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
570             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
571                     suggestedLevels)
572             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
573             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
574             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
575 
576             // Framework will drop out of bound levels in the config
577             assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
578                     intArrayOf(0, 22, 63, 135, 196, 255))
579         }
580     }
581 
582     @Test
583     fun testAmbientBacklightControl_doesntRestoreBacklightLevel() {
584         KeyboardBacklightFlags(
585             animationEnabled = false,
586             customLevelsEnabled = false,
587             ambientControlEnabled = true
588         ).use {
589             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
590             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
591             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
592             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
593 
594             dataStore.setKeyboardBacklightBrightness(
595                 keyboardWithBacklight.descriptor,
596                 LIGHT_ID,
597                 DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1]
598             )
599 
600             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
601             keyboardBacklightController.notifyUserActivity()
602             testLooper.dispatchNext()
603             assertNull(
604                 "Keyboard backlight level should not be restored to the saved level",
605                 lightColorMap[LIGHT_ID]
606             )
607         }
608     }
609 
610     @Test
611     fun testAmbientBacklightControl_doesntBackupBacklightLevel() {
612         KeyboardBacklightFlags(
613             animationEnabled = false,
614             customLevelsEnabled = false,
615             ambientControlEnabled = true
616         ).use {
617             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
618             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
619             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
620             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
621 
622             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
623             incrementKeyboardBacklight(DEVICE_ID)
624             assertFalse(
625                 "Light value should not be backed up if ambient control is enabled",
626                 dataStore.getKeyboardBacklightBrightness(
627                     keyboardWithBacklight.descriptor, LIGHT_ID
628                 ).isPresent
629             )
630         }
631     }
632 
633     @Test
634     fun testAmbientBacklightControl_incrementLevel_afterAmbientChange() {
635         KeyboardBacklightFlags(
636             animationEnabled = false,
637             customLevelsEnabled = false,
638             ambientControlEnabled = true
639         ).use {
640             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
641             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
642             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
643             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
644             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
645             sendAmbientBacklightValue(1)
646             assertEquals(
647                 "Light value should be changed to ambient provided value",
648                 Color.argb(1, 0, 0, 0),
649                 lightColorMap[LIGHT_ID]
650             )
651 
652             incrementKeyboardBacklight(DEVICE_ID)
653 
654             assertEquals(
655                 "Light value for level after increment post Ambient change is mismatched",
656                 Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
657                 lightColorMap[LIGHT_ID]
658             )
659         }
660     }
661 
662     @Test
663     fun testAmbientBacklightControl_decrementLevel_afterAmbientChange() {
664         KeyboardBacklightFlags(
665             animationEnabled = false,
666             customLevelsEnabled = false,
667             ambientControlEnabled = true
668         ).use {
669             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
670             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
671             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
672             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
673             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
674             sendAmbientBacklightValue(254)
675             assertEquals(
676                 "Light value should be changed to ambient provided value",
677                 Color.argb(254, 0, 0, 0),
678                 lightColorMap[LIGHT_ID]
679             )
680 
681             decrementKeyboardBacklight(DEVICE_ID)
682 
683             val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
684             assertEquals(
685                 "Light value for level after decrement post Ambient change is mismatched",
686                 Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
687                 lightColorMap[LIGHT_ID]
688             )
689         }
690     }
691 
692     @Test
693     fun testAmbientBacklightControl_ambientChanges_afterManualChange() {
694         KeyboardBacklightFlags(
695             animationEnabled = false,
696             customLevelsEnabled = false,
697             ambientControlEnabled = true
698         ).use {
699             val keyboardWithBacklight = createKeyboard(DEVICE_ID)
700             val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
701             `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
702             `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
703             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
704             incrementKeyboardBacklight(DEVICE_ID)
705             assertEquals(
706                 "Light value should be changed to the first level",
707                 Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
708                 lightColorMap[LIGHT_ID]
709             )
710 
711             sendAmbientBacklightValue(100)
712             assertNotEquals(
713                 "Light value should not change based on ambient changes after manual changes",
714                 Color.argb(100, 0, 0, 0),
715                 lightColorMap[LIGHT_ID]
716             )
717         }
718     }
719 
720     private fun assertIncrementDecrementForLevels(
721         device: InputDevice,
722         light: Light,
723         expectedLevels: IntArray
724     ) {
725         val deviceId = device.id
726         val lightId = light.id
727         for (level in 1 until expectedLevels.size) {
728             incrementKeyboardBacklight(deviceId)
729             assertEquals(
730                 "Light value for level $level mismatched",
731                 Color.argb(expectedLevels[level], 0, 0, 0),
732                 lightColorMap[lightId]
733             )
734             assertEquals(
735                 "Light value for level $level must be correctly stored in the datastore",
736                 expectedLevels[level],
737                 dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
738             )
739         }
740 
741         // Increment above max level
742         incrementKeyboardBacklight(deviceId)
743         assertEquals(
744             "Light value for max level mismatched",
745             Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
746             lightColorMap[lightId]
747         )
748         assertEquals(
749             "Light value for max level must be correctly stored in the datastore",
750             MAX_BRIGHTNESS,
751             dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
752         )
753 
754         for (level in expectedLevels.size - 2 downTo 0) {
755             decrementKeyboardBacklight(deviceId)
756             assertEquals(
757                 "Light value for level $level mismatched",
758                 Color.argb(expectedLevels[level], 0, 0, 0),
759                 lightColorMap[lightId]
760             )
761             assertEquals(
762                 "Light value for level $level must be correctly stored in the datastore",
763                 expectedLevels[level],
764                 dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
765             )
766         }
767 
768         // Decrement below min level
769         decrementKeyboardBacklight(deviceId)
770         assertEquals(
771             "Light value for min level mismatched",
772             Color.argb(0, 0, 0, 0),
773             lightColorMap[lightId]
774         )
775         assertEquals(
776             "Light value for min level must be correctly stored in the datastore",
777             0,
778             dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
779         )
780     }
781 
782     inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
783         override fun onBrightnessChanged(
784             deviceId: Int,
785             state: IKeyboardBacklightState,
786             isTriggeredByKeyPress: Boolean
787         ) {
788             lastBacklightState = KeyboardBacklightState(
789                 deviceId,
790                 state.brightnessLevel,
791                 state.maxBrightnessLevel,
792                 isTriggeredByKeyPress
793             )
794         }
795     }
796 
797     private fun incrementKeyboardBacklight(deviceId: Int) {
798         keyboardBacklightController.incrementKeyboardBacklight(deviceId)
799         keyboardBacklightController.notifyUserActivity()
800         testLooper.dispatchAll()
801     }
802 
803     private fun decrementKeyboardBacklight(deviceId: Int) {
804         keyboardBacklightController.decrementKeyboardBacklight(deviceId)
805         keyboardBacklightController.notifyUserActivity()
806         testLooper.dispatchAll()
807     }
808 
809     private fun sendAmbientBacklightValue(brightnessValue: Int) {
810         keyboardBacklightController.handleAmbientLightValueChanged(brightnessValue)
811         keyboardBacklightController.notifyUserActivity()
812         testLooper.dispatchAll()
813     }
814 
815     class KeyboardBacklightState(
816         val deviceId: Int,
817         val brightnessLevel: Int,
818         val maxBrightnessLevel: Int,
819         val isTriggeredByKeyPress: Boolean
820     )
821 
822     private inner class KeyboardBacklightFlags constructor(
823         animationEnabled: Boolean,
824         customLevelsEnabled: Boolean,
825         ambientControlEnabled: Boolean
826     ) : AutoCloseable {
827         init {
828             InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled)
829             InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled)
830             InputFeatureFlagProvider
831                 .setAmbientKeyboardBacklightControlEnabled(ambientControlEnabled)
832         }
833 
834         override fun close() {
835             InputFeatureFlagProvider.clearOverrides()
836         }
837     }
838 
839     private inner class FakeAnimatorFactory : KeyboardBacklightController.AnimatorFactory {
840         override fun makeIntAnimator(from: Int, to: Int): ValueAnimator {
841             lastAnimationValues[0] = from
842             lastAnimationValues[1] = to
843             return ValueAnimator.ofInt(from, to)
844         }
845     }
846 }
847