/* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.input import android.content.Context import android.content.ContextWrapper import android.content.res.Resources import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager import android.hardware.display.DisplayManagerInternal import android.hardware.input.InputSensorInfo import android.os.Handler import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.util.TypedValue import android.view.Display import android.view.DisplayInfo import androidx.test.core.app.ApplicationProvider import com.android.internal.R import com.android.server.LocalServices import com.android.server.input.AmbientKeyboardBacklightController.HYSTERESIS_THRESHOLD import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt import org.mockito.Mockito.eq import org.mockito.Mockito.spy import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit /** * Tests for {@link AmbientKeyboardBacklightController}. * * Build/Install/Run: * atest InputTests:AmbientKeyboardBacklightControllerTests */ @Presubmit class AmbientKeyboardBacklightControllerTests { companion object { const val DEFAULT_DISPLAY_UNIQUE_ID = "uniqueId_1" const val SENSOR_NAME = "test_sensor_name" const val SENSOR_TYPE = "test_sensor_type" } @get:Rule val rule = MockitoJUnit.rule()!! private lateinit var context: Context private lateinit var testLooper: TestLooper private lateinit var ambientController: AmbientKeyboardBacklightController @Mock private lateinit var resources: Resources @Mock private lateinit var lightSensorInfo: InputSensorInfo @Mock private lateinit var sensorManager: SensorManager @Mock private lateinit var displayManagerInternal: DisplayManagerInternal private lateinit var lightSensor: Sensor private var currentDisplayInfo = DisplayInfo() private var lastBrightnessCallback: Int = 0 private var listenerRegistered: Boolean = false private var listenerRegistrationCount: Int = 0 @Before fun setup() { context = spy(ContextWrapper(ApplicationProvider.getApplicationContext())) `when`(context.resources).thenReturn(resources) setupBrightnessSteps() setupSensor() testLooper = TestLooper() ambientController = AmbientKeyboardBacklightController(context, testLooper.looper) ambientController.systemRunning() testLooper.dispatchAll() } private fun setupBrightnessSteps() { val brightnessValues = intArrayOf(100, 200, 0) val decreaseThresholds = intArrayOf(-1, 900, 1900) val increaseThresholds = intArrayOf(1000, 2000, -1) `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightBrightnessValues)) .thenReturn(brightnessValues) `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightDecreaseLuxThreshold)) .thenReturn(decreaseThresholds) `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightIncreaseLuxThreshold)) .thenReturn(increaseThresholds) `when`( resources.getValue( eq(R.dimen.config_autoKeyboardBrightnessSmoothingConstant), any(TypedValue::class.java), anyBoolean() ) ).then { val args = it.arguments val outValue = args[1] as TypedValue outValue.data = java.lang.Float.floatToRawIntBits(1.0f) Unit } } private fun setupSensor() { LocalServices.removeServiceForTest(DisplayManagerInternal::class.java) LocalServices.addService(DisplayManagerInternal::class.java, displayManagerInternal) currentDisplayInfo.uniqueId = DEFAULT_DISPLAY_UNIQUE_ID `when`(displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn( currentDisplayInfo ) val sensorData = DisplayManagerInternal.AmbientLightSensorData(SENSOR_NAME, SENSOR_TYPE) `when`(displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY)) .thenReturn(sensorData) `when`(lightSensorInfo.name).thenReturn(SENSOR_NAME) `when`(lightSensorInfo.stringType).thenReturn(SENSOR_TYPE) lightSensor = Sensor(lightSensorInfo) `when`(context.getSystemService(eq(Context.SENSOR_SERVICE))).thenReturn(sensorManager) `when`(sensorManager.getSensorList(anyInt())).thenReturn(listOf(lightSensor)) `when`( sensorManager.registerListener( any(), eq(lightSensor), anyInt(), any(Handler::class.java) ) ).then { listenerRegistered = true listenerRegistrationCount++ true } `when`( sensorManager.unregisterListener( any(SensorEventListener::class.java), eq(lightSensor) ) ).then { listenerRegistered = false Unit } } private fun setupSensorWithInitialLux(luxValue: Float) { ambientController.registerAmbientBacklightListener { brightnessValue: Int -> lastBrightnessCallback = brightnessValue } sendAmbientLuxValue(luxValue) testLooper.dispatchAll() } @Test fun testInitialAmbientLux_sendsCallbackImmediately() { setupSensorWithInitialLux(500F) assertEquals( "Should receive immediate callback for first lux change", 100, lastBrightnessCallback ) } @Test fun testBrightnessIncrease_afterInitialLuxChanges() { setupSensorWithInitialLux(500F) // Current state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1] repeat(HYSTERESIS_THRESHOLD) { sendAmbientLuxValue(1500F) } testLooper.dispatchAll() assertEquals( "Should receive brightness change callback for increasing lux change", 200, lastBrightnessCallback ) } @Test fun testBrightnessDecrease_afterInitialLuxChanges() { setupSensorWithInitialLux(1500F) // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900] repeat(HYSTERESIS_THRESHOLD) { sendAmbientLuxValue(500F) } testLooper.dispatchAll() assertEquals( "Should receive brightness change callback for decreasing lux change", 100, lastBrightnessCallback ) } @Test fun testRegisterAmbientListener_throwsExceptionOnRegisteringDuplicate() { val ambientListener = AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { } ambientController.registerAmbientBacklightListener(ambientListener) assertThrows(IllegalStateException::class.java) { ambientController.registerAmbientBacklightListener( ambientListener ) } } @Test fun testUnregisterAmbientListener_throwsExceptionOnUnregisteringNonExistent() { val ambientListener = AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { } assertThrows(IllegalStateException::class.java) { ambientController.unregisterAmbientBacklightListener( ambientListener ) } } @Test fun testSensorListenerRegistered_onRegisterUnregisterAmbientListener() { assertEquals( "Should not have a sensor listener registered at init", 0, listenerRegistrationCount ) assertFalse("Should not have a sensor listener registered at init", listenerRegistered) val ambientListener1 = AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { } ambientController.registerAmbientBacklightListener(ambientListener1) assertEquals( "Should register a new sensor listener", 1, listenerRegistrationCount ) assertTrue("Should have sensor listener registered", listenerRegistered) val ambientListener2 = AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { } ambientController.registerAmbientBacklightListener(ambientListener2) assertEquals( "Should not register a new sensor listener when adding a second ambient listener", 1, listenerRegistrationCount ) assertTrue("Should have sensor listener registered", listenerRegistered) ambientController.unregisterAmbientBacklightListener(ambientListener1) assertTrue("Should have sensor listener registered", listenerRegistered) ambientController.unregisterAmbientBacklightListener(ambientListener2) assertFalse( "Should not have sensor listener registered if there are no ambient listeners", listenerRegistered ) } @Test fun testDisplayChange_shouldNotReRegisterListener_ifUniqueIdSame() { setupSensorWithInitialLux(0F) val count = listenerRegistrationCount ambientController.onDisplayChanged(Display.DEFAULT_DISPLAY) testLooper.dispatchAll() assertEquals( "Should not re-register listener on display change if unique is same", count, listenerRegistrationCount ) } @Test fun testDisplayChange_shouldReRegisterListener_ifUniqueIdChanges() { setupSensorWithInitialLux(0F) val count = listenerRegistrationCount currentDisplayInfo.uniqueId = "xyz" ambientController.onDisplayChanged(Display.DEFAULT_DISPLAY) testLooper.dispatchAll() assertEquals( "Should re-register listener on display change if unique id changed", count + 1, listenerRegistrationCount ) } @Test fun testBrightnessDoesntChange_betweenIncreaseAndDecreaseThresholds() { setupSensorWithInitialLux(1001F) // Previous state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1] // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900] lastBrightnessCallback = -1 repeat(HYSTERESIS_THRESHOLD) { sendAmbientLuxValue(999F) } testLooper.dispatchAll() assertEquals( "Should not receive any callback for brightness change", -1, lastBrightnessCallback ) } @Test fun testBrightnessDoesntChange_onChangeOccurringLessThanHysteresisThreshold() { setupSensorWithInitialLux(1001F) // Previous state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1] // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900] lastBrightnessCallback = -1 repeat(HYSTERESIS_THRESHOLD - 1) { sendAmbientLuxValue(2001F) } testLooper.dispatchAll() assertEquals( "Should not receive any callback for brightness change", -1, lastBrightnessCallback ) } private fun sendAmbientLuxValue(luxValue: Float) { ambientController.onSensorChanged(SensorEvent(lightSensor, 0, 0, floatArrayOf(luxValue))) } }