diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index ffed68ed7d38c..f4c36c6b6ec2a 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -74,13 +74,6 @@ class AutomaticBrightnessController { private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5; private static final int MSG_RUN_UPDATE = 6; - // Length of the ambient light horizon used to calculate the long term estimate of ambient - // light. - private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000; - - // Length of the ambient light horizon used to calculate short-term estimate of ambient light. - private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; - // Callbacks for requesting updates to the display's power state private final Callbacks mCallbacks; @@ -125,8 +118,10 @@ class AutomaticBrightnessController { // and only then decide whether to change brightness. private final boolean mResetAmbientLuxAfterWarmUpConfig; - // Period of time in which to consider light samples in milliseconds. - private final int mAmbientLightHorizon; + // Period of time in which to consider light samples for a short/long-term estimate of ambient + // light in milliseconds. + private final int mAmbientLightHorizonLong; + private final int mAmbientLightHorizonShort; // The intercept used for the weighting calculation. This is used in order to keep all possible // weighting values positive. @@ -220,6 +215,7 @@ class AutomaticBrightnessController { private Context mContext; private int mState = AUTO_BRIGHTNESS_DISABLED; + private Clock mClock; private final Injector mInjector; AutomaticBrightnessController(Callbacks callbacks, Looper looper, @@ -231,14 +227,16 @@ class AutomaticBrightnessController { boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, Context context, HighBrightnessModeController hbmController, - BrightnessMappingStrategy idleModeBrightnessMapper) { + BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, + int ambientLightHorizonLong) { this(new Injector(), callbacks, looper, sensorManager, lightSensor, interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, context, - hbmController, idleModeBrightnessMapper + hbmController, idleModeBrightnessMapper, ambientLightHorizonShort, + ambientLightHorizonLong ); } @@ -252,8 +250,10 @@ class AutomaticBrightnessController { boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, Context context, HighBrightnessModeController hbmController, - BrightnessMappingStrategy idleModeBrightnessMapper) { + BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, + int ambientLightHorizonLong) { mInjector = injector; + mClock = injector.createClock(); mContext = context; mCallbacks = callbacks; mSensorManager = sensorManager; @@ -268,15 +268,16 @@ class AutomaticBrightnessController { mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; - mAmbientLightHorizon = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; - mWeightingIntercept = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; + mAmbientLightHorizonLong = ambientLightHorizonLong; + mAmbientLightHorizonShort = ambientLightHorizonShort; + mWeightingIntercept = ambientLightHorizonLong; mAmbientBrightnessThresholds = ambientBrightnessThresholds; mScreenBrightnessThresholds = screenBrightnessThresholds; mShortTermModelValid = true; mShortTermModelAnchor = -1; mHandler = new AutomaticBrightnessHandler(looper); mAmbientLightRingBuffer = - new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon); + new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock); if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { mLightSensor = lightSensor; @@ -397,6 +398,11 @@ class AutomaticBrightnessController { mHandler.sendEmptyMessage(MSG_RUN_UPDATE); } + @VisibleForTesting + float getAmbientLux() { + return mAmbientLux; + } + private boolean setDisplayPolicy(int policy) { if (mDisplayPolicy == policy) { return false; @@ -476,7 +482,8 @@ class AutomaticBrightnessController { pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); - pw.println(" mAmbientLightHorizon=" + mAmbientLightHorizon); + pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong); + pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort); pw.println(" mWeightingIntercept=" + mWeightingIntercept); pw.println(); @@ -545,7 +552,7 @@ class AutomaticBrightnessController { if (enable) { if (!mLightSensorEnabled) { mLightSensorEnabled = true; - mLightSensorEnableTime = SystemClock.uptimeMillis(); + mLightSensorEnableTime = mClock.uptimeMillis(); mCurrentLightSensorRate = mInitialLightSensorRate; registerForegroundAppUpdater(); mSensorManager.registerListener(mLightSensorListener, mLightSensor, @@ -580,7 +587,7 @@ class AutomaticBrightnessController { private void applyLightSensorMeasurement(long time, float lux) { mRecentLightSamples++; - mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon); + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); mAmbientLightRingBuffer.push(time, lux); // Remember this sample value. @@ -721,8 +728,8 @@ class AutomaticBrightnessController { } private void updateAmbientLux() { - long time = SystemClock.uptimeMillis(); - mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon); + long time = mClock.uptimeMillis(); + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); updateAmbientLux(time); } @@ -742,7 +749,7 @@ class AutomaticBrightnessController { timeWhenSensorWarmedUp); return; } - setAmbientLux(calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS)); + setAmbientLux(calculateAmbientLux(time, mAmbientLightHorizonShort)); mAmbientLuxValid = true; if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: Initializing: " + @@ -762,8 +769,8 @@ class AutomaticBrightnessController { // proposed ambient light value since the slow value might be sufficiently far enough away // from the fast value to cause a recalculation while its actually just converging on // the fast value still. - float slowAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_LONG_HORIZON_MILLIS); - float fastAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS); + float slowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong); + float fastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort); if ((slowAmbientLux >= mAmbientBrighteningThreshold && fastAmbientLux >= mAmbientBrighteningThreshold @@ -1044,7 +1051,7 @@ class AutomaticBrightnessController { @Override public void onSensorChanged(SensorEvent event) { if (mLightSensorEnabled) { - final long time = SystemClock.uptimeMillis(); + final long time = mClock.uptimeMillis(); final float lux = event.values[0]; handleLightSensorEvent(time, lux); } @@ -1070,6 +1077,15 @@ class AutomaticBrightnessController { void updateBrightness(); } + /** Functional interface for providing time. */ + @VisibleForTesting + interface Clock { + /** + * Returns current time in milliseconds since boot, not counting time spent in deep sleep. + */ + long uptimeMillis(); + } + /** * A ring buffer of ambient light measurements sorted by time. * @@ -1089,14 +1105,16 @@ class AutomaticBrightnessController { private int mStart; private int mEnd; private int mCount; + Clock mClock; - public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) { + public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon, Clock clock) { 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]; + mClock = clock; } public float getLux(int index) { @@ -1181,7 +1199,7 @@ class AutomaticBrightnessController { StringBuilder buf = new StringBuilder(); buf.append('['); for (int i = 0; i < mCount; i++) { - final long next = i + 1 < mCount ? getTime(i + 1) : SystemClock.uptimeMillis(); + final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis(); if (i != 0) { buf.append(", "); } @@ -1210,5 +1228,9 @@ class AutomaticBrightnessController { public Handler getBackgroundThreadHandler() { return BackgroundThread.getHandler(); } + + Clock createClock() { + return SystemClock::uptimeMillis; + } } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index a9e1647446cb8..3df2422071b82 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -52,6 +52,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -86,6 +87,13 @@ public class DisplayDeviceConfig { private static final float NITS_INVALID = -1; + // Length of the ambient light horizon used to calculate the long term estimate of ambient + // light. + private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000; + + // Length of the ambient light horizon used to calculate short-term estimate of ambient light. + private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; + private final Context mContext; // The details of the ambient light sensor associated with this display. @@ -120,6 +128,8 @@ public class DisplayDeviceConfig { private float mBrightnessRampFastIncrease = Float.NaN; private float mBrightnessRampSlowDecrease = Float.NaN; private float mBrightnessRampSlowIncrease = Float.NaN; + private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; + private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS; private Spline mBrightnessToBacklightSpline; private Spline mBacklightToBrightnessSpline; private Spline mBacklightToNitsSpline; @@ -346,6 +356,14 @@ public class DisplayDeviceConfig { return mBrightnessRampSlowIncrease; } + public int getAmbientHorizonLong() { + return mAmbientHorizonLong; + } + + public int getAmbientHorizonShort() { + return mAmbientHorizonShort; + } + SensorData getAmbientLightSensor() { return mAmbientLightSensor; } @@ -405,6 +423,8 @@ public class DisplayDeviceConfig { + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease + + ", mAmbientHorizonLong=" + mAmbientHorizonLong + + ", mAmbientHorizonShort=" + mAmbientHorizonShort + ", mAmbientLightSensor=" + mAmbientLightSensor + ", mProximitySensor=" + mProximitySensor + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray()) @@ -461,6 +481,7 @@ public class DisplayDeviceConfig { loadBrightnessRamps(config); loadAmbientLightSensorFromDdc(config); loadProxSensorFromDdc(config); + loadAmbientHorizonFromDdc(config); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); } @@ -869,6 +890,17 @@ public class DisplayDeviceConfig { } } + private void loadAmbientHorizonFromDdc(DisplayConfiguration config) { + final BigInteger configLongHorizon = config.getAmbientLightHorizonLong(); + if (configLongHorizon != null) { + mAmbientHorizonLong = configLongHorizon.intValue(); + } + final BigInteger configShortHorizon = config.getAmbientLightHorizonShort(); + if (configShortHorizon != null) { + mAmbientHorizonShort = configShortHorizon.intValue(); + } + } + static class SensorData { public String type; public String name; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index faf0038b29c8d..34f915e41cd77 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -957,7 +957,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, mContext, - mHbmController, mIdleModeBrightnessMapper); + mHbmController, mIdleModeBrightnessMapper, + mDisplayDeviceConfig.getAmbientHorizonShort(), + mDisplayDeviceConfig.getAmbientHorizonLong()); } else { mUseSoftwareAutoBrightnessConfig = false; } diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 2f4dd57ab15b0..baf2ede07fa30 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -58,6 +58,15 @@ + + + + + + + + diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 5b2b87c3f14e4..6f97431b48739 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -18,6 +18,8 @@ package com.android.server.display.config { public class DisplayConfiguration { ctor public DisplayConfiguration(); + method public final java.math.BigInteger getAmbientLightHorizonLong(); + method public final java.math.BigInteger getAmbientLightHorizonShort(); method @Nullable public final com.android.server.display.config.DensityMap getDensityMap(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public final com.android.server.display.config.SensorDetails getLightSensor(); @@ -29,6 +31,8 @@ package com.android.server.display.config { method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); + method public final void setAmbientLightHorizonLong(java.math.BigInteger); + method public final void setAmbientLightHorizonShort(java.math.BigInteger); method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public final void setLightSensor(com.android.server.display.config.SensorDetails); diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 176e5a9fdc512..54945e44fee58 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.display; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyFloat; @@ -34,18 +35,22 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.testutils.OffsettableClock; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest @@ -61,7 +66,11 @@ public class AutomaticBrightnessControllerTest { private static final float DOZE_SCALE_FACTOR = 0.0f; private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; - + private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000; + private static final int AMBIENT_LIGHT_HORIZON_LONG = 2000; + private static final float EPSILON = 0.001f; + private OffsettableClock mClock = new OffsettableClock(); + private TestLooper mTestLooper; private Context mContext; private AutomaticBrightnessController mController; @@ -91,21 +100,36 @@ public class AutomaticBrightnessControllerTest { } } + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } + private AutomaticBrightnessController setupController(Sensor lightSensor) { + mClock = new OffsettableClock.Stopped(); + mTestLooper = new TestLooper(mClock::now); + AutomaticBrightnessController controller = new AutomaticBrightnessController( new AutomaticBrightnessController.Injector() { @Override public Handler getBackgroundThreadHandler() { return mNoOpHandler; } - }, - () -> { }, mContext.getMainLooper(), mSensorManager, lightSensor, + + @Override + AutomaticBrightnessController.Clock createClock() { + return mClock::now; + } + + }, // pass in test looper instead, pass in offsetable clock + () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor, mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT, BRIGHTNESS_MAX_FLOAT, 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, - mContext, mHbmController, mIdleBrightnessMappingStrategy + mContext, mHbmController, mIdleBrightnessMappingStrategy, + AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG ); when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT); @@ -276,4 +300,95 @@ public class AutomaticBrightnessControllerTest { // Ensure we use the correct mapping strategy verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f); } + + @Test + public void testAmbientLightHorizon() throws Exception { + // create abc + Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor"); + mController = setupController(lightSensor); + ArgumentCaptor 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(); + + long increment = 500; + // set autobrightness to low + // t = 0 + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + + // t = 500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + + // t = 1000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 1500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2000 + // ensure that our reading is at 0. + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // first 10000 lux sensor event reading + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 3000 + // lux reading should still not yet be 10000. + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 3500 + mClock.fastForward(increment); + // lux has been high (10000) for 1000ms. + // lux reading should be 10000 + // short horizon (ambient lux) is high, long horizon is still not high + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 4000 + // stay high + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 4500 + Mockito.clearInvocations(mBrightnessMappingStrategy); + mClock.fastForward(increment); + // short horizon is high, long horizon is high too + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 5000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 5500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 6000 + mClock.fastForward(increment); + // ambient lux goes to 0 + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + } }