在上一篇文章中,对自动背光的流程做了总结,在本篇中,将对自动背光涉及到的一些算法进行分析总结。
AmbientLightRingBuffer类是一个用于存储采集到的光照强度和对应时间点的数据结构。在自动背光控制器中,实例化了两个AmbientLightRingBuffer对象:
//包含所有光照样例的AmbientLightRingBuffer对象
mAmbientLightRingBuffer =
new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon);
//只包含在初始时间范围周期的光照样例的AmbientLightRingBuffer对象
mInitialHorizonAmbientLightRingBuffer =
new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon);
mAmbientLightRingBuffer是包含所有光照样例的AmbientLightRingBuffer对象,mInitialHorizonAmbientLightRingBuffer只包含在初始采光时间范围周期的光照样例的AmbientLightRingBuffer对象,这个初始采光时间范围周期是指当LSensor启用开始计时,到一个采光时间周期,采光时间周期mAmbientLightHorizon则在配置文件中读取:
<integer name="config_autoBrightnessAmbientLightHorizon">10000integer>
下面我们从它的构造方法开始,来分析缓冲区的实现原理和其中包括的算法。
AmbientLightRingBuffer的构造方法如下:
private static final class AmbientLightRingBuffer {
private static final float BUFFER_SLACK = 1.5f;
private float[] mRingLux;
private long[] mRingTime;
private int mCapacity;
//缓冲区中第一个数据位置
private int mStart;
//缓冲区中下一个槽口位置
private int mEnd;
//缓冲区中光照样例的数量
private int mCount;
public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
//缓冲区容量
mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
//存储Lux值的数组
mRingLux = new float[mCapacity];
//存储时间的数组
mRingTime = new long[mCapacity];
}
在构造方法中,初始化了两个数组分别用来存储时间和Lux值,而这两个数组的长度,是根据采集光照样例的时间范围周期 * BUFFER_SLACK/LSensor事件率得到的。其实也可以这样理解:总时间 / 每次上报数据的时间 = 时间周期内上报事件的个数 = 缓冲区大小。
将上报的LSensor数据记录到缓冲区时,使用push()
方法,该方法如下:
public void push(long time, float lux) {
//标记要存储的位置
int next = mEnd;
if (mCount == mCapacity) {
//如果缓冲区已满,则将进行扩容
int newSize = mCapacity * 2;
//初始化两个新的数组,大小为原来数组大小的2倍
float[] newRingLux = new float[newSize];
long[] newRingTime = new long[newSize];
//数组长度减去第一个元素位置=原数组的长度
int length = mCapacity - mStart;
//将旧数组中内容拷贝到新数组中
//将mRingLux数组从mStart位起,拷贝到newRingLux中,放在0位置起,拷贝length个数据
System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
//如果原数组中第一个数据的索引不为0
if (mStart != 0) {
//将mRingLux数组从0位起,拷贝到newRingLux中,放在length位置起,拷贝mStart个数据
//即如果mStart不为0,将mStart前的数据放在了最后
System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
}
mRingLux = newRingLux;
mRingTime = newRingTime;
next = mCapacity;
mCapacity = newSize;
mStart = 0;
}
//记录时间和Lux
mRingTime[next] = time;
mRingLux[next] = lux;
mEnd = next + 1;//下一个槽口
//如果到达数组尾端,则将mEnd置为0,在下一次进入后,由于mCount == mCapacity,因此又会将mEnd置为next+1
if (mEnd == mCapacity) {
mEnd = 0;
}
//记录数+1
mCount++;
}
在这个方法中,会将LSensor上报的值和时间点记录到缓冲区中,此处有两个地方的计算方式如下:
prune()
方法操作过),所以0-mStart之间的记录已被标记为删除,则在拷贝时,将旧数组中0-mStart之间的元素拷贝到新数组中,从length索引开始存放。根据以上两个计算方式,从而形成了类似环的环形缓冲区。
每当LSensor收到上报数据后,会通过push()
方法记录到缓冲区,而对据此次上报时间一个采光时间周期前的数据,则进行删除,该方法如下:
public void prune(long horizon) {
//如果当前缓冲区中无数据,直接返回
if (mCount == 0) {
return;
}
while (mCount > 1) {
//表示第二个有效元素位置
int next = mStart + 1;
//如果第二个元素置位值大于等于容量值,说明整个缓冲区中只有一个元素,索引为mStart.
if (next >= mCapacity) {
next -= mCapacity;
}
//如果第二个有效元素的时间点大于传入的时间,则停止
if (mRingTime[next] > horizon) {
break;
}
//重置第一个有效元素索引
mStart = next;
//数量值-1
mCount -= 1;
}
//如果第一个有效元素时间小于传入的时间,则重置第一个有效元素的时间值
if (mRingTime[mStart] < horizon) {
mRingTime[mStart] = horizon;
}
}
下图简单表示了push()
和prune()
方法调用时缓冲区状态示例:
缓冲区提供了getTime(int index)
和getLux(int index)
方法,根据参数index得到缓冲区中对应的数据,然而,由于缓冲区中数据的存储是根据缓冲区内部的变量mStart标记存储的起始位置,因此,需要将index进行转换,使用offsetOf(int index)
转换,从而得到正确的位置:
private int offsetOf(int index) {
if (index >= mCount || index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
//mStart表示第一个元素的位置
index += mStart;
//如果index >= mCapacity,直接index = index -mCapacity
if (index >= mCapacity) {
index -= mCapacity;
}
return index;
}
}
其他方法比较简单,如下:
//获取Lux值
public float getLux(int index) {
return mRingLux[offsetOf(index)];
}
//获取时间值
public long getTime(int index) {
return mRingTime[offsetOf(index)];
}
//清空缓冲区
public void clear() {
mStart = 0;
mEnd = 0;
mCount = 0;
}
在上一篇文章中分析自动背光流程时,对最终Lux值的计算直接略过了,只提到它是通过calculateAmbientLux()
方法进行计算后,将结果通过setAmbientLux()
方法设置给了代表当前Lux值的全局变量mAmbientLux。因此我们这里对这两个方法进行分析。
calculateAmbientLux()
方法计算Lux值该方法如下:
private float calculateAmbientLux(long now, long horizon) {
final int N = mAmbientLightRingBuffer.size();
if (N == 0) {
Slog.e(TAG, "calculateAmbientLux: No ambient light readings available");
return -1;
}
// Find the first measurement that is just outside of the horizon.
//表示计算lux值时的左边界([endIndex,N-1])
int endIndex = 0;
//当前时间-时长范围,即前horizon秒的时间点
final long horizonStartTime = now - horizon;
//计算左边界,其为大于指定时间的第一个元素的索引值
//为何从i+1开始呢?如从i开始,必然有第一个元素 <=horizonStartTime,从而获得的endIndex大了1.
for (int i = 0; i < N-1; i++) {
if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) {
endIndex++;
} else {
break;
}
}
float sum = 0;
float totalWeight = 0;
//用于计算权重的常亮
long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
for (int i = N - 1; i >= endIndex; i--) {
//得到缓冲区中记录的LSensor事件时间
long eventTime = mAmbientLightRingBuffer.getTime(i);
if (i == endIndex && eventTime < horizonStartTime) {
// If we're at the final value, make sure we only consider the part of the sample
// within our desired horizon.
eventTime = horizonStartTime;
}
//缓存区中记录的时间-当前时间,肯定小于等于零
final long startTime = eventTime - now;
//计算权重
float weight = calculateWeight(startTime, endTime);
//得到缓冲区中记录的Lux值
float lux = mAmbientLightRingBuffer.getLux(i);
//累计每次得到的权重值
totalWeight += weight;
//累计每次得到的(Lux值 * 权重值)
sum += mAmbientLightRingBuffer.getLux(i) * weight;
endTime = startTime;
}
//加权平均
return sum / totalWeight;
}
在以上方法中可以看出,最终的Lux值,是在缓冲区中,时间点在now-horizon ~ now范围中的lux值加权平均获得。如在自动背光打开的情况下,亮屏开启LSensor后,会走calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS)
流程,AMBIENT_LIGHT_SHORT_HORIZON_MILLIS
值为2000,也就是,亮屏后立即调整背光时,使用前2s内的数据进行加权平均得到Lux值,并进一步得到亮度值。
计算权重的公式如下:
private float calculateWeight(long startDelta, long endDelta) {
return weightIntegral(endDelta) - weightIntegral(startDelta);
}
// Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the
// horizon we're looking at and provides a non-linear weighting for light samples.
private float weightIntegral(long x) {
return x * (x * 0.5f + mWeightingIntercept);
}
可以看到计算权重的公式为一个一元二次方式,顶点为0,开口向上,因此,可以保证是权重值一个正值。
setAmbientLux()
设置Lux值当得到Lux值后,通过setAmbientLux()
将Lux值赋给全局变量:
private void setAmbientLux(float lux) {
//将lux赋值给表示当前lux的全局变量mAmbientLux
mAmbientLux = lux;
//环境亮度阈值,用于使屏幕变亮或变暗的Lux值
mBrighteningLuxThreshold = mDynamicHysteresis.getBrighteningThreshold(lux);
mDarkeningLuxThreshold = mDynamicHysteresis.getDarkeningThreshold(lux);
}
在以上方法的最后,通过HysteresisLevel和当前Lux值获取Lux阀值,超过或小于两个阀值时,将会更新屏幕的亮度,这部分会在下面分析到。
当获取到Lux值后,我们来看看是如何计算要显示的亮度的。
在updateAutoBrightness()
方法中,首先根据如下逻辑获得了一个value值:
//根据当前的Lux值得到样条曲线中的value
float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux);
我们要暂时停到这里,来看看mScreenAutoBrightnessSpline这个对象了。
mScreenAutoBrightnessSpline是MonotoneCubicSpline对象,表示一个单调的三次样条曲线,这里我们来看下他的创建过程。
在DisplayPowerController构造方法中:
int[] lux = resources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevels);
int[] screenBrightness = resources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
Spline screenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness);
在以上逻辑中,从config.xml文件中获取了两个数组,这两个数组对于自动背光来说是非常重要,因为他们分别定义了Lux的级别和对应Lux的亮度。从配置文件中看,Lux 始终比screenBrightness少1.
需要注意的是,这些数组值需要厂商各自去根据硬件环境进行配置,在Google原生代码中,这些数组为空。
再来看看createAutoBrightnessSpline()
方法:
private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) {
try {
final int n = brightness.length;
//初始化两个数组,分别存储Lux值和value/255的值
float[] x = new float[n];
float[] y = new float[n];
//将y[0]设置为第0个亮度值
y[0] = normalizeAbsoluteBrightness(brightness[0]);
//将x[i]设置为lux[i-1]元素,y[i]设置为(value/brightness[i])
//由于lux.length = brightness.length -1.故x[0]没有存储元素
for (int i = 1; i < n; i++) {
x[i] = lux[i - 1];
y[i] = normalizeAbsoluteBrightness(brightness[i]);
}
Spline spline = Spline.createSpline(x, y);
return spline;
} catch (IllegalArgumentException ex) {
Slog.e(TAG, "Could not create auto-brightness spline.", ex);
return null;
}
}
在这个方法中,将从配置文件中得到的Lux Level数组和brightness level数组作为参数,并将两数组中的元素复制到了新的数组中,并利用该数组创建Spline对象,继续:
public static Spline createSpline(float[] x, float[] y) {
if (!isStrictlyIncreasing(x)) {
throw new IllegalArgumentException("The control points must all have strictly "
+ "increasing X values.");
}
//是否单调递增
if (isMonotonic(y)) {
return createMonotoneCubicSpline(x, y);
} else {
return createLinearSpline(x, y);
}
}
这个方法中,判断数组x中的值、即Lux值是否严格单调递增,然后进一步执行。根据配置文件,Lux值递增,所以调用下一个方法createMonotoneCubicSpline()
,创建得到MonotoneCubicSpline对象。
public static Spline createMonotoneCubicSpline(float[] x, float[] y) {
return new MonotoneCubicSpline(x, y);
}
紧接着看MonotoneCubicSpline的构造方法:
public static class MonotoneCubicSpline extends Spline {
private float[] mX;
private float[] mY;
private float[] mM;
public MonotoneCubicSpline(float[] x, float[] y) {
if (x == null || y == null || x.length != y.length || x.length < 2) {
throw new IllegalArgumentException("There must be at least two control "
+ "points and the arrays must be of equal length.");
}
final int n = x.length;
//初始化数组,d数组用来存储相邻两点之间的斜率,m数组存储两个相邻d数组中的平均值
float[] d = new float[n - 1]; // could optimize this out
float[] m = new float[n];
// Compute slopes of secant lines between successive points.
//得到每个相邻Lux Level值之间曲线的斜率
for (int i = 0; i < n - 1; i++) {
float h = x[i + 1] - x[i];
if (h <= 0f) {
throw new IllegalArgumentException("The control points must all "
+ "have strictly increasing X values.");
}
d[i] = (y[i + 1] - y[i]) / h;
}
// Initialize the tangents as the average of the secants.
//相邻斜率平均值
m[0] = d[0];
for (int i = 1; i < n - 1; i++) {
m[i] = (d[i - 1] + d[i]) * 0.5f;
}
m[n - 1] = d[n - 2];
// Update the tangents to preserve monotonicity.
for (int i = 0; i < n - 1; i++) {
if (d[i] == 0f) { // successive Y values are equal
m[i] = 0f;
m[i + 1] = 0f;
} else {
float a = m[i] / d[i];
float b = m[i + 1] / d[i];
if (a < 0f || b < 0f) {
throw new IllegalArgumentException("The control points must have "
+ "monotonic Y values.");
}
//a和b平方和的二次方根
float h = (float) Math.hypot(a, b);
if (h > 3f) {
float t = 3f / h;
m[i] *= t;
m[i + 1] *= t;
}
}
}
mX = x;//lux level值
mY = y;//bright level/255的值
mM = m;//平均斜率
}
在以上方法中,涉及的算法目前还不是非常清晰,不过还是可以确定,其成员数组mX、mY、mM分别表示存储Lux Level值、(brightness Level/255)值和由以上两值组成的曲线相邻两点之间的斜率。
现在,让我们回到亮度的计算中,当自动背光控制器中得到当前Lux值时,通过mScreenAutoBrightnessSpline.interpolate(mAmbientLux)
得到一个value值,该方法如下:
@Override
public float interpolate(float x) {
final int n = mX.length;
if (Float.isNaN(x)) {
return x;
}
//当传入Lux值< mX中第一个元素时,使用mY[0]
if (x <= mX[0]) {
return mY[0];
}
//当传入Lux值 > mX中最后一个元素时,使用mY的最后一个元素
if (x >= mX[n - 1]) {
return mY[n - 1];
}
int i = 0;
//遍历Lux值,若恰好相等,则直接return
while (x >= mX[i + 1]) {
i += 1;
if (x == mX[i]) {
return mY[i];
}
}
// Perform cubic Hermite spline interpolation.
//进行三次Hermite样条插值计算
float h = mX[i + 1] - mX[i];
float t = (x - mX[i]) / h;
return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)
+ (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;
}
在以上方法中,复杂的是最后的三次Hermite样条插值计算,这个我目前也不清楚,先不深入分析了,总之从这里就会得到一个值,用value表示。
得到value值后,它并不是最终的亮度值,因为还需要进行一个操作才能得到最终的亮度值。
我们在上一篇文章中说过,当打开自动背光后,拖动亮度条,实际上不是调节的亮度值,而是自动背光调节值mScreenAutoBrightnessAdjustment
,正是通过这个值,和得到的value进行一个简单计算,得到了最终的亮度值:
private void updateAutoBrightness(boolean sendUpdate) {
if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT
&& mScreenAutoBrightnessAdjustment != 0.0f) {
//得到adjGamma值
final float adjGamma = MathUtils.pow(mScreenAutoBrightnessAdjustmentMaxGamma,
Math.min(1.0f, Math.max(-1.0f, -mScreenAutoBrightnessAdjustment)));
gamma *= adjGamma;
}
if (gamma != 1.0f) {//表示gamma发生变化
final float in = value;
//再次计算value = value的gamma次方
value = MathUtils.pow(value, gamma);
}
//新的亮度值=value * 255
int newScreenAutoBrightness =
clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
}
对于亮度计算的算法就分析到这里,这里再对上面提到的两个数组再次进行下说明。
config.xml中的config_autoBrightnessLevels
和config_autoBrightnessLcdBacklightValues
数组对于自动背光来说非常重要,若不配置它,自动背光功能将失效。它们的定义如下:
<integer-array name="config_autoBrightnessLevels">
<item>1item>
<item>40item>
<item>100item>
<item>325item>
<item>600item>
<item>1250item>
<item>2200item>
<item>4000item>
<item>10000item>
integer-array>
<integer-array name="config_autoBrightnessLcdBacklightValues">
<item>11item>
<item>22item>
<item>47item>
<item>61item>
<item>84item>
<item>107item>
<item>154item>
<item>212item>
<item>245item>
<item>255item>
integer-array>
结合以上配置值和流程我们可以知道,假设自动背光不使用调节值mScreenAutoBrightnessAdjustment
,则:
mAmibentLux <= config_autoBrightnessLevels[0]
时,背光值直接采用config_autoBrightnessLcdBacklightValues[0];mAmibentLux >= config_autoBrightnessLevels[size-1]
时,背光值直接采用config_autoBrightnessLcdBacklightValues[size-1];config_autoBrightnessLevels[0] < mAmibentLux <= config_autoBrightnessLevels[size-1]
时,背光参考值通过三次样条插值计算获得。HysteresisLevel是一个计算Lux阀值的帮助类,当Lux值发生变化后,到达何值时更新亮度,就是通过这个帮助类计算的,而在这个类内部计算阀值时,又使用到了配置文件中的三个配置数组。下面就来看看它的实现原理。
在DisplayPowerController中的构造方法中,获得了HysteresisLevel的实例,并作为参数传递给了自动背光控制器,其实例化代码如下:
int[] brightLevels = resources.getIntArray(
com.android.internal.R.array.config_dynamicHysteresisBrightLevels);
int[] darkLevels = resources.getIntArray(
com.android.internal.R.array.config_dynamicHysteresisDarkLevels);
int[] luxLevels = resources.getIntArray(
com.android.internal.R.array.config_dynamicHysteresisLuxLevels);
HysteresisLevels dynamicHysteresis = new HysteresisLevels(
brightLevels, darkLevels, luxLevels);
这三个数组中,前两个数组中的元素分别用于计算明亮Lux阀值、昏暗Lux阀值的比例值,最后一个数组中的元素则用来和当前Lux作比较,得到一个索引值,以确定前两个数组中的元素。这三个数组定义时,前两数组的长度必须比后一个数组长度大1。
进入它的构造方法:
public HysteresisLevels(int[] brightLevels, int[] darkLevels, int[] luxLevels) {
if (brightLevels.length != darkLevels.length || darkLevels.length != luxLevels.length + 1) {
throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
}
mBrightLevels = setArrayFormat(brightLevels, 1000.0f);
mDarkLevels = setArrayFormat(darkLevels, 1000.0f);
mLuxLevels = setArrayFormat(luxLevels, 1.0f);
}
在构造方法中,将三个数组中的各个元素通过setArrayFormat()
方法处理后,分别赋给了三个全局变量,setArrayFormat():
private float[] setArrayFormat(int[] configArray, float divideFactor) {
float[] levelArray = new float[configArray.length];
for (int index = 0; levelArray.length > index; ++index) {
levelArray[index] = (float)configArray[index] / divideFactor;
}
return levelArray;
}
从以上逻辑中也可以看出,该方法只是进行了(float)configArray[index] / divideFactor操作。
通过对HysteresisLevel构造方法的分析,对它有了大致的了解,现在来看看它是如何计算Lux阀值的。
在自动背光控制器中,获取Lux阀值方式如下:
private void setAmbientLux(float lux) {
//将lux赋值给表示当前lux的全局变量mAmbientLux
mAmbientLux = lux;
//环境亮度阈值,用于使屏幕变亮或变暗的Lux值
mBrighteningLuxThreshold = mDynamicHysteresis.getBrighteningThreshold(lux);
mDarkeningLuxThreshold = mDynamicHysteresis.getDarkeningThreshold(lux);
}
那么就来看看以上两个方法:
/**
* Return the brightening hysteresis threshold for the given lux level.
*/
public float getBrighteningThreshold(float lux) {
//将lux和mLuxLevels中的元素进行比较,得到一个索引值index,然后返回mBrightLevels[index]
float brightConstant = getReferenceLevel(lux, mBrightLevels);
//阀值的计算公式
float brightThreshold = lux * (1.0f + brightConstant);
return brightThreshold;
}
/**
* Return the darkening hysteresis threshold for the given lux level.
*/
public float getDarkeningThreshold(float lux) {
//将lux和mLuxLevels中的元素进行比较,得到一个索引值index,然后返回mDarkLevels[index]
float darkConstant = getReferenceLevel(lux, mDarkLevels);
float darkThreshold = lux * (1.0f - darkConstant);
return darkThreshold;
}
在以上方法中,可以看出Lux阀值的计算公式为分别如下:
mBrighteningLuxThreshold = lux * (1.0f + brightConstant);
mDarkeningLuxThreshold = lux * (1.0f - darkConstant);
getReferenceLevel()
方法返回了一个亮度比例值,该方法如下:
private float getReferenceLevel(float lux, float[] referenceLevels) {
int index = 0;
//如果index小于mLuxLevels的长度且当前Lux值大于等于mLuxLevels[index],则index加1.
//当index等于mLuxLevels.length,说明已经lux值大于等于mLuxLevels[length-1]最后一个元素,此时break
//当lux值小于mLuxLevels[index]时,break。
while (mLuxLevels.length > index && lux >= mLuxLevels[index]) {
++index;
}
return referenceLevels[index];
}
在以上方法中,根据当前Lux值和mLuxLevels数组中的元素进行比较,从而确定一个index值,利用这个index值,得到mBrightLevels[index]或mDarkLevels[index],从而套用公式计算阀值。而在这个方法中可以看出,当前Lux值、mLuxLevels、index的关系如下:
条件 | 得到的index值 |
---|---|
lux < mLuxLevels[0] | 0 |
mLuxLevels[n-1] <= lux < mLuxLevels[n]( 0 < n < MAX) | n |
… | … |
lux > mLuxLevels[MAX] | MAX+1 |
一般情况下,对于以上提到的三个数组,并不需要厂商再进行配置,都是使用原生的数据,Google原生提供数据如下:
<integer-array name="config_dynamicHysteresisBrightLevels">
<item>100item>
integer-array>
<integer-array name="config_dynamicHysteresisDarkLevels">
<item>200item>
integer-array>
<integer-array name="config_dynamicHysteresisLuxLevels">
integer-array>
至此,关于自动背光涉及到的一些算法及原理,算是分析了一遍了。虽有些如样条插值等算法未领悟到其目的,不过还是对这个功能有一个较清晰的认识了。
整个自动背光流程时序图如下:
此外,android P中自动背光机制相比Android O有较大的变化,在之后会对Android P进行总结。
以下是自动背光调节中的一些常用配置值:
<integer name="config_autoBrightnessBrighteningLightDebounce">4000integer>
<integer name="config_autoBrightnessDarkeningLightDebounce">8000integer>
//frameworks/base/services/core/java/com/android/server/display/AutomaticBrightnessController.java
private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = true;//false将取消
<bool name="config_autoBrightnessResetAmbientLuxAfterWarmUp">truebool>
<bool name="config_automatic_brightness_available">truebool>