Add ALS controlled keyboard backlight support

DD: go/pk_ambient_backlight_control
Bug: 262039362
Test: atest FrameworksServicesTests:AmbientKeyboardBacklightControllerTests
Test: atest FrameworksServicesTests:KeyboardBacklightControllerTests
Change-Id: I5254d7db06f0beedeab59c0cc69dd3d8d046a152
This commit is contained in:
Vaibhav Devmurari
2023-05-25 17:09:19 +00:00
parent 0279d73396
commit e048d65f45
9 changed files with 1230 additions and 61 deletions

View File

@@ -412,6 +412,15 @@ public abstract class DisplayManagerInternal {
@Nullable
public abstract HostUsiVersion getHostUsiVersion(int displayId);
/**
* Get the ALS data for a particular display.
*
* @param displayId The id of the display.
* @return {@link AmbientLightSensorData}
*/
@Nullable
public abstract AmbientLightSensorData getAmbientLightSensorData(int displayId);
/**
* Get all available DisplayGroupIds.
*/
@@ -669,4 +678,23 @@ public abstract class DisplayManagerInternal {
return "RefreshRateLimitation(" + type + ": " + range + ")";
}
}
/**
* Class to provide Ambient sensor data using the API
* {@link DisplayManagerInternal#getAmbientLightSensorData(int)}
*/
public static final class AmbientLightSensorData {
public String sensorName;
public String sensorType;
public AmbientLightSensorData(String name, String type) {
sensorName = name;
sensorType = type;
}
@Override
public String toString() {
return "AmbientLightSensorData(" + sensorName + ", " + sensorType + ")";
}
}
}

View File

@@ -1675,16 +1675,56 @@
in the config_autoBrightnessLevels array. This array should have size one greater
than the size of the config_autoBrightnessLevels array.
The brightness values must be between 0 and 255 and be non-decreasing.
This must be overridden in platform specific overlays -->
<integer-array name="config_autoBrightnessButtonBacklightValues">
</integer-array>
<!-- Array of output values for keyboard backlight corresponding to the lux values
in the config_autoBrightnessLevels array. This array should have size one greater
than the size of the config_autoBrightnessLevels array.
The brightness values must be between 0 and 255 and be non-decreasing.
<!-- Smoothing constant for Ambient keyboard backlight change. It should contain value
in the range (0.0, 1.0] that will be used to calculate smoothed lux values using
simple exponential smoothing. This value indicated how quickly we transition to
the lux values provided by the Ambient light sensor.
Simple formula for newLuxValue = (1-constant)*currLuxValue + constant*rawLuxValue, where
rawLuxValue is the value provided by the Ambient light sensor. (e.g. value of 1.0 means we
immediately start using the value provided by the Ambient light sensor)
This must be overridden in platform specific overlays -->
<integer-array name="config_autoBrightnessKeyboardBacklightValues">
<item name="config_autoKeyboardBrightnessSmoothingConstant" format="float" type="dimen">
1.0
</item>
<!-- Array of output values for keyboard backlight corresponding to the lux values
in the config_autoKeyboardBacklight(Increase/Decrease)LuxThreshold arrays.
The brightness values must be between 0 and 255 and be non-decreasing.
This can be overridden in platform specific overlays -->
<integer-array name="config_autoKeyboardBacklightBrightnessValues">
<item>102</item>
<item>153</item>
<item>0</item>
</integer-array>
<!-- Array of threshold values for keyboard backlight corresponding to the values
in the config_autoKeyboardBacklightBrightnessValues array.
These lux values indicate when to move to a lower keyboard backlight value,
as defined in config_autoKeyboardBacklightBrightnessValues array.
This can be overridden in platform specific overlays -->
<integer-array name="config_autoKeyboardBacklightDecreaseLuxThreshold">
<item>-1</item>
<item>40</item>
<item>150</item>
</integer-array>
<!-- Array of threshold values for keyboard backlight corresponding to the values
in the config_autoKeyboardBacklightBrightnessValues array.
These lux values indicate when to move to a higher keyboard backlight value,
as defined in config_autoKeyboardBacklightBrightnessValues array.
This can be overridden in platform specific overlays -->
<integer-array name="config_autoKeyboardBacklightIncreaseLuxThreshold">
<item>55</item>
<item>200</item>
<item>-1</item>
</integer-array>
<!-- An array describing the screen's backlight values corresponding to the brightness

View File

@@ -520,6 +520,7 @@
<java-symbol type="dimen" name="config_viewConfigurationHandwritingSlop" />
<java-symbol type="dimen" name="config_viewConfigurationHoverSlop" />
<java-symbol type="dimen" name="config_ambiguousGestureMultiplier" />
<java-symbol type="dimen" name="config_autoKeyboardBrightnessSmoothingConstant" />
<java-symbol type="dimen" name="config_viewMinFlingVelocity" />
<java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
<java-symbol type="dimen" name="config_viewMinRotaryEncoderFlingVelocity" />
@@ -1888,11 +1889,13 @@
<java-symbol type="anim" name="dream_activity_open_enter" />
<java-symbol type="anim" name="dream_activity_close_exit" />
<java-symbol type="array" name="config_autoBrightnessButtonBacklightValues" />
<java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" />
<java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" />
<java-symbol type="array" name="config_autoBrightnessLcdBacklightValues_doze" />
<java-symbol type="array" name="config_autoBrightnessLevels" />
<java-symbol type="array" name="config_autoBrightnessLevelsIdle" />
<java-symbol type="array" name="config_autoKeyboardBacklightBrightnessValues" />
<java-symbol type="array" name="config_autoKeyboardBacklightDecreaseLuxThreshold" />
<java-symbol type="array" name="config_autoKeyboardBacklightIncreaseLuxThreshold" />
<java-symbol type="array" name="config_ambientThresholdLevels" />
<java-symbol type="array" name="config_ambientBrighteningThresholds" />
<java-symbol type="array" name="config_ambientDarkeningThresholds" />

View File

@@ -4627,6 +4627,22 @@ public final class DisplayManagerService extends SystemService {
}
}
@Override
public AmbientLightSensorData getAmbientLightSensorData(int displayId) {
synchronized (mSyncRoot) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display == null) {
return null;
}
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
if (device == null) {
return null;
}
SensorData data = device.getDisplayDeviceConfig().getAmbientLightSensor();
return new AmbientLightSensorData(data.name, data.type);
}
}
@Override
public IntArray getDisplayGroupIds() {
Set<Integer> visitedIds = new ArraySet<>();

View File

@@ -0,0 +1,431 @@
/*
* 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.annotation.MainThread;
import android.annotation.Nullable;
import android.content.Context;
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.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.Slog;
import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayInfo;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.display.utils.SensorUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
* backlight based on ambient light sensor.
*/
final class AmbientKeyboardBacklightController implements DisplayManager.DisplayListener,
SensorEventListener {
private static final String TAG = "KbdBacklightController";
// To enable these logs, run:
// 'adb shell setprop log.tag.KbdBacklightController DEBUG' (requires restart)
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// Number of light sensor responses required to overcome temporal hysteresis.
@VisibleForTesting
public static final int HYSTERESIS_THRESHOLD = 2;
private static final int MSG_BRIGHTNESS_CALLBACK = 0;
private static final int MSG_SETUP_DISPLAY_AND_SENSOR = 1;
private static final Object sAmbientControllerLock = new Object();
private final Context mContext;
private final Handler mHandler;
@Nullable
@GuardedBy("sAmbientControllerLock")
private Sensor mLightSensor;
@GuardedBy("sAmbientControllerLock")
private String mCurrentDefaultDisplayUniqueId;
// List of currently registered ambient backlight listeners
@GuardedBy("sAmbientControllerLock")
private final List<AmbientKeyboardBacklightListener> mAmbientKeyboardBacklightListeners =
new ArrayList<>();
private BrightnessStep[] mBrightnessSteps;
private int mCurrentBrightnessStepIndex;
private HysteresisState mHysteresisState;
private int mHysteresisCount = 0;
private float mSmoothingConstant;
private int mSmoothedLux;
private int mSmoothedLuxAtLastAdjustment;
private enum HysteresisState {
// The most-recent mSmoothedLux matched mSmoothedLuxAtLastAdjustment.
STABLE,
// The most-recent mSmoothedLux was less than mSmoothedLuxAtLastAdjustment.
DECREASING,
// The most-recent mSmoothedLux was greater than mSmoothedLuxAtLastAdjustment.
INCREASING,
// The brightness should be adjusted immediately after the next sensor reading.
IMMEDIATE,
}
AmbientKeyboardBacklightController(Context context, Looper looper) {
mContext = context;
mHandler = new Handler(looper, this::handleMessage);
initConfiguration();
}
public void systemRunning() {
mHandler.sendEmptyMessage(MSG_SETUP_DISPLAY_AND_SENSOR);
DisplayManager displayManager = Objects.requireNonNull(
mContext.getSystemService(DisplayManager.class));
displayManager.registerDisplayListener(this, mHandler);
}
public void registerAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
synchronized (sAmbientControllerLock) {
if (mAmbientKeyboardBacklightListeners.contains(listener)) {
throw new IllegalStateException(
"AmbientKeyboardBacklightListener was already registered, listener = "
+ listener);
}
if (mAmbientKeyboardBacklightListeners.isEmpty()) {
// Add sensor listener when we add the first ambient backlight listener.
addSensorListener(mLightSensor);
}
mAmbientKeyboardBacklightListeners.add(listener);
}
}
public void unregisterAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
synchronized (sAmbientControllerLock) {
if (!mAmbientKeyboardBacklightListeners.contains(listener)) {
throw new IllegalStateException(
"AmbientKeyboardBacklightListener was never registered, listener = "
+ listener);
}
mAmbientKeyboardBacklightListeners.remove(listener);
if (mAmbientKeyboardBacklightListeners.isEmpty()) {
removeSensorListener(mLightSensor);
}
}
}
private void sendBrightnessAdjustment(int brightnessValue) {
Message msg = Message.obtain(mHandler, MSG_BRIGHTNESS_CALLBACK, brightnessValue);
mHandler.sendMessage(msg);
}
@MainThread
private void handleBrightnessCallback(int brightnessValue) {
synchronized (sAmbientControllerLock) {
for (AmbientKeyboardBacklightListener listener : mAmbientKeyboardBacklightListeners) {
listener.onKeyboardBacklightValueChanged(brightnessValue);
}
}
}
@MainThread
private void handleAmbientLuxChange(float rawLux) {
if (rawLux < 0) {
Slog.w(TAG, "Light sensor doesn't have valid value");
return;
}
updateSmoothedLux(rawLux);
if (mHysteresisState != HysteresisState.IMMEDIATE
&& mSmoothedLux == mSmoothedLuxAtLastAdjustment) {
mHysteresisState = HysteresisState.STABLE;
return;
}
int newStepIndex = Math.max(0, mCurrentBrightnessStepIndex);
int numSteps = mBrightnessSteps.length;
if (mSmoothedLux > mSmoothedLuxAtLastAdjustment) {
if (mHysteresisState != HysteresisState.IMMEDIATE
&& mHysteresisState != HysteresisState.INCREASING) {
if (DEBUG) {
Slog.d(TAG, "ALS transitioned to brightness increasing state");
}
mHysteresisState = HysteresisState.INCREASING;
mHysteresisCount = 0;
}
for (; newStepIndex < numSteps; newStepIndex++) {
if (mSmoothedLux < mBrightnessSteps[newStepIndex].mIncreaseLuxThreshold) {
break;
}
}
} else if (mSmoothedLux < mSmoothedLuxAtLastAdjustment) {
if (mHysteresisState != HysteresisState.IMMEDIATE
&& mHysteresisState != HysteresisState.DECREASING) {
if (DEBUG) {
Slog.d(TAG, "ALS transitioned to brightness decreasing state");
}
mHysteresisState = HysteresisState.DECREASING;
mHysteresisCount = 0;
}
for (; newStepIndex >= 0; newStepIndex--) {
if (mSmoothedLux > mBrightnessSteps[newStepIndex].mDecreaseLuxThreshold) {
break;
}
}
}
if (mHysteresisState == HysteresisState.IMMEDIATE) {
mCurrentBrightnessStepIndex = newStepIndex;
mSmoothedLuxAtLastAdjustment = mSmoothedLux;
mHysteresisState = HysteresisState.STABLE;
mHysteresisCount = 0;
sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
return;
}
if (newStepIndex == mCurrentBrightnessStepIndex) {
return;
}
mHysteresisCount++;
if (DEBUG) {
Slog.d(TAG, "Incremented hysteresis count to " + mHysteresisCount + " (lux went from "
+ mSmoothedLuxAtLastAdjustment + " to " + mSmoothedLux + ")");
}
if (mHysteresisCount >= HYSTERESIS_THRESHOLD) {
mCurrentBrightnessStepIndex = newStepIndex;
mSmoothedLuxAtLastAdjustment = mSmoothedLux;
mHysteresisCount = 1;
sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
}
}
@MainThread
private void handleDisplayChange() {
DisplayManagerInternal displayManagerInternal = LocalServices.getService(
DisplayManagerInternal.class);
DisplayInfo displayInfo = displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY);
synchronized (sAmbientControllerLock) {
if (Objects.equals(mCurrentDefaultDisplayUniqueId, displayInfo.uniqueId)) {
return;
}
if (DEBUG) {
Slog.d(TAG, "Default display changed: resetting the light sensor");
}
// Keep track of current default display
mCurrentDefaultDisplayUniqueId = displayInfo.uniqueId;
// Clear all existing sensor listeners
if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
removeSensorListener(mLightSensor);
}
mLightSensor = getAmbientLightSensor(
displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY));
// Re-add sensor listeners if required;
if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
addSensorListener(mLightSensor);
}
}
}
private Sensor getAmbientLightSensor(
DisplayManagerInternal.AmbientLightSensorData ambientSensor) {
SensorManager sensorManager = Objects.requireNonNull(
mContext.getSystemService(SensorManager.class));
if (DEBUG) {
Slog.d(TAG, "Ambient Light sensor data: " + ambientSensor);
}
return SensorUtils.findSensor(sensorManager, ambientSensor.sensorType,
ambientSensor.sensorName, Sensor.TYPE_LIGHT);
}
private void updateSmoothedLux(float rawLux) {
// For the first sensor reading, use raw lux value directly without smoothing.
if (mHysteresisState == HysteresisState.IMMEDIATE) {
mSmoothedLux = (int) rawLux;
} else {
mSmoothedLux =
(int) (mSmoothingConstant * rawLux + (1 - mSmoothingConstant) * mSmoothedLux);
}
if (DEBUG) {
Slog.d(TAG, "Current smoothed lux from ALS = " + mSmoothedLux);
}
}
@VisibleForTesting
public void addSensorListener(@Nullable Sensor sensor) {
SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
if (sensorManager == null || sensor == null) {
return;
}
// Reset values before registering listener
reset();
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler);
if (DEBUG) {
Slog.d(TAG, "Registering ALS listener");
}
}
private void removeSensorListener(@Nullable Sensor sensor) {
SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
if (sensorManager == null || sensor == null) {
return;
}
sensorManager.unregisterListener(this, sensor);
if (DEBUG) {
Slog.d(TAG, "Unregistering ALS listener");
}
}
private void initConfiguration() {
Resources res = mContext.getResources();
int[] brightnessValueArray = res.getIntArray(
com.android.internal.R.array.config_autoKeyboardBacklightBrightnessValues);
int[] decreaseThresholdArray = res.getIntArray(
com.android.internal.R.array.config_autoKeyboardBacklightDecreaseLuxThreshold);
int[] increaseThresholdArray = res.getIntArray(
com.android.internal.R.array.config_autoKeyboardBacklightIncreaseLuxThreshold);
if (brightnessValueArray.length != decreaseThresholdArray.length
|| decreaseThresholdArray.length != increaseThresholdArray.length) {
throw new IllegalArgumentException(
"The config files for auto keyboard backlight brightness must contain arrays "
+ "of equal lengths");
}
final int size = brightnessValueArray.length;
mBrightnessSteps = new BrightnessStep[size];
for (int i = 0; i < size; i++) {
int increaseThreshold =
increaseThresholdArray[i] < 0 ? Integer.MAX_VALUE : increaseThresholdArray[i];
int decreaseThreshold =
decreaseThresholdArray[i] < 0 ? Integer.MIN_VALUE : decreaseThresholdArray[i];
mBrightnessSteps[i] = new BrightnessStep(brightnessValueArray[i], increaseThreshold,
decreaseThreshold);
}
int numSteps = mBrightnessSteps.length;
if (numSteps == 0 || mBrightnessSteps[0].mDecreaseLuxThreshold != Integer.MIN_VALUE
|| mBrightnessSteps[numSteps - 1].mIncreaseLuxThreshold != Integer.MAX_VALUE) {
throw new IllegalArgumentException(
"The config files for auto keyboard backlight brightness must contain arrays "
+ "of length > 0 and have -1 or Integer.MIN_VALUE as lower bound for "
+ "decrease thresholds and -1 or Integer.MAX_VALUE as upper bound for "
+ "increase thresholds");
}
final TypedValue smoothingConstantValue = new TypedValue();
res.getValue(
com.android.internal.R.dimen.config_autoKeyboardBrightnessSmoothingConstant,
smoothingConstantValue,
true /*resolveRefs*/);
mSmoothingConstant = smoothingConstantValue.getFloat();
if (mSmoothingConstant <= 0.0 || mSmoothingConstant > 1.0) {
throw new IllegalArgumentException(
"The config files for auto keyboard backlight brightness must contain "
+ "smoothing constant in range (0.0, 1.0].");
}
if (DEBUG) {
Log.d(TAG, "Brightness steps: " + Arrays.toString(mBrightnessSteps)
+ " Smoothing constant = " + mSmoothingConstant);
}
}
private void reset() {
mHysteresisState = HysteresisState.IMMEDIATE;
mSmoothedLux = 0;
mSmoothedLuxAtLastAdjustment = 0;
mCurrentBrightnessStepIndex = -1;
}
private boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_BRIGHTNESS_CALLBACK:
handleBrightnessCallback((int) msg.obj);
return true;
case MSG_SETUP_DISPLAY_AND_SENSOR:
handleDisplayChange();
return true;
}
return false;
}
@Override
public void onSensorChanged(SensorEvent event) {
handleAmbientLuxChange(event.values[0]);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public void onDisplayAdded(int displayId) {
handleDisplayChange();
}
@Override
public void onDisplayRemoved(int displayId) {
handleDisplayChange();
}
@Override
public void onDisplayChanged(int displayId) {
handleDisplayChange();
}
public interface AmbientKeyboardBacklightListener {
/**
* @param value between [0, 255] to which keyboard backlight needs to be set according
* to Ambient light sensor.
*/
void onKeyboardBacklightValueChanged(int value);
}
private static class BrightnessStep {
private final int mBrightnessValue;
private final int mIncreaseLuxThreshold;
private final int mDecreaseLuxThreshold;
private BrightnessStep(int brightnessValue, int increaseLuxThreshold,
int decreaseLuxThreshold) {
mBrightnessValue = brightnessValue;
mIncreaseLuxThreshold = increaseLuxThreshold;
mDecreaseLuxThreshold = decreaseLuxThreshold;
}
@Override
public String toString() {
return "BrightnessStep{" + "mBrightnessValue=" + mBrightnessValue
+ ", mIncreaseThreshold=" + mIncreaseLuxThreshold + ", mDecreaseThreshold="
+ mDecreaseLuxThreshold + '}';
}
}
}

View File

@@ -39,14 +39,21 @@ public final class InputFeatureFlagProvider {
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
// 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);
// To disable als based ambient keyboard backlight control run:
// adb shell setprop persist.input.keyboard.ambient_backlight_control.enabled false (requires
// restart)
private static final boolean AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
InputProperties.enable_ambient_keyboard_backlight_control().orElse(true);
private static Optional<Boolean> sKeyboardBacklightControlOverride = Optional.empty();
private static Optional<Boolean> sKeyboardBacklightAnimationOverride = Optional.empty();
private static Optional<Boolean> sKeyboardBacklightCustomLevelsOverride = Optional.empty();
private static Optional<Boolean> sAmbientKeyboardBacklightControlOverride = Optional.empty();
public static boolean isKeyboardBacklightControlEnabled() {
return sKeyboardBacklightControlOverride.orElse(KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
@@ -61,6 +68,11 @@ public final class InputFeatureFlagProvider {
KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED);
}
public static boolean isAmbientKeyboardBacklightControlEnabled() {
return sAmbientKeyboardBacklightControlOverride.orElse(
AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
}
public static void setKeyboardBacklightControlEnabled(boolean enabled) {
sKeyboardBacklightControlOverride = Optional.of(enabled);
}
@@ -73,6 +85,10 @@ public final class InputFeatureFlagProvider {
sKeyboardBacklightCustomLevelsOverride = Optional.of(enabled);
}
public static void setAmbientKeyboardBacklightControlEnabled(boolean enabled) {
sAmbientKeyboardBacklightControlOverride = Optional.of(enabled);
}
/**
* Clears all input feature flag overrides.
*/
@@ -80,5 +96,6 @@ public final class InputFeatureFlagProvider {
sKeyboardBacklightControlOverride = Optional.empty();
sKeyboardBacklightAnimationOverride = Optional.empty();
sKeyboardBacklightCustomLevelsOverride = Optional.empty();
sAmbientKeyboardBacklightControlOverride = Optional.empty();
}
}

View File

@@ -18,6 +18,7 @@ package com.android.server.input;
import android.animation.ValueAnimator;
import android.annotation.BinderThread;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import android.hardware.input.IKeyboardBacklightListener;
@@ -105,6 +106,12 @@ final class KeyboardBacklightController implements
private final SparseArray<KeyboardBacklightListenerRecord> mKeyboardBacklightListenerRecords =
new SparseArray<>();
private final AmbientKeyboardBacklightController mAmbientController;
@Nullable
private AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener mAmbientListener;
private int mAmbientBacklightValue = 0;
static {
// Fixed brightness levels to avoid issues when converting back and forth from the
// device brightness range to [0-255]
@@ -128,6 +135,7 @@ final class KeyboardBacklightController implements
mDataStore = dataStore;
mHandler = new Handler(looper, this::handleMessage);
mAnimatorFactory = animatorFactory;
mAmbientController = new AmbientKeyboardBacklightController(context, looper);
}
@Override
@@ -151,6 +159,11 @@ final class KeyboardBacklightController implements
}
};
observer.startObserving(UEVENT_KEYBOARD_BACKLIGHT_TAG);
if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
// Start ambient backlight controller
mAmbientController.systemRunning();
}
}
@Override
@@ -183,9 +196,21 @@ final class KeyboardBacklightController implements
if (inputDevice == null || state == null) {
return;
}
Light keyboardBacklight = state.mLight;
// Follow preset levels of brightness defined in BRIGHTNESS_LEVELS
final int currBrightnessLevel = state.mBrightnessLevel;
final int currBrightnessLevel;
if (state.mUseAmbientController) {
int index = Arrays.binarySearch(state.mBrightnessValueForLevel, mAmbientBacklightValue);
// Set current level to the lower bound of the ambient value in the brightness array.
if (index < 0) {
int lowerBound = Math.max(0, -(index + 1) - 1);
currBrightnessLevel =
direction == Direction.DIRECTION_UP ? lowerBound : lowerBound + 1;
} else {
currBrightnessLevel = index;
}
} else {
currBrightnessLevel = state.mBrightnessLevel;
}
final int newBrightnessLevel;
if (direction == Direction.DIRECTION_UP) {
newBrightnessLevel = Math.min(currBrightnessLevel + 1,
@@ -193,33 +218,67 @@ final class KeyboardBacklightController implements
} else {
newBrightnessLevel = Math.max(currBrightnessLevel - 1, 0);
}
updateBacklightState(deviceId, newBrightnessLevel, true /* isTriggeredByKeyPress */);
state.setBrightnessLevel(newBrightnessLevel);
// Might need to stop listening to ALS since user has manually selected backlight
// level through keyboard up/down button
updateAmbientLightListener();
maybeBackupBacklightBrightness(inputDevice, state.mLight,
state.mBrightnessValueForLevel[newBrightnessLevel]);
if (DEBUG) {
Slog.d(TAG,
"Changing state from " + state.mBrightnessLevel + " to " + newBrightnessLevel);
}
synchronized (mKeyboardBacklightListenerRecords) {
for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
IKeyboardBacklightState callbackState = new IKeyboardBacklightState();
callbackState.brightnessLevel = newBrightnessLevel;
callbackState.maxBrightnessLevel = state.getNumBrightnessChangeSteps();
mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
deviceId, callbackState, true);
}
}
}
private void maybeBackupBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight,
int brightnessValue) {
// Don't back up or restore when ALS based keyboard backlight is enabled
if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
return;
}
synchronized (mDataStore) {
try {
mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
keyboardBacklight.getId(),
state.mBrightnessValueForLevel[newBrightnessLevel]);
brightnessValue);
} finally {
mDataStore.saveIfNeeded();
}
}
}
private void restoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
private void maybeRestoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
// Don't back up or restore when ALS based keyboard backlight is enabled
if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
return;
}
KeyboardBacklightState state = mKeyboardBacklights.get(inputDevice.getId());
OptionalInt brightness;
synchronized (mDataStore) {
brightness = mDataStore.getKeyboardBacklightBrightness(
inputDevice.getDescriptor(), keyboardBacklight.getId());
}
if (brightness.isPresent()) {
if (state != null && brightness.isPresent()) {
int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
int index = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue);
if (index < 0) {
index = Math.min(state.getNumBrightnessChangeSteps(), -(index + 1));
int newLevel = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue);
if (newLevel < 0) {
newLevel = Math.min(state.getNumBrightnessChangeSteps(), -(newLevel + 1));
}
updateBacklightState(inputDevice.getId(), index, false /* isTriggeredByKeyPress */);
state.setBrightnessLevel(newLevel);
if (DEBUG) {
Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
}
@@ -260,6 +319,16 @@ final class KeyboardBacklightController implements
} else {
handleUserInactivity();
}
updateAmbientLightListener();
}
@VisibleForTesting
public void handleAmbientLightValueChanged(int brightnessValue) {
mAmbientBacklightValue = brightnessValue;
for (int i = 0; i < mKeyboardBacklights.size(); i++) {
KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
state.onAmbientBacklightValueChanged();
}
}
private boolean handleMessage(Message msg) {
@@ -292,12 +361,14 @@ final class KeyboardBacklightController implements
@Override
public void onInputDeviceAdded(int deviceId) {
onInputDeviceChanged(deviceId);
updateAmbientLightListener();
}
@VisibleForTesting
@Override
public void onInputDeviceRemoved(int deviceId) {
mKeyboardBacklights.remove(deviceId);
updateAmbientLightListener();
}
@VisibleForTesting
@@ -318,7 +389,7 @@ final class KeyboardBacklightController implements
}
// The keyboard backlight was added or changed.
mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(deviceId, keyboardBacklight));
restoreBacklightBrightness(inputDevice, keyboardBacklight);
maybeRestoreBacklightBrightness(inputDevice, keyboardBacklight);
}
private InputDevice getInputDevice(int deviceId) {
@@ -379,30 +450,6 @@ final class KeyboardBacklightController implements
}
}
private void updateBacklightState(int deviceId, int brightnessLevel,
boolean isTriggeredByKeyPress) {
KeyboardBacklightState state = mKeyboardBacklights.get(deviceId);
if (state == null) {
return;
}
state.setBrightnessLevel(brightnessLevel);
synchronized (mKeyboardBacklightListenerRecords) {
for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
IKeyboardBacklightState callbackState = new IKeyboardBacklightState();
callbackState.brightnessLevel = brightnessLevel;
callbackState.maxBrightnessLevel = state.getNumBrightnessChangeSteps();
mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
deviceId, callbackState, isTriggeredByKeyPress);
}
}
if (DEBUG) {
Slog.d(TAG, "Changing state from " + state.mBrightnessLevel + " to " + brightnessLevel);
}
}
private void onKeyboardBacklightListenerDied(int pid) {
synchronized (mKeyboardBacklightListenerRecords) {
mKeyboardBacklightListenerRecords.remove(pid);
@@ -420,6 +467,25 @@ final class KeyboardBacklightController implements
}
}
private void updateAmbientLightListener() {
if (!InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
return;
}
boolean needToListenAmbientLightSensor = false;
for (int i = 0; i < mKeyboardBacklights.size(); i++) {
needToListenAmbientLightSensor |= mKeyboardBacklights.valueAt(i).mUseAmbientController;
}
needToListenAmbientLightSensor &= mIsInteractive;
if (needToListenAmbientLightSensor && mAmbientListener == null) {
mAmbientListener = this::handleAmbientLightValueChanged;
mAmbientController.registerAmbientBacklightListener(mAmbientListener);
}
if (!needToListenAmbientLightSensor && mAmbientListener != null) {
mAmbientController.unregisterAmbientBacklightListener(mAmbientListener);
mAmbientListener = null;
}
}
private static boolean isValidBacklightNodePath(String devPath) {
if (TextUtils.isEmpty(devPath)) {
return false;
@@ -485,6 +551,8 @@ final class KeyboardBacklightController implements
private int mBrightnessLevel;
private ValueAnimator mAnimator;
private final int[] mBrightnessValueForLevel;
private boolean mUseAmbientController =
InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled();
KeyboardBacklightState(int deviceId, Light light) {
mDeviceId = deviceId;
@@ -525,15 +593,25 @@ final class KeyboardBacklightController implements
}
private void onBacklightStateChanged() {
setBacklightValue(mIsBacklightOn ? mBrightnessValueForLevel[mBrightnessLevel] : 0);
int toValue = mUseAmbientController ? mAmbientBacklightValue
: mBrightnessValueForLevel[mBrightnessLevel];
setBacklightValue(mIsBacklightOn ? toValue : 0);
}
private void setBrightnessLevel(int brightnessLevel) {
// Once we manually set level, disregard ambient light controller
mUseAmbientController = false;
if (mIsBacklightOn) {
setBacklightValue(mBrightnessValueForLevel[brightnessLevel]);
}
mBrightnessLevel = brightnessLevel;
}
private void onAmbientBacklightValueChanged() {
if (mIsBacklightOn && mUseAmbientController) {
setBacklightValue(mAmbientBacklightValue);
}
}
private void cancelAnimation() {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();

View File

@@ -0,0 +1,355 @@
/*
* 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 FrameworksServicesTests: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)))
}
}

View File

@@ -37,6 +37,8 @@ import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANG
import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
@@ -154,7 +156,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightIncrementDecrement() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = false
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -168,7 +174,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardWithoutBacklight() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = false
).use {
val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
@@ -182,7 +192,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardWithMultipleLight() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = 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)
@@ -204,7 +218,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testRestoreBacklightOnInputDeviceAdded() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = false
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -233,7 +251,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testRestoreBacklightOnInputDeviceChanged() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = false
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -265,7 +287,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklight_registerUnregisterListener() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = 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
@@ -314,7 +340,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklight_userActivity() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = false
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -346,7 +376,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklight_displayOnOff() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = false
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -436,7 +470,11 @@ class KeyboardBacklightControllerTests {
@Test
@UiThreadTest
fun testKeyboardBacklightAnimation_onChangeLevels() {
KeyboardBacklightFlags(animationEnabled = true, customLevelsEnabled = false).use {
KeyboardBacklightFlags(
animationEnabled = true,
customLevelsEnabled = false,
ambientControlEnabled = false
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -459,7 +497,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightPreferredLevels() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = true,
ambientControlEnabled = false
).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,
@@ -475,7 +517,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = true,
ambientControlEnabled = false
).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,
@@ -491,7 +537,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = true,
ambientControlEnabled = false
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val suggestedLevels = intArrayOf(22, 63, 135, 196)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
@@ -508,7 +558,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() {
KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = true,
ambientControlEnabled = false
).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,
@@ -523,10 +577,148 @@ class KeyboardBacklightControllerTests {
}
}
@Test
fun testAmbientBacklightControl_doesntRestoreBacklightLevel() {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = true
).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))
dataStore.setKeyboardBacklightBrightness(
keyboardWithBacklight.descriptor,
LIGHT_ID,
DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1]
)
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
keyboardBacklightController.notifyUserActivity()
testLooper.dispatchNext()
assertNull(
"Keyboard backlight level should not be restored to the saved level",
lightColorMap[LIGHT_ID]
)
}
}
@Test
fun testAmbientBacklightControl_doesntBackupBacklightLevel() {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = true
).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)
incrementKeyboardBacklight(DEVICE_ID)
assertFalse(
"Light value should not be backed up if ambient control is enabled",
dataStore.getKeyboardBacklightBrightness(
keyboardWithBacklight.descriptor, LIGHT_ID
).isPresent
)
}
}
@Test
fun testAmbientBacklightControl_incrementLevel_afterAmbientChange() {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = true
).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)
sendAmbientBacklightValue(1)
assertEquals(
"Light value should be changed to ambient provided value",
Color.argb(1, 0, 0, 0),
lightColorMap[LIGHT_ID]
)
incrementKeyboardBacklight(DEVICE_ID)
assertEquals(
"Light value for level after increment post Ambient change is mismatched",
Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
lightColorMap[LIGHT_ID]
)
}
}
@Test
fun testAmbientBacklightControl_decrementLevel_afterAmbientChange() {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = true
).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)
sendAmbientBacklightValue(254)
assertEquals(
"Light value should be changed to ambient provided value",
Color.argb(254, 0, 0, 0),
lightColorMap[LIGHT_ID]
)
decrementKeyboardBacklight(DEVICE_ID)
val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
assertEquals(
"Light value for level after decrement post Ambient change is mismatched",
Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
lightColorMap[LIGHT_ID]
)
}
}
@Test
fun testAmbientBacklightControl_ambientChanges_afterManualChange() {
KeyboardBacklightFlags(
animationEnabled = false,
customLevelsEnabled = false,
ambientControlEnabled = true
).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)
incrementKeyboardBacklight(DEVICE_ID)
assertEquals(
"Light value should be changed to the first level",
Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
lightColorMap[LIGHT_ID]
)
sendAmbientBacklightValue(100)
assertNotEquals(
"Light value should not change based on ambient changes after manual changes",
Color.argb(100, 0, 0, 0),
lightColorMap[LIGHT_ID]
)
}
}
private fun assertIncrementDecrementForLevels(
device: InputDevice,
light: Light,
expectedLevels: IntArray
device: InputDevice,
light: Light,
expectedLevels: IntArray
) {
val deviceId = device.id
val lightId = light.id
@@ -612,6 +804,12 @@ class KeyboardBacklightControllerTests {
testLooper.dispatchAll()
}
private fun sendAmbientBacklightValue(brightnessValue: Int) {
keyboardBacklightController.handleAmbientLightValueChanged(brightnessValue)
keyboardBacklightController.notifyUserActivity()
testLooper.dispatchAll()
}
class KeyboardBacklightState(
val deviceId: Int,
val brightnessLevel: Int,
@@ -620,12 +818,15 @@ class KeyboardBacklightControllerTests {
)
private inner class KeyboardBacklightFlags constructor(
animationEnabled: Boolean,
customLevelsEnabled: Boolean
animationEnabled: Boolean,
customLevelsEnabled: Boolean,
ambientControlEnabled: Boolean
) : AutoCloseable {
init {
InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled)
InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled)
InputFeatureFlagProvider
.setAmbientKeyboardBacklightControlEnabled(ambientControlEnabled)
}
override fun close() {