Add support for custom keyboard backlight levels via IDC file

Test: atest KeyboardBacklightControllerTests
Bug: 280797494
Change-Id: Ib9e919ad280d8f8f7adf3491fab2f69616af1762
This commit is contained in:
Vaibhav Devmurari
2023-05-04 15:15:41 +00:00
parent e043e8b713
commit 3becba6b3b
5 changed files with 279 additions and 108 deletions

View File

@@ -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;
}
}

View File

@@ -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<Boolean> sKeyboardBacklightControlOverride = Optional.empty();
private static Optional<Boolean> sKeyboardBacklightAnimationOverride = Optional.empty();
private static Optional<Boolean> 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();
}
}

View File

@@ -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<Integer> 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));

View File

@@ -2214,13 +2214,25 @@ static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId
jCapability |= env->GetStaticIntField(gLightClassInfo.clazz,
gLightClassInfo.lightCapabilityColorRgb);
}
ScopedLocalRef<jintArray> jPreferredBrightnessLevels{env};
if (!lightInfo.preferredBrightnessLevels.empty()) {
std::vector<int32_t> 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<jobject> lightObj(env,
env->NewObject(gLightClassInfo.clazz,
gLightClassInfo.constructor,
static_cast<jint>(lightInfo.id),
env->NewStringUTF(lightInfo.name.c_str()),
static_cast<jint>(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, "<init>",
"(ILjava/lang/String;III)V");
"(ILjava/lang/String;III[I)V");
gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz));
gLightClassInfo.lightTypeInput =

View File

@@ -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<Int, Int> = 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() {