Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9fe4e91e authored by Santos Cordon's avatar Santos Cordon Committed by android-build-merger
Browse files

Merge "Add Display White Balance tests." into qt-dev

am: 93b56d70

Change-Id: I765329471120a50d4b769f11d46b4f97443f37e2
parents 87557d8b 93b56d70
Loading
Loading
Loading
Loading
+19 −4
Original line number Diff line number Diff line
@@ -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
+125 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+193 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}