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