summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2019-05-10 12:22:37 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-05-10 12:22:37 +0000
commit93b56d70dd9f40e090faf7a8afe0ba0018d80996 (patch)
tree8d06bbe10d0dd406f8925182c0b6d48bb0a3eab7
parentdffca3a6fa1552a9b5b389f35799b314a053024f (diff)
parentceb0ae15eb7b3a054efcb02e6dbe817ed7d56f59 (diff)
Merge "Add Display White Balance tests." into qt-dev
-rw-r--r--services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterTest.java125
-rw-r--r--services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java193
3 files changed, 337 insertions, 4 deletions
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
index 449f115ad3e3..6ff2b09988e6 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
@@ -22,6 +22,8 @@ import android.hardware.SensorManager;
import android.os.Handler;
import android.util.TypedValue;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* The DisplayWhiteBalanceFactory creates and configures an DisplayWhiteBalanceController.
*/
@@ -40,7 +42,7 @@ public class DisplayWhiteBalanceFactory {
* @param resources
* The resources used to configure the various components.
*
- * @return An DisplayWhiteBalanceController.
+ * @return A DisplayWhiteBalanceController.
*
* @throws NullPointerException
* - handler is null;
@@ -83,14 +85,23 @@ public class DisplayWhiteBalanceFactory {
// Instantiation is disabled.
private DisplayWhiteBalanceFactory() { }
- private static AmbientSensor.AmbientBrightnessSensor createBrightnessSensor(Handler handler,
+ /**
+ * Creates a brightness sensor instance to redirect sensor data to callbacks.
+ */
+ @VisibleForTesting
+ public static AmbientSensor.AmbientBrightnessSensor createBrightnessSensor(Handler handler,
SensorManager sensorManager, Resources resources) {
final int rate = resources.getInteger(
com.android.internal.R.integer.config_displayWhiteBalanceBrightnessSensorRate);
return new AmbientSensor.AmbientBrightnessSensor(handler, sensorManager, rate);
}
- private static AmbientFilter createBrightnessFilter(Resources resources) {
+ /**
+ * Creates a BrightnessFilter which functions as a weighted moving average buffer for recent
+ * brightness values.
+ */
+ @VisibleForTesting
+ static AmbientFilter createBrightnessFilter(Resources resources) {
final int horizon = resources.getInteger(
com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon);
final float intercept = getFloat(resources,
@@ -104,7 +115,11 @@ public class DisplayWhiteBalanceFactory {
}
- private static AmbientSensor.AmbientColorTemperatureSensor createColorTemperatureSensor(
+ /**
+ * Creates an ambient color sensor instance to redirect sensor data to callbacks.
+ */
+ @VisibleForTesting
+ public static AmbientSensor.AmbientColorTemperatureSensor createColorTemperatureSensor(
Handler handler, SensorManager sensorManager, Resources resources) {
final String name = resources.getString(
com.android.internal.R.string
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterTest.java
new file mode 100644
index 000000000000..78164939aa49
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AmbientFilterTest {
+ private ContextWrapper mContextSpy;
+ private Resources mResourcesSpy;
+
+ @Before
+ public void setUp() throws Exception {
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ mResourcesSpy = spy(mContextSpy.getResources());
+ when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+ }
+
+ @Test
+ public void testBrightnessFilter_ZeroIntercept() throws Exception {
+ final int horizon = 5 * 1000;
+ final int time_start = 30 * 1000;
+ final float intercept = 0.0f;
+ final int prediction_time = 100; // Hardcoded in AmbientFilter: prediction of how long the
+ // latest prediction will last before a new prediction.
+ setMockValues(mResourcesSpy, horizon, intercept);
+ AmbientFilter filter = DisplayWhiteBalanceFactory.createBrightnessFilter(mResourcesSpy);
+
+ // Add first value and verify
+ filter.addValue(time_start, 30);
+ assertEquals(30, filter.getEstimate(time_start + prediction_time), 0.001);
+
+ // Add second value and verify that they are being averaged:
+ filter.addValue(time_start + prediction_time, 40);
+ // We check immediately after the value is added to verify that the weight of the
+ // prediction time is being correctly applied to the recent value correctly.
+ // In this case (time is in seconds so 100ms = 0.1s):
+ // weight 1 (w1) = (0.5*0.1^2 - 0.5*0^2) = 0.005
+ // weight 2 (w2) = (0.5*0.2^2 - 0.5*0.1^2) = 0.015
+ // w_t = w1 + w2 = 0.02
+ // total = w1 * 30 + w2 * 40 = 0.75
+ // estimate = total / w_t = 0.75 / 0.02 = 37.5
+ assertEquals(37.5, filter.getEstimate(time_start + prediction_time), 0.001);
+
+ // Add a third value to push the first value off of the buffer.
+ filter.addValue(time_start + horizon + prediction_time, 50);
+ assertEquals(40.38846f, filter.getEstimate(time_start + horizon + prediction_time), 0.001);
+ }
+
+ @Test
+ public void testBrightnessFilter_WithIntercept() throws Exception {
+ final int horizon = 5 * 1000;
+ final int time_start = 30 * 1000;
+ final float intercept = 10f;
+ final int prediction_time = 100;
+
+ setMockValues(mResourcesSpy, horizon, intercept);
+ AmbientFilter filter = DisplayWhiteBalanceFactory.createBrightnessFilter(mResourcesSpy);
+
+ // Add first value and verify
+ filter.addValue(time_start, 30);
+ assertEquals(30, filter.getEstimate(time_start + prediction_time), 0.001);
+
+ // Add second value and verify that they are being averaged:
+ filter.addValue(time_start + prediction_time, 40);
+ // We check immediately after the value is added to verify that the weight of the
+ // prediction time is being correctly applied to the recent value correctly.
+ // In this case (time is in seconds so 100ms = 0.1s):
+ // weight 1 (w1) = (0.5*0.1^2 + 0.1*100) - (0.5*0^2 + 0*100) = 1.005
+ // weight 2 (w2) = (0.5*0.2^2 + 0.2*100) - (0.5*0.1^2 + 0.1*100) = 1.015
+ // w_t = w1 + w2 = 2.02
+ // total = w1 * 30 + w2 * 40 = 70.75
+ // estimate = total / w_t = 70.75 / 2.02 = 35.024752475
+ assertEquals(35.02475f, filter.getEstimate(time_start + prediction_time), 0.001);
+
+ // Add a third value to push the first value off of the buffer.
+ filter.addValue(time_start + horizon + prediction_time, 50);
+ assertEquals(40.23513f, filter.getEstimate(time_start + horizon + prediction_time), 0.001);
+ }
+
+ private void setMockValues(Resources resources, int horizon, float intercept) {
+ doAnswer(invocation -> {
+ TypedValue value = (TypedValue) invocation.getArguments()[1];
+ value.type = TypedValue.TYPE_FLOAT;
+ value.data = Float.floatToRawIntBits(intercept);
+ return null;
+ }).when(mResourcesSpy).getValue(
+ eq(com.android.internal.R.dimen
+ .config_displayWhiteBalanceBrightnessFilterIntercept),
+ any(TypedValue.class), eq(true));
+ when(mResourcesSpy.getInteger(
+ com.android.internal.R.integer
+ .config_displayWhiteBalanceBrightnessFilterHorizon)).thenReturn(horizon);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
new file mode 100644
index 000000000000..6ff4f3b22b9c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+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.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.display.whitebalance.AmbientSensor.AmbientBrightnessSensor;
+import com.android.server.display.whitebalance.AmbientSensor.AmbientColorTemperatureSensor;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class AmbientSensorTest {
+ private static final int AMBIENT_COLOR_TYPE = 20705;
+ private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
+
+ private Handler mHandler = new Handler(Looper.getMainLooper());
+ private Sensor mLightSensor;
+ private Sensor mAmbientColorSensor;
+ private ContextWrapper mContextSpy;
+ private Resources mResourcesSpy;
+
+ @Mock private SensorManager mSensorManagerMock;
+
+ @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);
+ }
+
+ @Test
+ public void testAmbientBrightnessSensorCallback_NoCallbacks() throws Exception {
+ when(mSensorManagerMock.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mLightSensor);
+ AmbientBrightnessSensor abs = DisplayWhiteBalanceFactory.createBrightnessSensor(
+ mHandler, mSensorManagerMock, InstrumentationRegistry.getContext().getResources());
+
+ abs.setCallbacks(null);
+ abs.setEnabled(true);
+ ArgumentCaptor<SensorEventListener> captor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManagerMock).registerListener(captor.capture(), isA(Sensor.class), anyInt(),
+ isA(Handler.class));
+
+ // There should be no issues when we callback the listener, even if there is no callback
+ // set.
+ SensorEventListener listener = captor.getValue();
+ listener.onSensorChanged(createSensorEvent(mLightSensor, 100));
+ }
+
+ @Test
+ public void testAmbientBrightnessSensorCallback_CallbacksCalled() throws Exception {
+ final int luxValue = 83;
+ when(mSensorManagerMock.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mLightSensor);
+ AmbientBrightnessSensor abs = DisplayWhiteBalanceFactory.createBrightnessSensor(
+ mHandler, mSensorManagerMock, InstrumentationRegistry.getContext().getResources());
+
+ final int[] luxReturned = new int[] { -1 };
+ final CountDownLatch changeSignal = new CountDownLatch(1);
+ abs.setCallbacks(new AmbientBrightnessSensor.Callbacks() {
+ @Override
+ public void onAmbientBrightnessChanged(float value) {
+ luxReturned[0] = (int) value;
+ changeSignal.countDown();
+ }
+ });
+
+ abs.setEnabled(true);
+ ArgumentCaptor<SensorEventListener> captor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManagerMock).registerListener(captor.capture(), eq(mLightSensor),
+ anyInt(), eq(mHandler));
+ SensorEventListener listener = captor.getValue();
+ listener.onSensorChanged(createSensorEvent(mLightSensor, luxValue));
+ assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
+ assertEquals(luxValue, luxReturned[0]);
+ }
+
+ @Test
+ public void testAmbientColorTemperatureSensorCallback_CallbacksCalled() throws Exception {
+ final int colorTempValue = 79;
+ final List<Sensor> sensorList = ImmutableList.of(mLightSensor, mAmbientColorSensor);
+ when(mSensorManagerMock.getSensorList(Sensor.TYPE_ALL)).thenReturn(sensorList);
+ when(mResourcesSpy.getString(
+ com.android.internal.R.string.config_displayWhiteBalanceColorTemperatureSensorName))
+ .thenReturn(AMBIENT_COLOR_TYPE_STR);
+
+ AmbientColorTemperatureSensor abs = DisplayWhiteBalanceFactory.createColorTemperatureSensor(
+ mHandler, mSensorManagerMock, mResourcesSpy);
+
+ final int[] colorTempReturned = new int[] { -1 };
+ final CountDownLatch changeSignal = new CountDownLatch(1);
+ abs.setCallbacks(new AmbientColorTemperatureSensor.Callbacks() {
+ @Override
+ public void onAmbientColorTemperatureChanged(float value) {
+ colorTempReturned[0] = (int) value;
+ changeSignal.countDown();
+ }
+ });
+
+ abs.setEnabled(true);
+ ArgumentCaptor<SensorEventListener> captor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManagerMock).registerListener(captor.capture(), eq(mAmbientColorSensor),
+ anyInt(), eq(mHandler));
+ SensorEventListener listener = captor.getValue();
+ listener.onSensorChanged(createSensorEvent(mAmbientColorSensor, colorTempValue));
+ assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
+ assertEquals(colorTempValue, colorTempReturned[0]);
+ }
+
+ private SensorEvent createSensorEvent(Sensor sensor, int lux) throws Exception {
+ final Constructor<SensorEvent> constructor =
+ SensorEvent.class.getDeclaredConstructor(int.class);
+ constructor.setAccessible(true);
+ final SensorEvent event = constructor.newInstance(1);
+ event.sensor = sensor;
+ event.values[0] = lux;
+ event.timestamp = SystemClock.elapsedRealtimeNanos();
+ return event;
+ }
+
+
+ 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;
+ }
+}