Merge "Interpolate low light white balance" into qt-r1-dev

am: 5622e1bff7

Change-Id: I18edab10bf4af20e3e6b4d8a21d7694f48e17686
This commit is contained in:
Anthony Han
2019-06-11 15:11:16 -07:00
committed by android-build-merger
6 changed files with 376 additions and 28 deletions

View File

@@ -4034,9 +4034,28 @@
</array>
<!-- See DisplayWhiteBalanceController.
The ambient brightness threshold (in lux) beneath which we fall back to a fixed ambient
color temperature. -->
<item name="config_displayWhiteBalanceLowLightAmbientBrightnessThreshold" format="float" type="dimen">10.0</item>
A float array containing a list of ambient brightnesses, in Lux. This array,
together with config_displayWhiteBalanceLowLightAmbientBiases, is used to generate a
lookup table used in DisplayWhiteBalanceController. This lookup table is used to map
ambient brightness readings to a bias, where the bias is used to linearly interpolate
between ambient color temperature and
config_displayWhiteBalanceLowLightAmbientColorTemperature.
This table is optional. If used, this array must,
1) Contain at least two entries
2) Be the same length as config_displayWhiteBalanceLowLightAmbientBiases. -->
<array name ="config_displayWhiteBalanceLowLightAmbientBrightnesses">
<item>10.0</item>
<item>10.0</item>
</array>
<!-- See DisplayWhiteBalanceController.
An array containing a list of biases. See
config_displayWhiteBalanceLowLightAmbientBrightnesses for additional details.
This array must be in the range of [0.0, 1.0]. -->
<array name ="config_displayWhiteBalanceLowLightAmbientBiases">
<item>0.0</item>
<item>1.0</item>
</array>
<!-- See DisplayWhiteBalanceController.
The ambient color temperature (in cct) to which we fall back when the ambient brightness

View File

@@ -3749,7 +3749,8 @@
<java-symbol type="array" name="config_displayWhiteBalanceBaseThresholds" />
<java-symbol type="array" name="config_displayWhiteBalanceIncreaseThresholds" />
<java-symbol type="array" name="config_displayWhiteBalanceDecreaseThresholds" />
<java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientBrightnessThreshold" />
<java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBrightnesses" />
<java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBiases" />
<java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientColorTemperature" />
<java-symbol type="array" name="config_displayWhiteBalanceAmbientColorTemperatures" />
<java-symbol type="array" name="config_displayWhiteBalanceDisplayColorTemperatures" />

View File

@@ -18,6 +18,7 @@ package com.android.server.display.whitebalance;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.utils.RollingBuffer;
import java.io.PrintWriter;
@@ -155,7 +156,8 @@ abstract class AmbientFilter {
/**
* A weighted average prioritising recent changes.
*/
static class WeightedMovingAverageAmbientFilter extends AmbientFilter {
@VisibleForTesting
public static class WeightedMovingAverageAmbientFilter extends AmbientFilter {
// How long the latest ambient value change is predicted to last.
private static final int PREDICTION_TIME = 100; // Milliseconds

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.util.Slog;
import android.util.Spline;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
@@ -53,18 +54,21 @@ public class DisplayWhiteBalanceController implements
private Callbacks mCallbacks;
private AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
private AmbientFilter mBrightnessFilter;
@VisibleForTesting
AmbientFilter mBrightnessFilter;
private AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
private AmbientFilter mColorTemperatureFilter;
@VisibleForTesting
AmbientFilter mColorTemperatureFilter;
private DisplayWhiteBalanceThrottler mThrottler;
// When the brightness drops below a certain threshold, it affects the color temperature
// accuracy, so we fall back to a fixed ambient color temperature.
private final float mLowLightAmbientBrightnessThreshold;
private final float mLowLightAmbientColorTemperature;
private float mAmbientColorTemperature;
private float mPendingAmbientColorTemperature;
@VisibleForTesting
float mPendingAmbientColorTemperature;
private float mLastAmbientColorTemperature;
private ColorDisplayServiceInternal mColorDisplayServiceInternal;
@@ -79,6 +83,9 @@ public class DisplayWhiteBalanceController implements
// A piecewise linear relationship between ambient and display color temperatures.
private Spline.LinearSpline mAmbientToDisplayColorTemperatureSpline;
// A piecewise linear relationship between low light brightness and low light bias.
private Spline.LinearSpline mLowLightAmbientBrightnessToBiasSpline;
/**
* @param brightnessSensor
* The sensor used to detect changes in the ambient brightness.
@@ -119,7 +126,8 @@ public class DisplayWhiteBalanceController implements
@NonNull AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor,
@NonNull AmbientFilter colorTemperatureFilter,
@NonNull DisplayWhiteBalanceThrottler throttler,
float lowLightAmbientBrightnessThreshold, float lowLightAmbientColorTemperature,
float[] lowLightAmbientBrightnesses, float[] lowLightAmbientBiases,
float lowLightAmbientColorTemperature,
float[] ambientColorTemperatures, float[] displayColorTemperatures) {
validateArguments(brightnessSensor, brightnessFilter, colorTemperatureSensor,
colorTemperatureFilter, throttler);
@@ -131,7 +139,6 @@ public class DisplayWhiteBalanceController implements
mColorTemperatureSensor = colorTemperatureSensor;
mColorTemperatureFilter = colorTemperatureFilter;
mThrottler = throttler;
mLowLightAmbientBrightnessThreshold = lowLightAmbientBrightnessThreshold;
mLowLightAmbientColorTemperature = lowLightAmbientColorTemperature;
mAmbientColorTemperature = -1.0f;
mPendingAmbientColorTemperature = -1.0f;
@@ -139,10 +146,28 @@ public class DisplayWhiteBalanceController implements
mAmbientColorTemperatureHistory = new History(HISTORY_SIZE);
mAmbientColorTemperatureOverride = -1.0f;
try {
mLowLightAmbientBrightnessToBiasSpline = new Spline.LinearSpline(
lowLightAmbientBrightnesses, lowLightAmbientBiases);
} catch (Exception e) {
Slog.e(TAG, "failed to create low light ambient brightness to bias spline.", e);
mLowLightAmbientBrightnessToBiasSpline = null;
}
if (mLowLightAmbientBrightnessToBiasSpline != null) {
if (mLowLightAmbientBrightnessToBiasSpline.interpolate(0.0f) != 0.0f ||
mLowLightAmbientBrightnessToBiasSpline.interpolate(Float.POSITIVE_INFINITY)
!= 1.0f) {
Slog.d(TAG, "invalid low light ambient brightness to bias spline, "
+ "bias must begin at 0.0 and end at 1.0");
mLowLightAmbientBrightnessToBiasSpline = null;
}
}
try {
mAmbientToDisplayColorTemperatureSpline = new Spline.LinearSpline(
ambientColorTemperatures, displayColorTemperatures);
} catch (Exception e) {
Slog.e(TAG, "failed to create ambient to display color temperature spline.", e);
mAmbientToDisplayColorTemperatureSpline = null;
}
@@ -238,8 +263,6 @@ public class DisplayWhiteBalanceController implements
mColorTemperatureSensor.dump(writer);
mColorTemperatureFilter.dump(writer);
mThrottler.dump(writer);
writer.println(" mLowLightAmbientBrightnessThreshold="
+ mLowLightAmbientBrightnessThreshold);
writer.println(" mLowLightAmbientColorTemperature=" + mLowLightAmbientColorTemperature);
writer.println(" mAmbientColorTemperature=" + mAmbientColorTemperature);
writer.println(" mPendingAmbientColorTemperature=" + mPendingAmbientColorTemperature);
@@ -248,6 +271,8 @@ public class DisplayWhiteBalanceController implements
writer.println(" mAmbientColorTemperatureOverride=" + mAmbientColorTemperatureOverride);
writer.println(" mAmbientToDisplayColorTemperatureSpline="
+ mAmbientToDisplayColorTemperatureSpline);
writer.println(" mLowLightAmbientBrightnessToBiasSpline="
+ mLowLightAmbientBrightnessToBiasSpline);
}
@Override // AmbientSensor.AmbientBrightnessSensor.Callbacks
@@ -276,15 +301,13 @@ public class DisplayWhiteBalanceController implements
mAmbientToDisplayColorTemperatureSpline.interpolate(ambientColorTemperature);
}
final float ambientBrightness = mBrightnessFilter.getEstimate(time);
if (ambientBrightness < mLowLightAmbientBrightnessThreshold) {
if (mLoggingEnabled) {
Slog.d(TAG, "low light ambient brightness: " + ambientBrightness + " < "
+ mLowLightAmbientBrightnessThreshold
+ ", falling back to fixed ambient color temperature: "
+ ambientColorTemperature + " => " + mLowLightAmbientColorTemperature);
}
ambientColorTemperature = mLowLightAmbientColorTemperature;
float ambientBrightness = mBrightnessFilter.getEstimate(time);
if (mLowLightAmbientBrightnessToBiasSpline != null) {
float bias = mLowLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
ambientColorTemperature =
bias * ambientColorTemperature + (1.0f - bias)
* mLowLightAmbientColorTemperature;
}
if (mAmbientColorTemperatureOverride != -1.0f) {

View File

@@ -63,9 +63,12 @@ public class DisplayWhiteBalanceFactory {
createColorTemperatureSensor(handler, sensorManager, resources);
final AmbientFilter colorTemperatureFilter = createColorTemperatureFilter(resources);
final DisplayWhiteBalanceThrottler throttler = createThrottler(resources);
final float lowLightAmbientBrightnessThreshold = getFloat(resources,
com.android.internal.R.dimen
.config_displayWhiteBalanceLowLightAmbientBrightnessThreshold);
final float[] displayWhiteBalanceLowLightAmbientBrightnesses = getFloatArray(resources,
com.android.internal.R.array
.config_displayWhiteBalanceLowLightAmbientBrightnesses);
final float[] displayWhiteBalanceLowLightAmbientBiases = getFloatArray(resources,
com.android.internal.R.array
.config_displayWhiteBalanceLowLightAmbientBiases);
final float lowLightAmbientColorTemperature = getFloat(resources,
com.android.internal.R.dimen
.config_displayWhiteBalanceLowLightAmbientColorTemperature);
@@ -75,7 +78,8 @@ public class DisplayWhiteBalanceFactory {
com.android.internal.R.array.config_displayWhiteBalanceDisplayColorTemperatures);
final DisplayWhiteBalanceController controller = new DisplayWhiteBalanceController(
brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter,
throttler, lowLightAmbientBrightnessThreshold, lowLightAmbientColorTemperature,
throttler, displayWhiteBalanceLowLightAmbientBrightnesses,
displayWhiteBalanceLowLightAmbientBiases, lowLightAmbientColorTemperature,
ambientColorTemperatures, displayColorTempeartures);
brightnessSensor.setCallbacks(controller);
colorTemperatureSensor.setCallbacks(controller);

View File

@@ -0,0 +1,299 @@
/*
* Copyright (C) 2019 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.display.whitebalance;
import com.android.internal.R;
import com.google.common.collect.ImmutableList;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.mockito.Matchers.anyLong;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Looper;
import android.util.TypedValue;
import androidx.test.InstrumentationRegistry;
import org.junit.Before;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
@RunWith(JUnit4.class)
public final class AmbientLuxTest {
private static final int AMBIENT_COLOR_TYPE = 20705;
private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5700;
private Handler mHandler = new Handler(Looper.getMainLooper());
private Sensor mLightSensor;
private Sensor mAmbientColorSensor;
private ContextWrapper mContextSpy;
private Resources mResourcesSpy;
@Mock private SensorManager mSensorManagerMock;
@Mock private TypedArray mBrightnesses;
@Mock private TypedArray mBiases;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mLightSensor = createSensor(Sensor.TYPE_LIGHT, null);
mAmbientColorSensor = createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
when(mSensorManagerMock.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mLightSensor);
final List<Sensor> sensorList = ImmutableList.of(mLightSensor, mAmbientColorSensor);
when(mSensorManagerMock.getSensorList(Sensor.TYPE_ALL)).thenReturn(sensorList);
when(mResourcesSpy.getString(
R.string.config_displayWhiteBalanceColorTemperatureSensorName))
.thenReturn(AMBIENT_COLOR_TYPE_STR);
when(mResourcesSpy.getInteger(
R.integer.config_displayWhiteBalanceDecreaseDebounce))
.thenReturn(0);
when(mResourcesSpy.getInteger(
R.integer.config_displayWhiteBalanceIncreaseDebounce))
.thenReturn(0);
when(mResourcesSpy.getFloat(
R.dimen.config_displayWhiteBalanceLowLightAmbientColorTemperature))
.thenReturn(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE);
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceAmbientColorTemperatures))
.thenReturn(createTypedArray());
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceDisplayColorTemperatures))
.thenReturn(createTypedArray());
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses))
.thenReturn(mBrightnesses);
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceLowLightAmbientBiases))
.thenReturn(mBiases);
}
@Test
public void testNoSpline() throws Exception {
setBrightnesses();
setBiases();
DisplayWhiteBalanceController controller =
DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
final float ambientColorTemperature = 8000.0f;
setEstimatedColorTemperature(controller, ambientColorTemperature);
controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
setEstimatedBrightnessAndUpdate(controller, luxOverride);
assertEquals(controller.mPendingAmbientColorTemperature,
ambientColorTemperature, 0.001);
}
}
@Test
public void testSpline_OneSegment() throws Exception {
final float lowerBrightness = 10.0f;
final float upperBrightness = 50.0f;
setBrightnesses(lowerBrightness, upperBrightness);
setBiases(0.0f, 1.0f);
DisplayWhiteBalanceController controller =
DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
final float ambientColorTemperature = 8000.0f;
setEstimatedColorTemperature(controller, ambientColorTemperature);
controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
setEstimatedBrightnessAndUpdate(controller,
mix(lowerBrightness, upperBrightness, t));
assertEquals(controller.mPendingAmbientColorTemperature,
mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, t), 0.001);
}
setEstimatedBrightnessAndUpdate(controller, 0.0f);
assertEquals(controller.mPendingAmbientColorTemperature,
LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
}
@Test
public void testSpline_TwoSegments() throws Exception {
final float brightness0 = 10.0f;
final float brightness1 = 50.0f;
final float brightness2 = 60.0f;
setBrightnesses(brightness0, brightness1, brightness2);
final float bias0 = 0.0f;
final float bias1 = 0.25f;
final float bias2 = 1.0f;
setBiases(bias0, bias1, bias2);
DisplayWhiteBalanceController controller =
DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
final float ambientColorTemperature = 8000.0f;
setEstimatedColorTemperature(controller, ambientColorTemperature);
controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
float luxOverride = mix(brightness0, brightness1, t);
setEstimatedBrightnessAndUpdate(controller, luxOverride);
float bias = mix(bias0, bias1, t);
assertEquals(controller.mPendingAmbientColorTemperature,
mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
}
for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
float luxOverride = mix(brightness1, brightness2, t);
setEstimatedBrightnessAndUpdate(controller, luxOverride);
float bias = mix(bias1, bias2, t);
assertEquals(controller.mPendingAmbientColorTemperature,
mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
}
setEstimatedBrightnessAndUpdate(controller, 0.0f);
assertEquals(controller.mPendingAmbientColorTemperature,
LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
setEstimatedBrightnessAndUpdate(controller, brightness2 + 1.0f);
assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
}
@Test
public void testSpline_VerticalSegment() throws Exception {
final float lowerBrightness = 10.0f;
final float upperBrightness = 10.0f;
setBrightnesses(lowerBrightness, upperBrightness);
setBiases(0.0f, 1.0f);
DisplayWhiteBalanceController controller =
DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
final float ambientColorTemperature = 8000.0f;
setEstimatedColorTemperature(controller, ambientColorTemperature);
controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
setEstimatedBrightnessAndUpdate(controller, 0.0f);
assertEquals(controller.mPendingAmbientColorTemperature,
LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
}
@Test
public void testSpline_InvalidBiases() throws Exception {
float[][] invalidBrightnesses =
{{10.0f, 1000.0f}, {10.0f, 1000.0f}, {10.0f, 1000.0f}, {10.0f, 1000.0f},
{10.0f, 1000.0f}, {-1.0f, 1.0f}, {-1.0f, 1.0f}};
float[][] invalidBiases =
{{0.0f, 2.0f}, {0.0f, 0.9f}, {0.1f, 1.0f}, {-0.1f, 1.0f},
{0.1f, 1.1f}, {0.0f, 1.0f}, {-2.0f, 1.0f}};
for (int i = 0; i < invalidBrightnesses.length; ++i) {
setBrightnesses(invalidBrightnesses[i][0], invalidBrightnesses[i][1]);
setBiases(invalidBiases[i][0], invalidBiases[i][1]);
DisplayWhiteBalanceController controller =
DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
final float ambientColorTemperature = 8000.0f;
setEstimatedColorTemperature(controller, ambientColorTemperature);
controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
setEstimatedBrightnessAndUpdate(controller, luxOverride);
assertEquals(controller.mPendingAmbientColorTemperature,
ambientColorTemperature, 0.001);
}
}
}
private void setEstimatedColorTemperature(DisplayWhiteBalanceController controller,
float ambientColorTemperature) {
AmbientFilter colorTemperatureFilter = spy(controller.mColorTemperatureFilter);
controller.mColorTemperatureFilter = colorTemperatureFilter;
when(colorTemperatureFilter.getEstimate(anyLong())).thenReturn(ambientColorTemperature);
}
private void setEstimatedBrightnessAndUpdate(DisplayWhiteBalanceController controller,
float brightness) {
when(controller.mBrightnessFilter.getEstimate(anyLong())).thenReturn(brightness);
controller.updateAmbientColorTemperature();
}
private void setBrightnesses(float... vals) {
setFloatArrayResource(mBrightnesses, vals);
}
private void setBiases(float... vals) {
setFloatArrayResource(mBiases, vals);
}
private void setFloatArrayResource(TypedArray array, float[] vals) {
when(array.length()).thenReturn(vals.length);
for (int i = 0; i < vals.length; i++) {
when(array.getFloat(i, Float.NaN)).thenReturn(vals[i]);
}
}
private void setSensorType(Sensor sensor, int type, String strType) throws Exception {
Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
setter.setAccessible(true);
setter.invoke(sensor, type);
if (strType != null) {
Field f = sensor.getClass().getDeclaredField("mStringType");
f.setAccessible(true);
f.set(sensor, strType);
}
}
private Sensor createSensor(int type, String strType) throws Exception {
Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
constr.setAccessible(true);
Sensor sensor = constr.newInstance();
setSensorType(sensor, type, strType);
return sensor;
}
private TypedArray createTypedArray() throws Exception {
TypedArray mockArray = mock(TypedArray.class);
return mockArray;
}
private static float mix(float a, float b, float t) {
return (1.0f - t) * a + t * b;
}
}