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

Commit 49f933a7 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Clamp brightness thresholds to the maximum brightness."

parents 2877e7a3 64a86279
Loading
Loading
Loading
Loading
+39 −9
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.util.MathUtils;
import android.util.Slog;
import android.util.TimeUtils;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;

@@ -215,7 +216,26 @@ class AutomaticBrightnessController {
    private IActivityTaskManager mActivityTaskManager;
    private PackageManager mPackageManager;

    public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
    private final Injector mInjector;

    AutomaticBrightnessController(Callbacks callbacks, Looper looper,
            SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
            int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
            int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
            long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
            HysteresisLevels ambientBrightnessThresholds,
            HysteresisLevels screenBrightnessThresholds, long shortTermModelTimeout,
            PackageManager packageManager) {
        this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
                lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
                lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
                darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
                ambientBrightnessThresholds, screenBrightnessThresholds, shortTermModelTimeout,
                packageManager);
    }

    @VisibleForTesting
    AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper,
            SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
            int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
            int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
@@ -223,6 +243,7 @@ class AutomaticBrightnessController {
            HysteresisLevels ambientBrightnessThresholds,
            HysteresisLevels screenBrightnessThresholds, long shortTermModelTimeout,
            PackageManager packageManager) {
        mInjector = injector;
        mCallbacks = callbacks;
        mSensorManager = sensorManager;
        mBrightnessMapper = mapper;
@@ -725,8 +746,8 @@ class AutomaticBrightnessController {
        float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
                mForegroundAppCategory);

        int newScreenAutoBrightness =
                clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
        int newScreenAutoBrightness = Math.round(clampScreenBrightness(
                value * PowerManager.BRIGHTNESS_ON));

        // If screenAutoBrightness is set, we should have screen{Brightening,Darkening}Threshold,
        // in which case we ignore the new screen brightness if it doesn't differ enough from the
@@ -750,10 +771,10 @@ class AutomaticBrightnessController {
            }

            mScreenAutoBrightness = newScreenAutoBrightness;
            mScreenBrighteningThreshold =
                    mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness);
            mScreenDarkeningThreshold =
                    mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness);
            mScreenBrighteningThreshold = clampScreenBrightness(
                    mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness));
            mScreenDarkeningThreshold = clampScreenBrightness(
                    mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness));

            if (sendUpdate) {
                mCallbacks.updateBrightness();
@@ -761,7 +782,7 @@ class AutomaticBrightnessController {
        }
    }

    private int clampScreenBrightness(int value) {
    private float clampScreenBrightness(float value) {
        return MathUtils.constrain(value,
                mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
    }
@@ -839,7 +860,7 @@ class AutomaticBrightnessController {
        }
        // The ActivityTaskManager's lock tends to get contended, so this is done in a background
        // thread and applied via this thread's handler synchronously.
        BackgroundThread.getHandler().post(new Runnable() {
        mInjector.getBackgroundThreadHandler().post(new Runnable() {
            public void run() {
                try {
                    // The foreground app is the top activity of the focused tasks stack.
@@ -965,6 +986,9 @@ class AutomaticBrightnessController {
        private int mCount;

        public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
            if (lightSensorRate <= 0) {
                throw new IllegalArgumentException("lightSensorRate must be above 0");
            }
            mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
            mRingLux = new float[mCapacity];
            mRingTime = new long[mCapacity];
@@ -1076,4 +1100,10 @@ class AutomaticBrightnessController {
            return index;
        }
    }

    public static class Injector {
        public Handler getBackgroundThreadHandler() {
            return BackgroundThread.getHandler();
        }
    }
}
+6 −3
Original line number Diff line number Diff line
@@ -18,13 +18,16 @@ package com.android.server.display;

import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.util.Arrays;

/**
 * A helper class for handling access to illuminance hysteresis level values.
 */
final class HysteresisLevels {
@VisibleForTesting
public class HysteresisLevels {
    private static final String TAG = "HysteresisLevels";

    // Default hysteresis constraints for brightening or darkening.
@@ -60,7 +63,7 @@ final class HysteresisLevels {
    /**
     * Return the brightening hysteresis threshold for the given value level.
     */
    float getBrighteningThreshold(float value) {
    public float getBrighteningThreshold(float value) {
        float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
        float brightThreshold = value * (1.0f + brightConstant);
        if (DEBUG) {
@@ -73,7 +76,7 @@ final class HysteresisLevels {
    /**
     * Return the darkening hysteresis threshold for the given value level.
     */
    float getDarkeningThreshold(float value) {
    public float getDarkeningThreshold(float value) {
        float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
        float darkThreshold = value * (1.0f - darkConstant);
        if (DEBUG) {
+192 −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;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class AutomaticBrightnessControllerTest {

    private static final int BRIGHTNESS_MIN = 1;
    private static final int BRIGHTNESS_MAX = 255;
    private static final int LIGHT_SENSOR_RATE = 20;
    private static final int INITIAL_LIGHT_SENSOR_RATE = 20;
    private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 0;
    private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 0;
    private static final int SHORT_TERM_MODEL_TIMEOUT = 0;
    private static final float DOZE_SCALE_FACTOR = 0.0f;
    private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;

    private Context mContext;
    @Mock SensorManager mSensorManager;
    @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
    @Mock HysteresisLevels mAmbientBrightnessThresholds;
    @Mock HysteresisLevels mScreenBrightnessThresholds;
    @Mock PackageManager mPackageManager;
    @Mock Handler mNoopHandler;

    private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mContext = InstrumentationRegistry.getContext();
    }

    private AutomaticBrightnessController setupController(Sensor lightSensor) {
        AutomaticBrightnessController controller = new AutomaticBrightnessController(
                new AutomaticBrightnessController.Injector() {
                    @Override
                    public Handler getBackgroundThreadHandler() {
                        return mNoopHandler;
                    }
                },
                () -> { }, mContext.getMainLooper(), mSensorManager, lightSensor,
                mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN,
                BRIGHTNESS_MAX, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, INITIAL_LIGHT_SENSOR_RATE,
                BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, DARKENING_LIGHT_DEBOUNCE_CONFIG,
                RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds,
                mScreenBrightnessThresholds, SHORT_TERM_MODEL_TIMEOUT, mPackageManager);
        controller.setLoggingEnabled(true);

        // Configure the brightness controller and grab an instance of the sensor listener,
        // through which we can deliver fake (for test) sensor values.
        controller.configure(true /* enable */, null /* configuration */,
                0 /* brightness */, false /* userChangedBrightness */, 0 /* adjustment */,
                false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);

        return controller;
    }

    @Test
    public void testNoHysteresisAtMinBrightness() throws Exception {
        Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
        AutomaticBrightnessController controller = setupController(lightSensor);

        ArgumentCaptor<SensorEventListener> listenerCaptor =
                ArgumentCaptor.forClass(SensorEventListener.class);
        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
        SensorEventListener listener = listenerCaptor.getValue();

        // Set up system to return 5 as a brightness value
        float lux1 = 100.0f;
        float normalizedBrightness1 = 0.02f;
        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
                .thenReturn(lux1);
        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
                .thenReturn(lux1);
        when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
                .thenReturn(normalizedBrightness1);

        // This is the important bit: When the new brightness is set, make sure the new
        // brightening threshold is beyond the maximum brightness value...so that we can test that
        // our threshold clamping works.
        when(mScreenBrightnessThresholds.getBrighteningThreshold(5)).thenReturn(1.0f);

        // Send new sensor value and verify
        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
        assertEquals(5, controller.getAutomaticScreenBrightness());


        // Set up system to return 255 as a brightness value
        float lux2 = 10.0f;
        float normalizedBrightness2 = 0.0f;
        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
                .thenReturn(lux2);
        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
                .thenReturn(lux2);
        when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
                .thenReturn(normalizedBrightness2);

        // Send new sensor value and verify
        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
        assertEquals(1, controller.getAutomaticScreenBrightness());
    }

    @Test
    public void testNoHysteresisAtMaxBrightness() throws Exception {
        Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
        AutomaticBrightnessController controller = setupController(lightSensor);

        ArgumentCaptor<SensorEventListener> listenerCaptor =
                ArgumentCaptor.forClass(SensorEventListener.class);
        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
        SensorEventListener listener = listenerCaptor.getValue();

        // Set up system to return 250 as a brightness value
        float lux1 = 100.0f;
        float normalizedBrightness1 = 0.98f;
        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
                .thenReturn(lux1);
        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
                .thenReturn(lux1);
        when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
                .thenReturn(normalizedBrightness1);

        // This is the important bit: When the new brightness is set, make sure the new
        // brightening threshold is beyond the maximum brightness value...so that we can test that
        // our threshold clamping works.
        when(mScreenBrightnessThresholds.getBrighteningThreshold(250)).thenReturn(260.0f);

        // Send new sensor value and verify
        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
        assertEquals(250, controller.getAutomaticScreenBrightness());


        // Set up system to return 255 as a brightness value
        float lux2 = 110.0f;
        float normalizedBrightness2 = 1.0f;
        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
                .thenReturn(lux2);
        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
                .thenReturn(lux2);
        when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
                .thenReturn(normalizedBrightness2);

        // Send new sensor value and verify
        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
        assertEquals(255, controller.getAutomaticScreenBrightness());
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@
 * 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
 * limitations under the License.
 */

package com.android.server.display;
+60 −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;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.os.SystemClock;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public final class TestUtils {

    public static 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;
    }


    public static 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);
        }
    }

    public static 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;
    }

}
Loading