diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java index 1df9b75f0b096..18d0b09faa145 100644 --- a/core/java/android/hardware/lights/Light.java +++ b/core/java/android/hardware/lights/Light.java @@ -110,6 +110,8 @@ public final class Light implements Parcelable { private final int mOrdinal; private final int mType; private final int mCapabilities; + @Nullable + private final int[] mPreferredBrightnessLevels; /** * Creates a new light with the given data. @@ -117,7 +119,7 @@ public final class Light implements Parcelable { * @hide */ public Light(int id, int ordinal, int type) { - this(id, "Light", ordinal, type, 0); + this(id, "Light", ordinal, type, 0, null); } /** @@ -126,11 +128,22 @@ public final class Light implements Parcelable { * @hide */ public Light(int id, String name, int ordinal, int type, int capabilities) { + this(id, name, ordinal, type, capabilities, null); + } + + /** + * Creates a new light with the given data. + * + * @hide + */ + public Light(int id, String name, int ordinal, int type, int capabilities, + @Nullable int[] preferredBrightnessLevels) { mId = id; mName = name; mOrdinal = ordinal; mType = type; mCapabilities = capabilities; + mPreferredBrightnessLevels = preferredBrightnessLevels; } private Light(@NonNull Parcel in) { @@ -139,6 +152,7 @@ public final class Light implements Parcelable { mOrdinal = in.readInt(); mType = in.readInt(); mCapabilities = in.readInt(); + mPreferredBrightnessLevels = in.createIntArray(); } /** Implement the Parcelable interface */ @@ -149,6 +163,7 @@ public final class Light implements Parcelable { dest.writeInt(mOrdinal); dest.writeInt(mType); dest.writeInt(mCapabilities); + dest.writeIntArray(mPreferredBrightnessLevels); } /** Implement the Parcelable interface */ @@ -252,4 +267,17 @@ public final class Light implements Parcelable { return (mCapabilities & LIGHT_CAPABILITY_COLOR_RGB) == LIGHT_CAPABILITY_COLOR_RGB; } + /** + * Returns preferred brightness levels for the light which will be used when user + * increase/decrease brightness levels for the light (currently only used for Keyboard + * backlight control using backlight up/down keys). + * + * The values in the preferred brightness level array are in the range [0, 255]. + * + * @hide + */ + @Nullable + public int[] getPreferredBrightnessLevels() { + return mPreferredBrightnessLevels; + } } diff --git a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java index 3854adad9cd85..7c7f1513bd96f 100644 --- a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java +++ b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java @@ -38,8 +38,15 @@ public final class InputFeatureFlagProvider { private static final boolean KEYBOARD_BACKLIGHT_ANIMATION_ENABLED = InputProperties.enable_keyboard_backlight_animation().orElse(false); + // To disable Custom keyboard backlight levels support via IDC files run: + // adb shell setprop persist.input.keyboard_backlight_custom_levels.enabled false (requires + // restart) + private static final boolean KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED = + InputProperties.enable_keyboard_backlight_custom_levels().orElse(true); + private static Optional sKeyboardBacklightControlOverride = Optional.empty(); private static Optional sKeyboardBacklightAnimationOverride = Optional.empty(); + private static Optional sKeyboardBacklightCustomLevelsOverride = Optional.empty(); public static boolean isKeyboardBacklightControlEnabled() { return sKeyboardBacklightControlOverride.orElse(KEYBOARD_BACKLIGHT_CONTROL_ENABLED); @@ -49,6 +56,11 @@ public final class InputFeatureFlagProvider { return sKeyboardBacklightAnimationOverride.orElse(KEYBOARD_BACKLIGHT_ANIMATION_ENABLED); } + public static boolean isKeyboardBacklightCustomLevelsEnabled() { + return sKeyboardBacklightCustomLevelsOverride.orElse( + KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED); + } + public static void setKeyboardBacklightControlEnabled(boolean enabled) { sKeyboardBacklightControlOverride = Optional.of(enabled); } @@ -57,11 +69,16 @@ public final class InputFeatureFlagProvider { sKeyboardBacklightAnimationOverride = Optional.of(enabled); } + public static void setKeyboardBacklightCustomLevelsEnabled(boolean enabled) { + sKeyboardBacklightCustomLevelsOverride = Optional.of(enabled); + } + /** * Clears all input feature flag overrides. */ public static void clearOverrides() { sKeyboardBacklightControlOverride = Optional.empty(); sKeyboardBacklightAnimationOverride = Optional.empty(); + sKeyboardBacklightCustomLevelsOverride = Optional.empty(); } } diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java index 61ca0cbff7bfd..36238a8cfd230 100644 --- a/services/core/java/com/android/server/input/KeyboardBacklightController.java +++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java @@ -46,6 +46,7 @@ import java.time.Duration; import java.util.Arrays; import java.util.Objects; import java.util.OptionalInt; +import java.util.TreeSet; /** * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard @@ -70,7 +71,9 @@ final class KeyboardBacklightController implements private static final int MSG_NOTIFY_USER_INACTIVITY = 5; private static final int MSG_INTERACTIVE_STATE_CHANGED = 6; private static final int MAX_BRIGHTNESS = 255; - private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10; + private static final int DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS = 10; + @VisibleForTesting + static final int MAX_BRIGHTNESS_CHANGE_STEPS = 10; private static final long TRANSITION_ANIMATION_DURATION_MILLIS = Duration.ofSeconds(1).toMillis(); @@ -80,7 +83,8 @@ final class KeyboardBacklightController implements static final long USER_INACTIVITY_THRESHOLD_MILLIS = Duration.ofSeconds(30).toMillis(); @VisibleForTesting - static final int[] BRIGHTNESS_VALUE_FOR_LEVEL = new int[NUM_BRIGHTNESS_CHANGE_STEPS + 1]; + static final int[] DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL = + new int[DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS + 1]; private final Context mContext; private final NativeInputManagerService mNative; @@ -104,10 +108,10 @@ final class KeyboardBacklightController implements static { // Fixed brightness levels to avoid issues when converting back and forth from the // device brightness range to [0-255] - // Levels are: 0, 25, 51, ..., 255 - for (int i = 0; i <= NUM_BRIGHTNESS_CHANGE_STEPS; i++) { - BRIGHTNESS_VALUE_FOR_LEVEL[i] = (int) Math.floor( - ((float) i * MAX_BRIGHTNESS) / NUM_BRIGHTNESS_CHANGE_STEPS); + // Levels are: 0, 51, ..., 255 + for (int i = 0; i <= DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS; i++) { + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[i] = (int) Math.floor( + ((float) i * MAX_BRIGHTNESS) / DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS); } } @@ -184,7 +188,8 @@ final class KeyboardBacklightController implements final int currBrightnessLevel = state.mBrightnessLevel; final int newBrightnessLevel; if (direction == Direction.DIRECTION_UP) { - newBrightnessLevel = Math.min(currBrightnessLevel + 1, NUM_BRIGHTNESS_CHANGE_STEPS); + newBrightnessLevel = Math.min(currBrightnessLevel + 1, + state.getNumBrightnessChangeSteps()); } else { newBrightnessLevel = Math.max(currBrightnessLevel - 1, 0); } @@ -194,7 +199,7 @@ final class KeyboardBacklightController implements try { mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(), keyboardBacklight.getId(), - BRIGHTNESS_VALUE_FOR_LEVEL[newBrightnessLevel]); + state.mBrightnessValueForLevel[newBrightnessLevel]); } finally { mDataStore.saveIfNeeded(); } @@ -202,6 +207,7 @@ final class KeyboardBacklightController implements } private void restoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) { + KeyboardBacklightState state = mKeyboardBacklights.get(inputDevice.getId()); OptionalInt brightness; synchronized (mDataStore) { brightness = mDataStore.getKeyboardBacklightBrightness( @@ -209,9 +215,9 @@ final class KeyboardBacklightController implements } if (brightness.isPresent()) { int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt())); - int index = Arrays.binarySearch(BRIGHTNESS_VALUE_FOR_LEVEL, brightnessValue); + int index = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue); if (index < 0) { - index = Math.min(NUM_BRIGHTNESS_CHANGE_STEPS, -(index + 1)); + index = Math.min(state.getNumBrightnessChangeSteps(), -(index + 1)); } updateBacklightState(inputDevice.getId(), index, false /* isTriggeredByKeyPress */); if (DEBUG) { @@ -386,7 +392,7 @@ final class KeyboardBacklightController implements for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) { IKeyboardBacklightState callbackState = new IKeyboardBacklightState(); callbackState.brightnessLevel = brightnessLevel; - callbackState.maxBrightnessLevel = NUM_BRIGHTNESS_CHANGE_STEPS; + callbackState.maxBrightnessLevel = state.getNumBrightnessChangeSteps(); mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged( deviceId, callbackState, isTriggeredByKeyPress); } @@ -443,10 +449,6 @@ final class KeyboardBacklightController implements ipw.decreaseIndent(); } - private static boolean isAnimationEnabled() { - return InputFeatureFlagProvider.isKeyboardBacklightAnimationEnabled(); - } - // A record of a registered Keyboard backlight listener from one process. private class KeyboardBacklightListenerRecord implements IBinder.DeathRecipient { public final int mPid; @@ -482,18 +484,52 @@ final class KeyboardBacklightController implements private final Light mLight; private int mBrightnessLevel; private ValueAnimator mAnimator; + private final int[] mBrightnessValueForLevel; KeyboardBacklightState(int deviceId, Light light) { mDeviceId = deviceId; mLight = light; + mBrightnessValueForLevel = setupBrightnessLevels(); + } + + private int[] setupBrightnessLevels() { + if (!InputFeatureFlagProvider.isKeyboardBacklightCustomLevelsEnabled()) { + return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL; + } + int[] customLevels = mLight.getPreferredBrightnessLevels(); + if (customLevels == null || customLevels.length == 0) { + return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL; + } + TreeSet brightnessLevels = new TreeSet<>(); + brightnessLevels.add(0); + for (int level : customLevels) { + if (level > 0 && level < MAX_BRIGHTNESS) { + brightnessLevels.add(level); + } + } + brightnessLevels.add(MAX_BRIGHTNESS); + int brightnessChangeSteps = brightnessLevels.size() - 1; + if (brightnessChangeSteps > MAX_BRIGHTNESS_CHANGE_STEPS) { + return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL; + } + int[] result = new int[brightnessLevels.size()]; + int index = 0; + for (int val : brightnessLevels) { + result[index++] = val; + } + return result; + } + + private int getNumBrightnessChangeSteps() { + return mBrightnessValueForLevel.length - 1; } private void onBacklightStateChanged() { - setBacklightValue(mIsBacklightOn ? BRIGHTNESS_VALUE_FOR_LEVEL[mBrightnessLevel] : 0); + setBacklightValue(mIsBacklightOn ? mBrightnessValueForLevel[mBrightnessLevel] : 0); } private void setBrightnessLevel(int brightnessLevel) { if (mIsBacklightOn) { - setBacklightValue(BRIGHTNESS_VALUE_FOR_LEVEL[brightnessLevel]); + setBacklightValue(mBrightnessValueForLevel[brightnessLevel]); } mBrightnessLevel = brightnessLevel; } @@ -509,7 +545,7 @@ final class KeyboardBacklightController implements if (fromValue == toValue) { return; } - if (isAnimationEnabled()) { + if (InputFeatureFlagProvider.isKeyboardBacklightAnimationEnabled()) { startAnimation(fromValue, toValue); } else { mNative.setLightColor(mDeviceId, mLight.getId(), Color.argb(toValue, 0, 0, 0)); diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 2f1bf35a5ca8a..8587270dd1870 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -2214,13 +2214,25 @@ static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId jCapability |= env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightCapabilityColorRgb); } + + ScopedLocalRef jPreferredBrightnessLevels{env}; + if (!lightInfo.preferredBrightnessLevels.empty()) { + std::vector vec; + for (auto it : lightInfo.preferredBrightnessLevels) { + vec.push_back(ftl::to_underlying(it)); + } + jPreferredBrightnessLevels.reset(env->NewIntArray(vec.size())); + env->SetIntArrayRegion(jPreferredBrightnessLevels.get(), 0, vec.size(), vec.data()); + } + ScopedLocalRef lightObj(env, env->NewObject(gLightClassInfo.clazz, gLightClassInfo.constructor, static_cast(lightInfo.id), env->NewStringUTF(lightInfo.name.c_str()), static_cast(lightInfo.ordinal), - jTypeId, jCapability)); + jTypeId, jCapability, + jPreferredBrightnessLevels.get())); // Add light object to list env->CallBooleanMethod(jLights, gArrayListClassInfo.add, lightObj.get()); } @@ -2846,7 +2858,7 @@ int register_android_server_InputManager(JNIEnv* env) { FIND_CLASS(gLightClassInfo.clazz, "android/hardware/lights/Light"); gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz)); GET_METHOD_ID(gLightClassInfo.constructor, gLightClassInfo.clazz, "", - "(ILjava/lang/String;III)V"); + "(ILjava/lang/String;III[I)V"); gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz)); gLightClassInfo.lightTypeInput = diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt index 272679280a622..ef15ccba4c746 100644 --- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt @@ -32,7 +32,8 @@ import android.platform.test.annotations.Presubmit import android.view.InputDevice import androidx.test.annotation.UiThreadTest import androidx.test.core.app.ApplicationProvider -import com.android.server.input.KeyboardBacklightController.BRIGHTNESS_VALUE_FOR_LEVEL +import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL +import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS import org.junit.After import org.junit.Assert.assertEquals @@ -65,12 +66,20 @@ private fun createKeyboard(deviceId: Int): InputDevice = .build() private fun createLight(lightId: Int, lightType: Int): Light = + createLight( + lightId, + lightType, + null + ) + +private fun createLight(lightId: Int, lightType: Int, suggestedBrightnessLevels: IntArray?): Light = Light( lightId, "Light $lightId", 1, lightType, - Light.LIGHT_CAPABILITY_BRIGHTNESS + Light.LIGHT_CAPABILITY_BRIGHTNESS, + suggestedBrightnessLevels ) /** * Tests for {@link KeyboardBacklightController}. @@ -98,7 +107,6 @@ class KeyboardBacklightControllerTests { private lateinit var context: Context private lateinit var dataStore: PersistentDataStore private lateinit var testLooper: TestLooper - private val totalLevels = BRIGHTNESS_VALUE_FOR_LEVEL.size private var lightColorMap: HashMap = HashMap() private var lastBacklightState: KeyboardBacklightState? = null private var sysfsNodeChanges = 0 @@ -146,84 +154,21 @@ class KeyboardBacklightControllerTests { @Test fun testKeyboardBacklightIncrementDecrement() { - BacklightAnimationFlag(false).use { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - for (level in 1 until totalLevels) { - incrementKeyboardBacklight(DEVICE_ID) - assertEquals( - "Light value for level $level mismatched", - Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0), - lightColorMap[LIGHT_ID] - ) - assertEquals( - "Light value for level $level must be correctly stored in the datastore", - BRIGHTNESS_VALUE_FOR_LEVEL[level], - dataStore.getKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, - LIGHT_ID - ).asInt - ) - } - - // Increment above max level - incrementKeyboardBacklight(DEVICE_ID) - assertEquals( - "Light value for max level mismatched", - Color.argb(MAX_BRIGHTNESS, 0, 0, 0), - lightColorMap[LIGHT_ID] - ) - assertEquals( - "Light value for max level must be correctly stored in the datastore", - MAX_BRIGHTNESS, - dataStore.getKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, - LIGHT_ID - ).asInt - ) - - for (level in totalLevels - 2 downTo 0) { - decrementKeyboardBacklight(DEVICE_ID) - assertEquals( - "Light value for level $level mismatched", - Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0), - lightColorMap[LIGHT_ID] - ) - assertEquals( - "Light value for level $level must be correctly stored in the datastore", - BRIGHTNESS_VALUE_FOR_LEVEL[level], - dataStore.getKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, - LIGHT_ID - ).asInt - ) - } - - // Decrement below min level - decrementKeyboardBacklight(DEVICE_ID) - assertEquals( - "Light value for min level mismatched", - Color.argb(0, 0, 0, 0), - lightColorMap[LIGHT_ID] - ) - assertEquals( - "Light value for min level must be correctly stored in the datastore", - 0, - dataStore.getKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, - LIGHT_ID - ).asInt - ) + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL) } } @Test fun testKeyboardWithoutBacklight() { - BacklightAnimationFlag(false).use { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use { val keyboardWithoutBacklight = createKeyboard(DEVICE_ID) val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT) `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight) @@ -237,7 +182,7 @@ class KeyboardBacklightControllerTests { @Test fun testKeyboardWithMultipleLight() { - BacklightAnimationFlag(false).use { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT) @@ -259,17 +204,17 @@ class KeyboardBacklightControllerTests { @Test fun testRestoreBacklightOnInputDeviceAdded() { - BacklightAnimationFlag(false).use { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - for (level in 1 until totalLevels) { + for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) { dataStore.setKeyboardBacklightBrightness( keyboardWithBacklight.descriptor, LIGHT_ID, - BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1 + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1 ) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) @@ -278,7 +223,7 @@ class KeyboardBacklightControllerTests { assertEquals( "Keyboard backlight level should be restored to the level saved in the " + "data store", - Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0), + Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0), lightColorMap[LIGHT_ID] ) keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID) @@ -288,7 +233,7 @@ class KeyboardBacklightControllerTests { @Test fun testRestoreBacklightOnInputDeviceChanged() { - BacklightAnimationFlag(false).use { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) @@ -320,9 +265,10 @@ class KeyboardBacklightControllerTests { @Test fun testKeyboardBacklight_registerUnregisterListener() { - BacklightAnimationFlag(false).use { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1 `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) @@ -341,13 +287,13 @@ class KeyboardBacklightControllerTests { lastBacklightState!!.deviceId ) assertEquals( - "Backlight state brightnessLevel should be " + 1, + "Backlight state brightnessLevel should be 1", 1, lastBacklightState!!.brightnessLevel ) assertEquals( - "Backlight state maxBrightnessLevel should be " + (totalLevels - 1), - (totalLevels - 1), + "Backlight state maxBrightnessLevel should be $maxLevel", + maxLevel, lastBacklightState!!.maxBrightnessLevel ) assertEquals( @@ -368,7 +314,7 @@ class KeyboardBacklightControllerTests { @Test fun testKeyboardBacklight_userActivity() { - BacklightAnimationFlag(false).use { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) @@ -400,7 +346,7 @@ class KeyboardBacklightControllerTests { @Test fun testKeyboardBacklight_displayOnOff() { - BacklightAnimationFlag(false).use { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) @@ -490,7 +436,7 @@ class KeyboardBacklightControllerTests { @Test @UiThreadTest fun testKeyboardBacklightAnimation_onChangeLevels() { - BacklightAnimationFlag(true).use { + KeyboardBacklightFlags(animationEnabled = true, customLevelsEnabled = false).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) @@ -500,17 +446,145 @@ class KeyboardBacklightControllerTests { incrementKeyboardBacklight(DEVICE_ID) assertEquals( "Should start animation from level 0", - BRIGHTNESS_VALUE_FOR_LEVEL[0], + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0], lastAnimationValues[0] ) assertEquals( "Should start animation to level 1", - BRIGHTNESS_VALUE_FOR_LEVEL[1], + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], lastAnimationValues[1] ) } } + @Test + fun testKeyboardBacklightPreferredLevels() { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use { + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, + suggestedLevels) + `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, + suggestedLevels) + } + } + + @Test + fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use { + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) } + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, + suggestedLevels) + `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL) + } + } + + @Test + fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use { + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val suggestedLevels = intArrayOf(22, 63, 135, 196) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, + suggestedLevels) + `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + // Framework will add the lowest and maximum levels if not provided via config + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, + intArrayOf(0, 22, 63, 135, 196, 255)) + } + } + + @Test + fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() { + KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use { + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, + suggestedLevels) + `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + // Framework will drop out of bound levels in the config + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, + intArrayOf(0, 22, 63, 135, 196, 255)) + } + } + + private fun assertIncrementDecrementForLevels( + device: InputDevice, + light: Light, + expectedLevels: IntArray + ) { + val deviceId = device.id + val lightId = light.id + for (level in 1 until expectedLevels.size) { + incrementKeyboardBacklight(deviceId) + assertEquals( + "Light value for level $level mismatched", + Color.argb(expectedLevels[level], 0, 0, 0), + lightColorMap[lightId] + ) + assertEquals( + "Light value for level $level must be correctly stored in the datastore", + expectedLevels[level], + dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt + ) + } + + // Increment above max level + incrementKeyboardBacklight(deviceId) + assertEquals( + "Light value for max level mismatched", + Color.argb(MAX_BRIGHTNESS, 0, 0, 0), + lightColorMap[lightId] + ) + assertEquals( + "Light value for max level must be correctly stored in the datastore", + MAX_BRIGHTNESS, + dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt + ) + + for (level in expectedLevels.size - 2 downTo 0) { + decrementKeyboardBacklight(deviceId) + assertEquals( + "Light value for level $level mismatched", + Color.argb(expectedLevels[level], 0, 0, 0), + lightColorMap[lightId] + ) + assertEquals( + "Light value for level $level must be correctly stored in the datastore", + expectedLevels[level], + dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt + ) + } + + // Decrement below min level + decrementKeyboardBacklight(deviceId) + assertEquals( + "Light value for min level mismatched", + Color.argb(0, 0, 0, 0), + lightColorMap[lightId] + ) + assertEquals( + "Light value for min level must be correctly stored in the datastore", + 0, + dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt + ) + } + inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() { override fun onBrightnessChanged( deviceId: Int, @@ -545,9 +619,13 @@ class KeyboardBacklightControllerTests { val isTriggeredByKeyPress: Boolean ) - private inner class BacklightAnimationFlag constructor(enabled: Boolean) : AutoCloseable { + private inner class KeyboardBacklightFlags constructor( + animationEnabled: Boolean, + customLevelsEnabled: Boolean + ) : AutoCloseable { init { - InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(enabled) + InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled) + InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled) } override fun close() {