diff options
| author | 2019-05-10 12:22:37 +0000 | |
|---|---|---|
| committer | 2019-05-10 12:22:37 +0000 | |
| commit | 93b56d70dd9f40e090faf7a8afe0ba0018d80996 (patch) | |
| tree | 8d06bbe10d0dd406f8925182c0b6d48bb0a3eab7 | |
| parent | dffca3a6fa1552a9b5b389f35799b314a053024f (diff) | |
| parent | ceb0ae15eb7b3a054efcb02e6dbe817ed7d56f59 (diff) | |
Merge "Add Display White Balance tests." into qt-dev
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; + } +} |