diff options
| author | 2024-03-27 00:44:23 +0000 | |
|---|---|---|
| committer | 2024-03-27 00:44:23 +0000 | |
| commit | 12fc3b7c207c261d0ee116043ad84d16e98db2f9 (patch) | |
| tree | 371e7a6510d505dfe318968267c4a989da0f7b1c | |
| parent | 28836af14bda6c1f7e27884d047a3f545e203206 (diff) | |
Revert "Extracting LightSensorController from ABC"
This reverts commit 28836af14bda6c1f7e27884d047a3f545e203206.
Reason for revert: Causes a regression in b/331157443
Bug: 331157443
Change-Id: Ica388c4f79da057c96ce0875f4085d3736f72de0
10 files changed, 1358 insertions, 1824 deletions
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 1546010717c5..4aab9d26dbcb 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -29,6 +29,9 @@ import android.app.TaskStackListener; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; @@ -37,19 +40,20 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; +import android.os.SystemClock; +import android.os.Trace; import android.util.EventLog; import android.util.MathUtils; import android.util.Slog; import android.util.SparseArray; +import android.util.TimeUtils; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; -import com.android.internal.os.Clock; import com.android.server.EventLogTags; import com.android.server.display.brightness.BrightnessEvent; -import com.android.server.display.brightness.LightSensorController; import com.android.server.display.brightness.clamper.BrightnessClamperController; import java.io.PrintWriter; @@ -64,6 +68,8 @@ import java.lang.annotation.RetentionPolicy; public class AutomaticBrightnessController { private static final String TAG = "AutomaticBrightnessController"; + private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; + public static final int AUTO_BRIGHTNESS_ENABLED = 1; public static final int AUTO_BRIGHTNESS_DISABLED = 2; public static final int AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE = 3; @@ -81,20 +87,32 @@ public class AutomaticBrightnessController { public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2; public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE; + // How long the current sensor reading is assumed to be valid beyond the current time. + // This provides a bit of prediction, as well as ensures that the weight for the last sample is + // non-zero, which in turn ensures that the total weight is non-zero. + private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100; + // Debounce for sampling user-initiated changes in display brightness to ensure // the user is satisfied with the result before storing the sample. private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000; - private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 1; - private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 2; - private static final int MSG_UPDATE_FOREGROUND_APP = 3; - private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 4; - private static final int MSG_RUN_UPDATE = 5; - private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 6; + private static final int MSG_UPDATE_AMBIENT_LUX = 1; + private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2; + private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 3; + private static final int MSG_UPDATE_FOREGROUND_APP = 4; + private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5; + private static final int MSG_RUN_UPDATE = 6; + private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 7; // Callbacks for requesting updates to the display's power state private final Callbacks mCallbacks; + // The sensor manager. + private final SensorManager mSensorManager; + + // The light sensor, or null if not available or needed. + private final Sensor mLightSensor; + // The mapper to translate ambient lux to screen brightness in the range [0, 1.0]. @NonNull private BrightnessMappingStrategy mCurrentBrightnessMapper; @@ -109,21 +127,94 @@ public class AutomaticBrightnessController { // How much to scale doze brightness by (should be (0, 1.0]). private final float mDozeScaleFactor; + // Initial light sensor event rate in milliseconds. + private final int mInitialLightSensorRate; + + // Steady-state light sensor event rate in milliseconds. + private final int mNormalLightSensorRate; + + // The current light sensor event rate in milliseconds. + private int mCurrentLightSensorRate; + + // Stability requirements in milliseconds for accepting a new brightness level. This is used + // for debouncing the light sensor. Different constants are used to debounce the light sensor + // when adapting to brighter or darker environments. This parameter controls how quickly + // brightness changes occur in response to an observed change in light level that exceeds the + // hysteresis threshold. + private final long mBrighteningLightDebounceConfig; + private final long mDarkeningLightDebounceConfig; + private final long mBrighteningLightDebounceConfigIdle; + private final long mDarkeningLightDebounceConfigIdle; + + // If true immediately after the screen is turned on the controller will try to adjust the + // brightness based on the current sensor reads. If false, the controller will collect more data + // and only then decide whether to change brightness. + private final boolean mResetAmbientLuxAfterWarmUpConfig; + + // 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. + private final int mWeightingIntercept; + // Configuration object for determining thresholds to change brightness dynamically + private final HysteresisLevels mAmbientBrightnessThresholds; private final HysteresisLevels mScreenBrightnessThresholds; + private final HysteresisLevels mAmbientBrightnessThresholdsIdle; private final HysteresisLevels mScreenBrightnessThresholdsIdle; private boolean mLoggingEnabled; + + // Amount of time to delay auto-brightness after screen on while waiting for + // the light sensor to warm-up in milliseconds. + // May be 0 if no warm-up is required. + private int mLightSensorWarmUpTimeConfig; + + // Set to true if the light sensor is enabled. + private boolean mLightSensorEnabled; + + // The time when the light sensor was enabled. + private long mLightSensorEnableTime; + // The currently accepted nominal ambient light level. private float mAmbientLux; + + // The last calculated ambient light level (long time window). + private float mSlowAmbientLux; + + // The last calculated ambient light level (short time window). + private float mFastAmbientLux; + + // The last ambient lux value prior to passing the darkening or brightening threshold. + private float mPreThresholdLux; + // True if mAmbientLux holds a valid value. private boolean mAmbientLuxValid; + + // The ambient light level threshold at which to brighten or darken the screen. + private float mAmbientBrighteningThreshold; + private float mAmbientDarkeningThreshold; + // The last brightness value prior to passing the darkening or brightening threshold. private float mPreThresholdBrightness; // The screen brightness threshold at which to brighten or darken the screen. private float mScreenBrighteningThreshold; private float mScreenDarkeningThreshold; + // The most recent light sample. + private float mLastObservedLux = INVALID_LUX; + + // The time of the most light recent sample. + private long mLastObservedLuxTime; + + // The number of light samples collected since the light sensor was enabled. + private int mRecentLightSamples; + + // A ring buffer containing all of the recent ambient light sensor readings. + private AmbientLightRingBuffer mAmbientLightRingBuffer; // The handler private AutomaticBrightnessHandler mHandler; @@ -182,55 +273,88 @@ public class AutomaticBrightnessController { private Context mContext; private int mState = AUTO_BRIGHTNESS_DISABLED; - private final Clock mClock; + private Clock mClock; private final Injector mInjector; - private final LightSensorController mLightSensorController; - - AutomaticBrightnessController(Callbacks callbacks, Looper looper, SensorManager sensorManager, + AutomaticBrightnessController(Callbacks callbacks, Looper looper, + SensorManager sensorManager, Sensor lightSensor, SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap, - float brightnessMin, float brightnessMax, float dozeScaleFactor, + int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, + float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, + long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, + boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, BrightnessRangeController brightnessModeController, - BrightnessThrottler brightnessThrottler, float userLux, float userNits, - int displayId, LightSensorController.LightSensorControllerConfig config, + BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort, + int ambientLightHorizonLong, float userLux, float userNits, BrightnessClamperController brightnessClamperController) { - this(new Injector(), callbacks, looper, - brightnessMappingStrategyMap, brightnessMin, brightnessMax, dozeScaleFactor, - screenBrightnessThresholds, + this(new Injector(), callbacks, looper, sensorManager, lightSensor, + brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, brightnessMax, + dozeScaleFactor, lightSensorRate, initialLightSensorRate, + brighteningLightDebounceConfig, darkeningLightDebounceConfig, + brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle, + resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, + screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, brightnessModeController, - brightnessThrottler, userLux, userNits, - new LightSensorController(sensorManager, looper, displayId, config), - brightnessClamperController + brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux, + userNits, brightnessClamperController ); } @VisibleForTesting AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper, + SensorManager sensorManager, Sensor lightSensor, SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap, - float brightnessMin, float brightnessMax, float dozeScaleFactor, + int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, + float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, + long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, + boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, BrightnessRangeController brightnessRangeController, - BrightnessThrottler brightnessThrottler, float userLux, float userNits, - LightSensorController lightSensorController, + BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort, + int ambientLightHorizonLong, float userLux, float userNits, BrightnessClamperController brightnessClamperController) { mInjector = injector; mClock = injector.createClock(); mContext = context; mCallbacks = callbacks; + mSensorManager = sensorManager; mCurrentBrightnessMapper = brightnessMappingStrategyMap.get(AUTO_BRIGHTNESS_MODE_DEFAULT); mScreenBrightnessRangeMinimum = brightnessMin; mScreenBrightnessRangeMaximum = brightnessMax; + mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime; mDozeScaleFactor = dozeScaleFactor; + mNormalLightSensorRate = lightSensorRate; + mInitialLightSensorRate = initialLightSensorRate; + mCurrentLightSensorRate = -1; + mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; + mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; + mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle; + mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle; + mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; + mAmbientLightHorizonLong = ambientLightHorizonLong; + mAmbientLightHorizonShort = ambientLightHorizonShort; + mWeightingIntercept = ambientLightHorizonLong; + mAmbientBrightnessThresholds = ambientBrightnessThresholds; + mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle; mScreenBrightnessThresholds = screenBrightnessThresholds; mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle; mShortTermModel = new ShortTermModel(); mPausedShortTermModel = new ShortTermModel(); mHandler = new AutomaticBrightnessHandler(looper); - mLightSensorController = lightSensorController; - mLightSensorController.setListener(this::setAmbientLux); + mAmbientLightRingBuffer = + new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock); + + if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { + mLightSensor = lightSensor; + } + mActivityTaskManager = ActivityTaskManager.getService(); mPackageManager = mContext.getPackageManager(); mTaskStackListener = new TaskStackListenerImpl(); @@ -282,13 +406,13 @@ public class AutomaticBrightnessController { if (brightnessEvent != null) { brightnessEvent.setLux( mAmbientLuxValid ? mAmbientLux : PowerManager.BRIGHTNESS_INVALID_FLOAT); + brightnessEvent.setPreThresholdLux(mPreThresholdLux); brightnessEvent.setPreThresholdBrightness(mPreThresholdBrightness); brightnessEvent.setRecommendedBrightness(mScreenAutoBrightness); brightnessEvent.setFlags(brightnessEvent.getFlags() | (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0) | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0)); brightnessEvent.setAutoBrightnessMode(getMode()); - mLightSensorController.updateBrightnessEvent(brightnessEvent); } if (!mAmbientLuxValid) { @@ -311,22 +435,21 @@ public class AutomaticBrightnessController { */ public float getAutomaticScreenBrightnessBasedOnLastObservedLux( BrightnessEvent brightnessEvent) { - float lastObservedLux = mLightSensorController.getLastObservedLux(); - if (lastObservedLux == INVALID_LUX) { + if (mLastObservedLux == INVALID_LUX) { return PowerManager.BRIGHTNESS_INVALID_FLOAT; } - float brightness = mCurrentBrightnessMapper.getBrightness(lastObservedLux, + float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux, mForegroundAppPackageName, mForegroundAppCategory); if (shouldApplyDozeScaleFactor()) { brightness *= mDozeScaleFactor; } if (brightnessEvent != null) { - brightnessEvent.setLux(lastObservedLux); + brightnessEvent.setLux(mLastObservedLux); brightnessEvent.setRecommendedBrightness(brightness); brightnessEvent.setFlags(brightnessEvent.getFlags() - | (lastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0) + | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0) | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0)); brightnessEvent.setAutoBrightnessMode(getMode()); } @@ -378,7 +501,6 @@ public class AutomaticBrightnessController { public void stop() { setLightSensorEnabled(false); - mLightSensorController.stop(); } public boolean hasUserDataPoints() { @@ -407,6 +529,14 @@ public class AutomaticBrightnessController { return mAmbientLux; } + float getSlowAmbientLux() { + return mSlowAmbientLux; + } + + float getFastAmbientLux() { + return mFastAmbientLux; + } + private boolean setDisplayPolicy(int policy) { if (mDisplayPolicy == policy) { return false; @@ -485,13 +615,36 @@ public class AutomaticBrightnessController { pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); pw.println(" mDozeScaleFactor=" + mDozeScaleFactor); + pw.println(" mInitialLightSensorRate=" + mInitialLightSensorRate); + pw.println(" mNormalLightSensorRate=" + mNormalLightSensorRate); + pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); + pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); + pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); + pw.println(" mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle); + pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle); + pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); + pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong); + pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort); + pw.println(" mWeightingIntercept=" + mWeightingIntercept); + pw.println(); pw.println("Automatic Brightness Controller State:"); + pw.println(" mLightSensor=" + mLightSensor); + pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); + pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime)); + pw.println(" mCurrentLightSensorRate=" + mCurrentLightSensorRate); pw.println(" mAmbientLux=" + mAmbientLux); pw.println(" mAmbientLuxValid=" + mAmbientLuxValid); + pw.println(" mPreThresholdLux=" + mPreThresholdLux); pw.println(" mPreThresholdBrightness=" + mPreThresholdBrightness); + pw.println(" mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold); + pw.println(" mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold); pw.println(" mScreenBrighteningThreshold=" + mScreenBrighteningThreshold); pw.println(" mScreenDarkeningThreshold=" + mScreenDarkeningThreshold); + pw.println(" mLastObservedLux=" + mLastObservedLux); + pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime)); + pw.println(" mRecentLightSamples=" + mRecentLightSamples); + pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer); pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness); pw.println(" mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy)); pw.println(" mShortTermModel="); @@ -520,21 +673,22 @@ public class AutomaticBrightnessController { } pw.println(); + pw.println(" mAmbientBrightnessThresholds="); + mAmbientBrightnessThresholds.dump(pw); pw.println(" mScreenBrightnessThresholds="); mScreenBrightnessThresholds.dump(pw); pw.println(" mScreenBrightnessThresholdsIdle="); mScreenBrightnessThresholdsIdle.dump(pw); - - pw.println(); - mLightSensorController.dump(pw); + pw.println(" mAmbientBrightnessThresholdsIdle="); + mAmbientBrightnessThresholdsIdle.dump(pw); } public float[] getLastSensorValues() { - return mLightSensorController.getLastSensorValues(); + return mAmbientLightRingBuffer.getAllLuxValues(); } public long[] getLastSensorTimestamps() { - return mLightSensorController.getLastSensorTimestamps(); + return mAmbientLightRingBuffer.getAllTimestamps(); } private String configStateToString(int state) { @@ -551,33 +705,273 @@ public class AutomaticBrightnessController { } private boolean setLightSensorEnabled(boolean enable) { - if (enable && mLightSensorController.enableLightSensorIfNeeded()) { - registerForegroundAppUpdater(); - return true; - } else if (!enable && mLightSensorController.disableLightSensorIfNeeded()) { + if (enable) { + if (!mLightSensorEnabled) { + mLightSensorEnabled = true; + mLightSensorEnableTime = mClock.uptimeMillis(); + mCurrentLightSensorRate = mInitialLightSensorRate; + registerForegroundAppUpdater(); + mSensorManager.registerListener(mLightSensorListener, mLightSensor, + mCurrentLightSensorRate * 1000, mHandler); + return true; + } + } else if (mLightSensorEnabled) { + mLightSensorEnabled = false; + mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig; + if (!mAmbientLuxValid) { + mPreThresholdLux = PowerManager.BRIGHTNESS_INVALID_FLOAT; + } mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; mRawScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; mPreThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; - mAmbientLuxValid = mLightSensorController.hasValidAmbientLux(); + mRecentLightSamples = 0; + mAmbientLightRingBuffer.clear(); + mCurrentLightSensorRate = -1; + mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); unregisterForegroundAppUpdater(); + mSensorManager.unregisterListener(mLightSensorListener); } return false; } + private void handleLightSensorEvent(long time, float lux) { + Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux); + mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); + + if (mAmbientLightRingBuffer.size() == 0) { + // switch to using the steady-state sample rate after grabbing the initial light sample + adjustLightSensorRate(mNormalLightSensorRate); + } + applyLightSensorMeasurement(time, lux); + updateAmbientLux(time); + } + + private void applyLightSensorMeasurement(long time, float lux) { + mRecentLightSamples++; + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); + mAmbientLightRingBuffer.push(time, lux); + + // Remember this sample value. + mLastObservedLux = lux; + mLastObservedLuxTime = time; + } + + private void adjustLightSensorRate(int lightSensorRate) { + // if the light sensor rate changed, update the sensor listener + if (lightSensorRate != mCurrentLightSensorRate) { + if (mLoggingEnabled) { + Slog.d(TAG, "adjustLightSensorRate: " + + "previousRate=" + mCurrentLightSensorRate + ", " + + "currentRate=" + lightSensorRate); + } + mCurrentLightSensorRate = lightSensorRate; + mSensorManager.unregisterListener(mLightSensorListener); + mSensorManager.registerListener(mLightSensorListener, mLightSensor, + lightSensorRate * 1000, mHandler); + } + } + private boolean setAutoBrightnessAdjustment(float adjustment) { return mCurrentBrightnessMapper.setAutoBrightnessAdjustment(adjustment); } private void setAmbientLux(float lux) { - // called by LightSensorController.setAmbientLux - mAmbientLuxValid = true; + if (mLoggingEnabled) { + Slog.d(TAG, "setAmbientLux(" + lux + ")"); + } + if (lux < 0) { + Slog.w(TAG, "Ambient lux was negative, ignoring and setting to 0"); + lux = 0; + } mAmbientLux = lux; + if (isInIdleMode()) { + mAmbientBrighteningThreshold = + mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux); + mAmbientDarkeningThreshold = + mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux); + } else { + mAmbientBrighteningThreshold = + mAmbientBrightnessThresholds.getBrighteningThreshold(lux); + mAmbientDarkeningThreshold = + mAmbientBrightnessThresholds.getDarkeningThreshold(lux); + } mBrightnessRangeController.onAmbientLuxChange(mAmbientLux); mBrightnessClamperController.onAmbientLuxChange(mAmbientLux); // If the short term model was invalidated and the change is drastic enough, reset it. mShortTermModel.maybeReset(mAmbientLux); - updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */); + } + + private float calculateAmbientLux(long now, long horizon) { + if (mLoggingEnabled) { + Slog.d(TAG, "calculateAmbientLux(" + now + ", " + horizon + ")"); + } + final int N = mAmbientLightRingBuffer.size(); + if (N == 0) { + Slog.e(TAG, "calculateAmbientLux: No ambient light readings available"); + return -1; + } + + // Find the first measurement that is just outside of the horizon. + int endIndex = 0; + final long horizonStartTime = now - horizon; + for (int i = 0; i < N-1; i++) { + if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) { + endIndex++; + } else { + break; + } + } + if (mLoggingEnabled) { + Slog.d(TAG, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" + + mAmbientLightRingBuffer.getTime(endIndex) + ", " + + mAmbientLightRingBuffer.getLux(endIndex) + ")"); + } + float sum = 0; + float totalWeight = 0; + long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS; + for (int i = N - 1; i >= endIndex; i--) { + long eventTime = mAmbientLightRingBuffer.getTime(i); + if (i == endIndex && eventTime < horizonStartTime) { + // If we're at the final value, make sure we only consider the part of the sample + // within our desired horizon. + eventTime = horizonStartTime; + } + final long startTime = eventTime - now; + float weight = calculateWeight(startTime, endTime); + float lux = mAmbientLightRingBuffer.getLux(i); + if (mLoggingEnabled) { + Slog.d(TAG, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " + + "lux=" + lux + ", " + + "weight=" + weight); + } + totalWeight += weight; + sum += lux * weight; + endTime = startTime; + } + if (mLoggingEnabled) { + Slog.d(TAG, "calculateAmbientLux: " + + "totalWeight=" + totalWeight + ", " + + "newAmbientLux=" + (sum / totalWeight)); + } + return sum / totalWeight; + } + + private float calculateWeight(long startDelta, long endDelta) { + return weightIntegral(endDelta) - weightIntegral(startDelta); + } + + // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the + // horizon we're looking at and provides a non-linear weighting for light samples. + private float weightIntegral(long x) { + return x * (x * 0.5f + mWeightingIntercept); + } + + private long nextAmbientLightBrighteningTransition(long time) { + final int N = mAmbientLightRingBuffer.size(); + long earliestValidTime = time; + for (int i = N - 1; i >= 0; i--) { + if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) { + break; + } + earliestValidTime = mAmbientLightRingBuffer.getTime(i); + } + return earliestValidTime + (isInIdleMode() + ? mBrighteningLightDebounceConfigIdle : mBrighteningLightDebounceConfig); + } + + private long nextAmbientLightDarkeningTransition(long time) { + final int N = mAmbientLightRingBuffer.size(); + long earliestValidTime = time; + for (int i = N - 1; i >= 0; i--) { + if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) { + break; + } + earliestValidTime = mAmbientLightRingBuffer.getTime(i); + } + return earliestValidTime + (isInIdleMode() + ? mDarkeningLightDebounceConfigIdle : mDarkeningLightDebounceConfig); + } + + private void updateAmbientLux() { + long time = mClock.uptimeMillis(); + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); + updateAmbientLux(time); + } + + private void updateAmbientLux(long time) { + // If the light sensor was just turned on then immediately update our initial + // estimate of the current ambient light level. + if (!mAmbientLuxValid) { + final long timeWhenSensorWarmedUp = + mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; + if (time < timeWhenSensorWarmedUp) { + if (mLoggingEnabled) { + Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: " + + "time=" + time + ", " + + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); + } + mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, + timeWhenSensorWarmedUp); + return; + } + setAmbientLux(calculateAmbientLux(time, mAmbientLightHorizonShort)); + mAmbientLuxValid = true; + if (mLoggingEnabled) { + Slog.d(TAG, "updateAmbientLux: Initializing: " + + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + + "mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */); + } + + long nextBrightenTransition = nextAmbientLightBrighteningTransition(time); + long nextDarkenTransition = nextAmbientLightDarkeningTransition(time); + // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term + // change in lighting conditions, and a fast ambient lux to determine what the new + // brightness situation is since the slow lux can be quite slow to converge. + // + // Note that both values need to be checked for sufficient change before updating the + // 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. + mSlowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong); + mFastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort); + + if ((mSlowAmbientLux >= mAmbientBrighteningThreshold + && mFastAmbientLux >= mAmbientBrighteningThreshold + && nextBrightenTransition <= time) + || (mSlowAmbientLux <= mAmbientDarkeningThreshold + && mFastAmbientLux <= mAmbientDarkeningThreshold + && nextDarkenTransition <= time)) { + mPreThresholdLux = mAmbientLux; + setAmbientLux(mFastAmbientLux); + if (mLoggingEnabled) { + Slog.d(TAG, "updateAmbientLux: " + + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " + + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", " + + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", " + + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + + "mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */); + nextBrightenTransition = nextAmbientLightBrighteningTransition(time); + nextDarkenTransition = nextAmbientLightDarkeningTransition(time); + } + long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition); + // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't + // exceed the necessary threshold, then it's possible we'll get a transition time prior to + // now. Rather than continually checking to see whether the weighted lux exceeds the + // threshold, schedule an update for when we'd normally expect another light sample, which + // should be enough time to decide whether we should actually transition to the new + // weighted ambient lux or not. + nextTransitionTime = + nextTransitionTime > time ? nextTransitionTime : time + mNormalLightSensorRate; + if (mLoggingEnabled) { + Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " + + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime)); + } + mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime); } private void updateAutoBrightness(boolean sendUpdate, boolean isManuallySet) { @@ -678,7 +1072,8 @@ public class AutomaticBrightnessController { if (mLoggingEnabled) { Slog.d(TAG, "Auto-brightness adjustment changed by user: " + "lux=" + mAmbientLux + ", " - + "brightness=" + mScreenAutoBrightness); + + "brightness=" + mScreenAutoBrightness + ", " + + "ring=" + mAmbientLightRingBuffer); } EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ, @@ -808,7 +1203,6 @@ public class AutomaticBrightnessController { if (mode == AUTO_BRIGHTNESS_MODE_IDLE || mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE) { switchModeAndShortTermModels(mode); - mLightSensorController.setIdleMode(isInIdleMode()); } else { resetShortTermModel(); mCurrentBrightnessMapper = mBrightnessMappingStrategyMap.get(mode); @@ -965,6 +1359,10 @@ public class AutomaticBrightnessController { updateAutoBrightness(true /*sendUpdate*/, false /*isManuallySet*/); break; + case MSG_UPDATE_AMBIENT_LUX: + updateAmbientLux(); + break; + case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE: collectBrightnessAdjustmentSample(); break; @@ -988,6 +1386,22 @@ public class AutomaticBrightnessController { } } + private final SensorEventListener mLightSensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + if (mLightSensorEnabled) { + final long time = mClock.uptimeMillis(); + final float lux = event.values[0]; + handleLightSensorEvent(time, lux); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Not used. + } + }; + // Call back whenever the tasks stack changes, which includes tasks being created, removed, and // moving to top. class TaskStackListenerImpl extends TaskStackListener { @@ -1002,13 +1416,192 @@ public 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. + * + * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted + * from oldest to newest. + */ + private static final class AmbientLightRingBuffer { + // Proportional extra capacity of the buffer beyond the expected number of light samples + // in the horizon + private static final float BUFFER_SLACK = 1.5f; + private float[] mRingLux; + private long[] mRingTime; + private int mCapacity; + + // The first valid element and the next open slot. + // Note that if mCount is zero then there are no valid elements. + private int mStart; + private int mEnd; + private int mCount; + Clock mClock; + + 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) { + return mRingLux[offsetOf(index)]; + } + + public float[] getAllLuxValues() { + float[] values = new float[mCount]; + if (mCount == 0) { + return values; + } + + if (mStart < mEnd) { + System.arraycopy(mRingLux, mStart, values, 0, mCount); + } else { + System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart); + System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd); + } + + return values; + } + + public long getTime(int index) { + return mRingTime[offsetOf(index)]; + } + + public long[] getAllTimestamps() { + long[] values = new long[mCount]; + if (mCount == 0) { + return values; + } + + if (mStart < mEnd) { + System.arraycopy(mRingTime, mStart, values, 0, mCount); + } else { + System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart); + System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd); + } + + return values; + } + + public void push(long time, float lux) { + int next = mEnd; + if (mCount == mCapacity) { + int newSize = mCapacity * 2; + + float[] newRingLux = new float[newSize]; + long[] newRingTime = new long[newSize]; + int length = mCapacity - mStart; + System.arraycopy(mRingLux, mStart, newRingLux, 0, length); + System.arraycopy(mRingTime, mStart, newRingTime, 0, length); + if (mStart != 0) { + System.arraycopy(mRingLux, 0, newRingLux, length, mStart); + System.arraycopy(mRingTime, 0, newRingTime, length, mStart); + } + mRingLux = newRingLux; + mRingTime = newRingTime; + + next = mCapacity; + mCapacity = newSize; + mStart = 0; + } + mRingTime[next] = time; + mRingLux[next] = lux; + mEnd = next + 1; + if (mEnd == mCapacity) { + mEnd = 0; + } + mCount++; + } + + public void prune(long horizon) { + if (mCount == 0) { + return; + } + + while (mCount > 1) { + int next = mStart + 1; + if (next >= mCapacity) { + next -= mCapacity; + } + if (mRingTime[next] > horizon) { + // Some light sensors only produce data upon a change in the ambient light + // levels, so we need to consider the previous measurement as the ambient light + // level for all points in time up until we receive a new measurement. Thus, we + // always want to keep the youngest element that would be removed from the + // buffer and just set its measurement time to the horizon time since at that + // point it is the ambient light level, and to remove it would be to drop a + // valid data point within our horizon. + break; + } + mStart = next; + mCount -= 1; + } + + if (mRingTime[mStart] < horizon) { + mRingTime[mStart] = horizon; + } + } + + public int size() { + return mCount; + } + + public void clear() { + mStart = 0; + mEnd = 0; + mCount = 0; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append('['); + for (int i = 0; i < mCount; i++) { + final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis(); + if (i != 0) { + buf.append(", "); + } + buf.append(getLux(i)); + buf.append(" / "); + buf.append(next - getTime(i)); + buf.append("ms"); + } + buf.append(']'); + return buf.toString(); + } + + private int offsetOf(int index) { + if (index >= mCount || index < 0) { + throw new ArrayIndexOutOfBoundsException(index); + } + index += mStart; + if (index >= mCapacity) { + index -= mCapacity; + } + return index; + } + } + public static class Injector { public Handler getBackgroundThreadHandler() { return BackgroundThread.getHandler(); } Clock createClock() { - return Clock.SYSTEM_CLOCK; + return SystemClock::uptimeMillis; } } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 0807cc056a39..90ad8c02c29c 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -81,7 +81,6 @@ import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.brightness.DisplayBrightnessController; -import com.android.server.display.brightness.LightSensorController; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; @@ -1049,13 +1048,102 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } if (defaultModeBrightnessMapper != null) { + // Ambient Lux - Active Mode Brightness Thresholds + float[] ambientBrighteningThresholds = + mDisplayDeviceConfig.getAmbientBrighteningPercentages(); + float[] ambientDarkeningThresholds = + mDisplayDeviceConfig.getAmbientDarkeningPercentages(); + float[] ambientBrighteningLevels = + mDisplayDeviceConfig.getAmbientBrighteningLevels(); + float[] ambientDarkeningLevels = + mDisplayDeviceConfig.getAmbientDarkeningLevels(); + float ambientDarkeningMinThreshold = + mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(); + float ambientBrighteningMinThreshold = + mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(); + HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels( + ambientBrighteningThresholds, ambientDarkeningThresholds, + ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold, + ambientBrighteningMinThreshold); + // Display - Active Mode Brightness Thresholds - HysteresisLevels screenBrightnessThresholds = - mInjector.getBrightnessThresholdsHysteresisLevels(mDisplayDeviceConfig); + float[] screenBrighteningThresholds = + mDisplayDeviceConfig.getScreenBrighteningPercentages(); + float[] screenDarkeningThresholds = + mDisplayDeviceConfig.getScreenDarkeningPercentages(); + float[] screenBrighteningLevels = + mDisplayDeviceConfig.getScreenBrighteningLevels(); + float[] screenDarkeningLevels = + mDisplayDeviceConfig.getScreenDarkeningLevels(); + float screenDarkeningMinThreshold = + mDisplayDeviceConfig.getScreenDarkeningMinThreshold(); + float screenBrighteningMinThreshold = + mDisplayDeviceConfig.getScreenBrighteningMinThreshold(); + HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels( + screenBrighteningThresholds, screenDarkeningThresholds, + screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold, + screenBrighteningMinThreshold, true); + + // Ambient Lux - Idle Screen Brightness Thresholds + float ambientDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(); + float ambientBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(); + float[] ambientBrighteningThresholdsIdle = + mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(); + float[] ambientDarkeningThresholdsIdle = + mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(); + float[] ambientBrighteningLevelsIdle = + mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(); + float[] ambientDarkeningLevelsIdle = + mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(); + HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels( + ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle, + ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle, + ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle); // Display - Idle Screen Brightness Thresholds - HysteresisLevels screenBrightnessThresholdsIdle = - mInjector.getBrightnessThresholdsIdleHysteresisLevels(mDisplayDeviceConfig); + float screenDarkeningMinThresholdIdle = + mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(); + float screenBrighteningMinThresholdIdle = + mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(); + float[] screenBrighteningThresholdsIdle = + mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(); + float[] screenDarkeningThresholdsIdle = + mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(); + float[] screenBrighteningLevelsIdle = + mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(); + float[] screenDarkeningLevelsIdle = + mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(); + HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels( + screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle, + screenBrighteningLevelsIdle, screenDarkeningLevelsIdle, + screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle); + + long brighteningLightDebounce = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounce(); + long darkeningLightDebounce = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounce(); + long brighteningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounceIdle(); + long darkeningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounceIdle(); + boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean( + R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); + + int lightSensorWarmUpTimeConfig = context.getResources().getInteger( + R.integer.config_lightSensorWarmupTime); + int lightSensorRate = context.getResources().getInteger( + R.integer.config_autoBrightnessLightSensorRate); + int initialLightSensorRate = context.getResources().getInteger( + R.integer.config_autoBrightnessInitialLightSensorRate); + if (initialLightSensorRate == -1) { + initialLightSensorRate = lightSensorRate; + } else if (initialLightSensorRate > lightSensorRate) { + Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate (" + + initialLightSensorRate + ") to be less than or equal to " + + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ")."); + } loadAmbientLightSensor(); // BrightnessTracker should only use one light sensor, we want to use the light sensor @@ -1067,15 +1155,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.stop(); } - - LightSensorController.LightSensorControllerConfig config = - mInjector.getLightSensorControllerConfig(context, mDisplayDeviceConfig); mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController( - this, handler.getLooper(), mSensorManager, brightnessMappers, - PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, - screenBrightnessThresholds, screenBrightnessThresholdsIdle, - mContext, mBrightnessRangeController, - mBrightnessThrottler, userLux, userNits, mDisplayId, config, + this, handler.getLooper(), mSensorManager, mLightSensor, + brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, lightSensorRate, + initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, + brighteningLightDebounceIdle, darkeningLightDebounceIdle, + autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, + screenBrightnessThresholds, ambientBrightnessThresholdsIdle, + screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, + mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(), + mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits, mBrightnessClamperController); mDisplayBrightnessController.setAutomaticBrightnessController( mAutomaticBrightnessController); @@ -3075,34 +3165,32 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call AutomaticBrightnessController getAutomaticBrightnessController( AutomaticBrightnessController.Callbacks callbacks, Looper looper, - SensorManager sensorManager, + SensorManager sensorManager, Sensor lightSensor, SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap, - float brightnessMin, float brightnessMax, float dozeScaleFactor, + int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, + float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, + long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, + boolean resetAmbientLuxAfterWarmUpConfig, + HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, BrightnessRangeController brightnessModeController, - BrightnessThrottler brightnessThrottler, float userLux, float userNits, - int displayId, LightSensorController.LightSensorControllerConfig config, + BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort, + int ambientLightHorizonLong, float userLux, float userNits, BrightnessClamperController brightnessClamperController) { - return new AutomaticBrightnessController(callbacks, looper, sensorManager, - brightnessMappingStrategyMap, brightnessMin, brightnessMax, dozeScaleFactor, - screenBrightnessThresholds, screenBrightnessThresholdsIdle, context, - brightnessModeController, brightnessThrottler, userLux, userNits, displayId, - config, brightnessClamperController); - } - - LightSensorController.LightSensorControllerConfig getLightSensorControllerConfig( - Context context, DisplayDeviceConfig displayDeviceConfig) { - return LightSensorController.LightSensorControllerConfig.create( - context.getResources(), displayDeviceConfig); - } - HysteresisLevels getBrightnessThresholdsIdleHysteresisLevels(DisplayDeviceConfig ddc) { - return HysteresisLevels.getBrightnessThresholdsIdle(ddc); - } - - HysteresisLevels getBrightnessThresholdsHysteresisLevels(DisplayDeviceConfig ddc) { - return HysteresisLevels.getBrightnessThresholds(ddc); + return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor, + brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, + brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, + brighteningLightDebounceConfig, darkeningLightDebounceConfig, + brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle, + resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, + screenBrightnessThresholds, ambientBrightnessThresholdsIdle, + screenBrightnessThresholdsIdle, context, brightnessModeController, + brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux, + userNits, brightnessClamperController); } BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context, @@ -3112,6 +3200,25 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController); } + HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, + float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, + float[] darkeningThresholdLevels, float minDarkeningThreshold, + float minBrighteningThreshold) { + return new HysteresisLevels(brighteningThresholdsPercentages, + darkeningThresholdsPercentages, brighteningThresholdLevels, + darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold); + } + + HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, + float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, + float[] darkeningThresholdLevels, float minDarkeningThreshold, + float minBrighteningThreshold, boolean potentialOldBrightnessRange) { + return new HysteresisLevels(brighteningThresholdsPercentages, + darkeningThresholdsPercentages, brighteningThresholdLevels, + darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold, + potentialOldBrightnessRange); + } + ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController( SensorManager sensorManager, Sensor lightSensor, diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java index bb349e76857c..0521b8ac4f3b 100644 --- a/services/core/java/com/android/server/display/HysteresisLevels.java +++ b/services/core/java/com/android/server/display/HysteresisLevels.java @@ -18,7 +18,6 @@ package com.android.server.display; import android.util.Slog; -import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.utils.DebugUtils; import java.io.PrintWriter; @@ -53,8 +52,7 @@ public class HysteresisLevels { * @param potentialOldBrightnessRange whether or not the values used could be from the old * screen brightness range ie, between 1-255. */ - @VisibleForTesting - public HysteresisLevels(float[] brighteningThresholdsPercentages, + HysteresisLevels(float[] brighteningThresholdsPercentages, float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, float[] darkeningThresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold, @@ -140,10 +138,7 @@ public class HysteresisLevels { return levelArray; } - /** - * Print the object's debug information into the given stream. - */ - public void dump(PrintWriter pw) { + void dump(PrintWriter pw) { pw.println("HysteresisLevels"); pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels)); pw.println(" mBrighteningThresholdsPercentages=" @@ -154,45 +149,4 @@ public class HysteresisLevels { + Arrays.toString(mDarkeningThresholdsPercentages)); pw.println(" mMinDarkening=" + mMinDarkening); } - - - /** - * Creates hysteresis levels for Active Ambient Lux - */ - public static HysteresisLevels getAmbientBrightnessThresholds(DisplayDeviceConfig ddc) { - return new HysteresisLevels(ddc.getAmbientBrighteningPercentages(), - ddc.getAmbientDarkeningPercentages(), ddc.getAmbientBrighteningLevels(), - ddc.getAmbientDarkeningLevels(), ddc.getAmbientLuxDarkeningMinThreshold(), - ddc.getAmbientLuxBrighteningMinThreshold()); - } - - /** - * Creates hysteresis levels for Active Screen Brightness - */ - public static HysteresisLevels getBrightnessThresholds(DisplayDeviceConfig ddc) { - return new HysteresisLevels(ddc.getScreenBrighteningPercentages(), - ddc.getScreenDarkeningPercentages(), ddc.getScreenBrighteningLevels(), - ddc.getScreenDarkeningLevels(), ddc.getScreenDarkeningMinThreshold(), - ddc.getScreenBrighteningMinThreshold(), true); - } - - /** - * Creates hysteresis levels for Idle Ambient Lux - */ - public static HysteresisLevels getAmbientBrightnessThresholdsIdle(DisplayDeviceConfig ddc) { - return new HysteresisLevels(ddc.getAmbientBrighteningPercentagesIdle(), - ddc.getAmbientDarkeningPercentagesIdle(), ddc.getAmbientBrighteningLevelsIdle(), - ddc.getAmbientDarkeningLevelsIdle(), ddc.getAmbientLuxDarkeningMinThresholdIdle(), - ddc.getAmbientLuxBrighteningMinThresholdIdle()); - } - - /** - * Creates hysteresis levels for Idle Screen Brightness - */ - public static HysteresisLevels getBrightnessThresholdsIdle(DisplayDeviceConfig ddc) { - return new HysteresisLevels(ddc.getScreenBrighteningPercentagesIdle(), - ddc.getScreenDarkeningPercentagesIdle(), ddc.getScreenBrighteningLevelsIdle(), - ddc.getScreenDarkeningLevelsIdle(), ddc.getScreenDarkeningMinThresholdIdle(), - ddc.getScreenBrighteningMinThresholdIdle()); - } } diff --git a/services/core/java/com/android/server/display/brightness/LightSensorController.java b/services/core/java/com/android/server/display/brightness/LightSensorController.java deleted file mode 100644 index d82d6983c3ba..000000000000 --- a/services/core/java/com/android/server/display/brightness/LightSensorController.java +++ /dev/null @@ -1,868 +0,0 @@ -/* - * Copyright (C) 2024 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.brightness; - -import static com.android.server.display.BrightnessMappingStrategy.INVALID_LUX; - -import android.annotation.Nullable; -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.PowerManager; -import android.os.Trace; -import android.util.Slog; -import android.util.TimeUtils; -import android.view.Display; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.Clock; -import com.android.server.display.DisplayDeviceConfig; -import com.android.server.display.HysteresisLevels; -import com.android.server.display.config.SensorData; -import com.android.server.display.utils.SensorUtils; - -import java.io.PrintWriter; - -/** - * Manages light sensor subscription and notifies its listeners about ambient lux changes based on - * configuration - */ -public class LightSensorController { - // How long the current sensor reading is assumed to be valid beyond the current time. - // This provides a bit of prediction, as well as ensures that the weight for the last sample is - // non-zero, which in turn ensures that the total weight is non-zero. - private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100; - - // Proportional extra capacity of the buffer beyond the expected number of light samples - // in the horizon - private static final float BUFFER_SLACK = 1.5f; - - private boolean mLoggingEnabled; - private boolean mLightSensorEnabled; - private long mLightSensorEnableTime; - // The current light sensor event rate in milliseconds. - private int mCurrentLightSensorRate = -1; - // The number of light samples collected since the light sensor was enabled. - private int mRecentLightSamples; - private float mAmbientLux; - // True if mAmbientLux holds a valid value. - private boolean mAmbientLuxValid; - // The last ambient lux value prior to passing the darkening or brightening threshold. - private float mPreThresholdLux; - // The most recent light sample. - private float mLastObservedLux = INVALID_LUX; - // The time of the most light recent sample. - private long mLastObservedLuxTime; - // The last calculated ambient light level (long time window). - private float mSlowAmbientLux; - // The last calculated ambient light level (short time window). - private float mFastAmbientLux; - private volatile boolean mIsIdleMode; - // The ambient light level threshold at which to brighten or darken the screen. - private float mAmbientBrighteningThreshold; - private float mAmbientDarkeningThreshold; - - private final LightSensorControllerConfig mConfig; - - // The light sensor, or null if not available or needed. - @Nullable - private final Sensor mLightSensor; - - // A ring buffer containing all of the recent ambient light sensor readings. - private final AmbientLightRingBuffer mAmbientLightRingBuffer; - - private final Injector mInjector; - - private final SensorEventListener mLightSensorListener = new SensorEventListener() { - @Override - public void onSensorChanged(SensorEvent event) { - if (mLightSensorEnabled) { - final long time = mClock.uptimeMillis(); - final float lux = event.values[0]; - handleLightSensorEvent(time, lux); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // Not used. - } - }; - - // Runnable used to delay ambient lux update when: - // 1) update triggered before configured warm up time - // 2) next brightening or darkening transition need to happen - private final Runnable mAmbientLuxUpdater = this::updateAmbientLux; - - private final Clock mClock; - - private final Handler mHandler; - - private final String mTag; - - private LightSensorListener mListener; - - public LightSensorController( - SensorManager sensorManager, - Looper looper, - int displayId, - LightSensorControllerConfig config) { - this(config, new RealInjector(sensorManager, displayId), new LightSensorHandler(looper)); - } - - @VisibleForTesting - LightSensorController( - LightSensorControllerConfig config, - Injector injector, - Handler handler) { - if (config.mNormalLightSensorRate <= 0) { - throw new IllegalArgumentException("lightSensorRate must be above 0"); - } - mInjector = injector; - int bufferInitialCapacity = (int) Math.ceil( - config.mAmbientLightHorizonLong * BUFFER_SLACK / config.mNormalLightSensorRate); - mClock = injector.getClock(); - mHandler = handler; - mAmbientLightRingBuffer = new AmbientLightRingBuffer(bufferInitialCapacity, mClock); - mConfig = config; - mLightSensor = mInjector.getLightSensor(mConfig); - mTag = mInjector.getTag(); - } - - public void setListener(LightSensorListener listener) { - mListener = listener; - } - - /** - * @return true if sensor registered, false if sensor already registered - */ - public boolean enableLightSensorIfNeeded() { - if (!mLightSensorEnabled) { - mLightSensorEnabled = true; - mLightSensorEnableTime = mClock.uptimeMillis(); - mCurrentLightSensorRate = mConfig.mInitialLightSensorRate; - mInjector.registerLightSensorListener( - mLightSensorListener, mLightSensor, mCurrentLightSensorRate, mHandler); - return true; - } - return false; - } - - /** - * @return true if sensor unregistered, false if sensor already unregistered - */ - public boolean disableLightSensorIfNeeded() { - if (mLightSensorEnabled) { - mLightSensorEnabled = false; - mAmbientLuxValid = !mConfig.mResetAmbientLuxAfterWarmUpConfig; - if (!mAmbientLuxValid) { - mPreThresholdLux = PowerManager.BRIGHTNESS_INVALID_FLOAT; - } - mRecentLightSamples = 0; - mAmbientLightRingBuffer.clear(); - mCurrentLightSensorRate = -1; - mInjector.unregisterLightSensorListener(mLightSensorListener); - return true; - } - return false; - } - - public void setLoggingEnabled(boolean loggingEnabled) { - mLoggingEnabled = loggingEnabled; - } - - /** - * Updates BrightnessEvent with LightSensorController details - */ - public void updateBrightnessEvent(BrightnessEvent brightnessEvent) { - brightnessEvent.setPreThresholdLux(mPreThresholdLux); - } - - /** - * Print the object's debug information into the given stream. - */ - public void dump(PrintWriter pw) { - pw.println("LightSensorController state:"); - pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); - pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime)); - pw.println(" mCurrentLightSensorRate=" + mCurrentLightSensorRate); - pw.println(" mRecentLightSamples=" + mRecentLightSamples); - pw.println(" mAmbientLux=" + mAmbientLux); - pw.println(" mAmbientLuxValid=" + mAmbientLuxValid); - pw.println(" mPreThresholdLux=" + mPreThresholdLux); - pw.println(" mLastObservedLux=" + mLastObservedLux); - pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime)); - pw.println(" mSlowAmbientLux=" + mSlowAmbientLux); - pw.println(" mFastAmbientLux=" + mFastAmbientLux); - pw.println(" mIsIdleMode=" + mIsIdleMode); - pw.println(" mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold); - pw.println(" mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold); - pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer); - pw.println(" mLightSensor=" + mLightSensor); - mConfig.dump(pw); - } - - /** - * This method should be called when this LightSensorController is no longer in use - * i.e. when corresponding display removed - */ - public void stop() { - mHandler.removeCallbacksAndMessages(null); - disableLightSensorIfNeeded(); - } - - public void setIdleMode(boolean isIdleMode) { - mIsIdleMode = isIdleMode; - } - - /** - * returns true if LightSensorController holds valid ambient lux value - */ - public boolean hasValidAmbientLux() { - return mAmbientLuxValid; - } - - /** - * returns all last observed sensor values - */ - public float[] getLastSensorValues() { - return mAmbientLightRingBuffer.getAllLuxValues(); - } - - /** - * returns all last observed sensor event timestamps - */ - public long[] getLastSensorTimestamps() { - return mAmbientLightRingBuffer.getAllTimestamps(); - } - - public float getLastObservedLux() { - return mLastObservedLux; - } - - private void handleLightSensorEvent(long time, float lux) { - Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux); - mHandler.removeCallbacks(mAmbientLuxUpdater); - - if (mAmbientLightRingBuffer.size() == 0) { - // switch to using the steady-state sample rate after grabbing the initial light sample - adjustLightSensorRate(mConfig.mNormalLightSensorRate); - } - applyLightSensorMeasurement(time, lux); - updateAmbientLux(time); - } - - private void applyLightSensorMeasurement(long time, float lux) { - mRecentLightSamples++; - mAmbientLightRingBuffer.prune(time - mConfig.mAmbientLightHorizonLong); - mAmbientLightRingBuffer.push(time, lux); - // Remember this sample value. - mLastObservedLux = lux; - mLastObservedLuxTime = time; - } - - private void adjustLightSensorRate(int lightSensorRate) { - // if the light sensor rate changed, update the sensor listener - if (lightSensorRate != mCurrentLightSensorRate) { - if (mLoggingEnabled) { - Slog.d(mTag, "adjustLightSensorRate: " - + "previousRate=" + mCurrentLightSensorRate + ", " - + "currentRate=" + lightSensorRate); - } - mCurrentLightSensorRate = lightSensorRate; - mInjector.unregisterLightSensorListener(mLightSensorListener); - mInjector.registerLightSensorListener( - mLightSensorListener, mLightSensor, lightSensorRate, mHandler); - } - } - - private void setAmbientLux(float lux) { - if (mLoggingEnabled) { - Slog.d(mTag, "setAmbientLux(" + lux + ")"); - } - if (lux < 0) { - Slog.w(mTag, "Ambient lux was negative, ignoring and setting to 0"); - lux = 0; - } - mAmbientLux = lux; - - if (mIsIdleMode) { - mAmbientBrighteningThreshold = - mConfig.mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux); - mAmbientDarkeningThreshold = - mConfig.mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux); - } else { - mAmbientBrighteningThreshold = - mConfig.mAmbientBrightnessThresholds.getBrighteningThreshold(lux); - mAmbientDarkeningThreshold = - mConfig.mAmbientBrightnessThresholds.getDarkeningThreshold(lux); - } - - mListener.onAmbientLuxChange(mAmbientLux); - } - - private float calculateAmbientLux(long now, long horizon) { - if (mLoggingEnabled) { - Slog.d(mTag, "calculateAmbientLux(" + now + ", " + horizon + ")"); - } - final int size = mAmbientLightRingBuffer.size(); - if (size == 0) { - Slog.e(mTag, "calculateAmbientLux: No ambient light readings available"); - return -1; - } - - // Find the first measurement that is just outside of the horizon. - int endIndex = 0; - final long horizonStartTime = now - horizon; - for (int i = 0; i < size - 1; i++) { - if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) { - endIndex++; - } else { - break; - } - } - if (mLoggingEnabled) { - Slog.d(mTag, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" - + mAmbientLightRingBuffer.getTime(endIndex) + ", " - + mAmbientLightRingBuffer.getLux(endIndex) + ")"); - } - float sum = 0; - float totalWeight = 0; - long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS; - for (int i = size - 1; i >= endIndex; i--) { - long eventTime = mAmbientLightRingBuffer.getTime(i); - if (i == endIndex && eventTime < horizonStartTime) { - // If we're at the final value, make sure we only consider the part of the sample - // within our desired horizon. - eventTime = horizonStartTime; - } - final long startTime = eventTime - now; - float weight = calculateWeight(startTime, endTime); - float lux = mAmbientLightRingBuffer.getLux(i); - if (mLoggingEnabled) { - Slog.d(mTag, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " - + "lux=" + lux + ", " - + "weight=" + weight); - } - totalWeight += weight; - sum += lux * weight; - endTime = startTime; - } - if (mLoggingEnabled) { - Slog.d(mTag, "calculateAmbientLux: " - + "totalWeight=" + totalWeight + ", " - + "newAmbientLux=" + (sum / totalWeight)); - } - return sum / totalWeight; - } - - private float calculateWeight(long startDelta, long endDelta) { - return weightIntegral(endDelta) - weightIntegral(startDelta); - } - - // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the - // horizon we're looking at and provides a non-linear weighting for light samples. - private float weightIntegral(long x) { - return x * (x * 0.5f + mConfig.mWeightingIntercept); - } - - private long nextAmbientLightBrighteningTransition(long time) { - final int size = mAmbientLightRingBuffer.size(); - long earliestValidTime = time; - for (int i = size - 1; i >= 0; i--) { - if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) { - break; - } - earliestValidTime = mAmbientLightRingBuffer.getTime(i); - } - return earliestValidTime + (mIsIdleMode ? mConfig.mBrighteningLightDebounceConfigIdle - : mConfig.mBrighteningLightDebounceConfig); - } - - private long nextAmbientLightDarkeningTransition(long time) { - final int size = mAmbientLightRingBuffer.size(); - long earliestValidTime = time; - for (int i = size - 1; i >= 0; i--) { - if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) { - break; - } - earliestValidTime = mAmbientLightRingBuffer.getTime(i); - } - return earliestValidTime + (mIsIdleMode ? mConfig.mDarkeningLightDebounceConfigIdle - : mConfig.mDarkeningLightDebounceConfig); - } - - private void updateAmbientLux() { - long time = mClock.uptimeMillis(); - mAmbientLightRingBuffer.prune(time - mConfig.mAmbientLightHorizonLong); - updateAmbientLux(time); - } - - private void updateAmbientLux(long time) { - // If the light sensor was just turned on then immediately update our initial - // estimate of the current ambient light level. - if (!mAmbientLuxValid) { - final long timeWhenSensorWarmedUp = - mConfig.mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; - if (time < timeWhenSensorWarmedUp) { - if (mLoggingEnabled) { - Slog.d(mTag, "updateAmbientLux: Sensor not ready yet: " - + "time=" + time + ", " - + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); - } - mHandler.postAtTime(mAmbientLuxUpdater, timeWhenSensorWarmedUp); - return; - } - mAmbientLuxValid = true; - setAmbientLux(calculateAmbientLux(time, mConfig.mAmbientLightHorizonShort)); - if (mLoggingEnabled) { - Slog.d(mTag, "updateAmbientLux: Initializing: " - + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " - + "mAmbientLux=" + mAmbientLux); - } - } - - long nextBrightenTransition = nextAmbientLightBrighteningTransition(time); - long nextDarkenTransition = nextAmbientLightDarkeningTransition(time); - // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term - // change in lighting conditions, and a fast ambient lux to determine what the new - // brightness situation is since the slow lux can be quite slow to converge. - // - // Note that both values need to be checked for sufficient change before updating the - // 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. - mSlowAmbientLux = calculateAmbientLux(time, mConfig.mAmbientLightHorizonLong); - mFastAmbientLux = calculateAmbientLux(time, mConfig.mAmbientLightHorizonShort); - - if ((mSlowAmbientLux >= mAmbientBrighteningThreshold - && mFastAmbientLux >= mAmbientBrighteningThreshold - && nextBrightenTransition <= time) - || (mSlowAmbientLux <= mAmbientDarkeningThreshold - && mFastAmbientLux <= mAmbientDarkeningThreshold - && nextDarkenTransition <= time)) { - mPreThresholdLux = mAmbientLux; - setAmbientLux(mFastAmbientLux); - if (mLoggingEnabled) { - Slog.d(mTag, "updateAmbientLux: " - + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " - + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", " - + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", " - + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " - + "mAmbientLux=" + mAmbientLux); - } - nextBrightenTransition = nextAmbientLightBrighteningTransition(time); - nextDarkenTransition = nextAmbientLightDarkeningTransition(time); - } - long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition); - // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't - // exceed the necessary threshold, then it's possible we'll get a transition time prior to - // now. Rather than continually checking to see whether the weighted lux exceeds the - // threshold, schedule an update for when we'd normally expect another light sample, which - // should be enough time to decide whether we should actually transition to the new - // weighted ambient lux or not. - nextTransitionTime = nextTransitionTime > time ? nextTransitionTime - : time + mConfig.mNormalLightSensorRate; - if (mLoggingEnabled) { - Slog.d(mTag, "updateAmbientLux: Scheduling ambient lux update for " - + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime)); - } - mHandler.postAtTime(mAmbientLuxUpdater, nextTransitionTime); - } - - public interface LightSensorListener { - /** - * Called when new ambient lux value is ready - */ - void onAmbientLuxChange(float ambientLux); - } - - private static final class LightSensorHandler extends Handler { - private LightSensorHandler(Looper looper) { - super(looper, /* callback= */ null, /* async= */ true); - } - } - - /** - * A ring buffer of ambient light measurements sorted by time. - * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted - * from oldest to newest. - */ - @VisibleForTesting - static final class AmbientLightRingBuffer { - - private float[] mRingLux; - private long[] mRingTime; - private int mCapacity; - - // The first valid element and the next open slot. - // Note that if mCount is zero then there are no valid elements. - private int mStart; - private int mEnd; - private int mCount; - - private final Clock mClock; - - @VisibleForTesting - AmbientLightRingBuffer(int initialCapacity, Clock clock) { - mCapacity = initialCapacity; - mRingLux = new float[mCapacity]; - mRingTime = new long[mCapacity]; - mClock = clock; - - } - - @VisibleForTesting - float getLux(int index) { - return mRingLux[offsetOf(index)]; - } - - @VisibleForTesting - float[] getAllLuxValues() { - float[] values = new float[mCount]; - if (mCount == 0) { - return values; - } - - if (mStart < mEnd) { - System.arraycopy(mRingLux, mStart, values, 0, mCount); - } else { - System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart); - System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd); - } - - return values; - } - - @VisibleForTesting - long getTime(int index) { - return mRingTime[offsetOf(index)]; - } - - @VisibleForTesting - long[] getAllTimestamps() { - long[] values = new long[mCount]; - if (mCount == 0) { - return values; - } - - if (mStart < mEnd) { - System.arraycopy(mRingTime, mStart, values, 0, mCount); - } else { - System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart); - System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd); - } - - return values; - } - - @VisibleForTesting - void push(long time, float lux) { - int next = mEnd; - if (mCount == mCapacity) { - int newSize = mCapacity * 2; - - float[] newRingLux = new float[newSize]; - long[] newRingTime = new long[newSize]; - int length = mCapacity - mStart; - System.arraycopy(mRingLux, mStart, newRingLux, 0, length); - System.arraycopy(mRingTime, mStart, newRingTime, 0, length); - if (mStart != 0) { - System.arraycopy(mRingLux, 0, newRingLux, length, mStart); - System.arraycopy(mRingTime, 0, newRingTime, length, mStart); - } - mRingLux = newRingLux; - mRingTime = newRingTime; - - next = mCapacity; - mCapacity = newSize; - mStart = 0; - } - mRingTime[next] = time; - mRingLux[next] = lux; - mEnd = next + 1; - if (mEnd == mCapacity) { - mEnd = 0; - } - mCount++; - } - - @VisibleForTesting - void prune(long horizon) { - if (mCount == 0) { - return; - } - - while (mCount > 1) { - int next = mStart + 1; - if (next >= mCapacity) { - next -= mCapacity; - } - if (mRingTime[next] > horizon) { - // Some light sensors only produce data upon a change in the ambient light - // levels, so we need to consider the previous measurement as the ambient light - // level for all points in time up until we receive a new measurement. Thus, we - // always want to keep the youngest element that would be removed from the - // buffer and just set its measurement time to the horizon time since at that - // point it is the ambient light level, and to remove it would be to drop a - // valid data point within our horizon. - break; - } - mStart = next; - mCount -= 1; - } - - if (mRingTime[mStart] < horizon) { - mRingTime[mStart] = horizon; - } - } - - @VisibleForTesting - int size() { - return mCount; - } - - @VisibleForTesting - void clear() { - mStart = 0; - mEnd = 0; - mCount = 0; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append('['); - for (int i = 0; i < mCount; i++) { - final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis(); - if (i != 0) { - buf.append(", "); - } - buf.append(getLux(i)); - buf.append(" / "); - buf.append(next - getTime(i)); - buf.append("ms"); - } - buf.append(']'); - return buf.toString(); - } - - private int offsetOf(int index) { - if (index >= mCount || index < 0) { - throw new ArrayIndexOutOfBoundsException(index); - } - index += mStart; - if (index >= mCapacity) { - index -= mCapacity; - } - return index; - } - } - - @VisibleForTesting - interface Injector { - Clock getClock(); - - Sensor getLightSensor(LightSensorControllerConfig config); - - boolean registerLightSensorListener( - SensorEventListener listener, Sensor sensor, int rate, Handler handler); - - void unregisterLightSensorListener(SensorEventListener listener); - - String getTag(); - - } - - private static class RealInjector implements Injector { - private final SensorManager mSensorManager; - private final int mSensorFallbackType; - - private final String mTag; - - private RealInjector(SensorManager sensorManager, int displayId) { - mSensorManager = sensorManager; - mSensorFallbackType = displayId == Display.DEFAULT_DISPLAY - ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK; - mTag = "LightSensorController [" + displayId + "]"; - } - - @Override - public Clock getClock() { - return Clock.SYSTEM_CLOCK; - } - - @Override - public Sensor getLightSensor(LightSensorControllerConfig config) { - return SensorUtils.findSensor( - mSensorManager, config.mAmbientLightSensor, mSensorFallbackType); - } - - @Override - public boolean registerLightSensorListener( - SensorEventListener listener, Sensor sensor, int rate, Handler handler) { - return mSensorManager.registerListener(listener, sensor, rate * 1000, handler); - } - - @Override - public void unregisterLightSensorListener(SensorEventListener listener) { - mSensorManager.unregisterListener(listener); - } - - @Override - public String getTag() { - return mTag; - } - } - - public static class LightSensorControllerConfig { - // Steady-state light sensor event rate in milliseconds. - private final int mNormalLightSensorRate; - private final int mInitialLightSensorRate; - - // If true immediately after the screen is turned on the controller will try to adjust the - // brightness based on the current sensor reads. If false, the controller will collect - // more data - // and only then decide whether to change brightness. - private final boolean mResetAmbientLuxAfterWarmUpConfig; - - // Period of time in which to consider light samples for a short/long-term estimate of - // ambient - // light in milliseconds. - private final int mAmbientLightHorizonShort; - private final int mAmbientLightHorizonLong; - - - // Amount of time to delay auto-brightness after screen on while waiting for - // the light sensor to warm-up in milliseconds. - // May be 0 if no warm-up is required. - private final int mLightSensorWarmUpTimeConfig; - - - // The intercept used for the weighting calculation. This is used in order to keep all - // possible - // weighting values positive. - private final int mWeightingIntercept; - - // Configuration object for determining thresholds to change brightness dynamically - private final HysteresisLevels mAmbientBrightnessThresholds; - private final HysteresisLevels mAmbientBrightnessThresholdsIdle; - - - // Stability requirements in milliseconds for accepting a new brightness level. This is - // used - // for debouncing the light sensor. Different constants are used to debounce the light - // sensor - // when adapting to brighter or darker environments. This parameter controls how quickly - // brightness changes occur in response to an observed change in light level that exceeds - // the - // hysteresis threshold. - private final long mBrighteningLightDebounceConfig; - private final long mDarkeningLightDebounceConfig; - private final long mBrighteningLightDebounceConfigIdle; - private final long mDarkeningLightDebounceConfigIdle; - - private final SensorData mAmbientLightSensor; - - @VisibleForTesting - LightSensorControllerConfig(int initialLightSensorRate, int normalLightSensorRate, - boolean resetAmbientLuxAfterWarmUpConfig, int ambientLightHorizonShort, - int ambientLightHorizonLong, int lightSensorWarmUpTimeConfig, - int weightingIntercept, HysteresisLevels ambientBrightnessThresholds, - HysteresisLevels ambientBrightnessThresholdsIdle, - long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, - long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, - SensorData ambientLightSensor) { - mInitialLightSensorRate = initialLightSensorRate; - mNormalLightSensorRate = normalLightSensorRate; - mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; - mAmbientLightHorizonShort = ambientLightHorizonShort; - mAmbientLightHorizonLong = ambientLightHorizonLong; - mLightSensorWarmUpTimeConfig = lightSensorWarmUpTimeConfig; - mWeightingIntercept = weightingIntercept; - mAmbientBrightnessThresholds = ambientBrightnessThresholds; - mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle; - mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; - mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; - mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle; - mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle; - mAmbientLightSensor = ambientLightSensor; - } - - private void dump(PrintWriter pw) { - pw.println("LightSensorControllerConfig:"); - pw.println(" mInitialLightSensorRate=" + mInitialLightSensorRate); - pw.println(" mNormalLightSensorRate=" + mNormalLightSensorRate); - pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); - pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort); - pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong); - pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); - pw.println(" mWeightingIntercept=" + mWeightingIntercept); - pw.println(" mAmbientBrightnessThresholds="); - mAmbientBrightnessThresholds.dump(pw); - pw.println(" mAmbientBrightnessThresholdsIdle="); - mAmbientBrightnessThresholdsIdle.dump(pw); - pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); - pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); - pw.println( - " mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle); - pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle); - pw.println(" mAmbientLightSensor=" + mAmbientLightSensor); - } - - /** - * Creates LightSensorControllerConfig object form Resources and DisplayDeviceConfig - */ - public static LightSensorControllerConfig create(Resources res, DisplayDeviceConfig ddc) { - int lightSensorRate = res.getInteger(R.integer.config_autoBrightnessLightSensorRate); - int initialLightSensorRate = res.getInteger( - R.integer.config_autoBrightnessInitialLightSensorRate); - if (initialLightSensorRate == -1) { - initialLightSensorRate = lightSensorRate; - } else if (initialLightSensorRate > lightSensorRate) { - Slog.w("LightSensorControllerConfig", - "Expected config_autoBrightnessInitialLightSensorRate (" - + initialLightSensorRate + ") to be less than or equal to " - + "config_autoBrightnessLightSensorRate (" + lightSensorRate - + ")."); - } - - boolean resetAmbientLuxAfterWarmUp = res.getBoolean( - R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); - int lightSensorWarmUpTimeConfig = res.getInteger( - R.integer.config_lightSensorWarmupTime); - - return new LightSensorControllerConfig(initialLightSensorRate, lightSensorRate, - resetAmbientLuxAfterWarmUp, ddc.getAmbientHorizonShort(), - ddc.getAmbientHorizonLong(), lightSensorWarmUpTimeConfig, - ddc.getAmbientHorizonLong(), - HysteresisLevels.getAmbientBrightnessThresholds(ddc), - HysteresisLevels.getAmbientBrightnessThresholdsIdle(ddc), - ddc.getAutoBrightnessBrighteningLightDebounce(), - ddc.getAutoBrightnessDarkeningLightDebounce(), - ddc.getAutoBrightnessBrighteningLightDebounceIdle(), - ddc.getAutoBrightnessDarkeningLightDebounceIdle(), - ddc.getAmbientLightSensor() - ); - } - } -} diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index dd8757236a36..54de64e2f3a8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -22,7 +22,9 @@ import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIG import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE; +import static org.junit.Assert.assertArrayEquals; 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; @@ -36,6 +38,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.hardware.Sensor; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; import android.os.PowerManager; @@ -47,8 +52,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.os.Clock; -import com.android.server.display.brightness.LightSensorController; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.testutils.OffsettableClock; @@ -58,6 +61,7 @@ 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 @@ -65,18 +69,31 @@ import org.mockito.MockitoAnnotations; public class AutomaticBrightnessControllerTest { private static final float BRIGHTNESS_MIN_FLOAT = 0.0f; private static final float BRIGHTNESS_MAX_FLOAT = 1.0f; + 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 = 2000; + private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000; + private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000; + private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000; private static final float DOZE_SCALE_FACTOR = 0.54f; + 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; + private Sensor mLightSensor; + @Mock SensorManager mSensorManager; @Mock BrightnessMappingStrategy mBrightnessMappingStrategy; @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy; @Mock BrightnessMappingStrategy mDozeBrightnessMappingStrategy; + @Mock HysteresisLevels mAmbientBrightnessThresholds; @Mock HysteresisLevels mScreenBrightnessThresholds; + @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle; @Mock HysteresisLevels mScreenBrightnessThresholdsIdle; @Mock Handler mNoOpHandler; @Mock BrightnessRangeController mBrightnessRangeController; @@ -84,18 +101,17 @@ public class AutomaticBrightnessControllerTest { BrightnessClamperController mBrightnessClamperController; @Mock BrightnessThrottler mBrightnessThrottler; - @Mock - LightSensorController mLightSensorController; - @Before public void setUp() throws Exception { // Share classloader to allow package private access. System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); + mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor"); mContext = InstrumentationRegistry.getContext(); setupController(BrightnessMappingStrategy.INVALID_LUX, - BrightnessMappingStrategy.INVALID_NITS); + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ false, + /* useHorizon= */ true); } @After @@ -107,7 +123,8 @@ public class AutomaticBrightnessControllerTest { } } - private void setupController(float userLux, float userNits) { + private void setupController(float userLux, float userNits, boolean applyDebounce, + boolean useHorizon) { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); @@ -130,22 +147,25 @@ public class AutomaticBrightnessControllerTest { } @Override - Clock createClock() { - return new Clock() { - @Override - public long uptimeMillis() { - return mClock.now(); - } - }; + AutomaticBrightnessController.Clock createClock() { + return mClock::now; } }, // pass in test looper instead, pass in offsettable clock - () -> { }, mTestLooper.getLooper(), - brightnessMappingStrategyMap, BRIGHTNESS_MIN_FLOAT, - BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, mScreenBrightnessThresholds, - mScreenBrightnessThresholdsIdle, + () -> { }, mTestLooper.getLooper(), mSensorManager, mLightSensor, + brightnessMappingStrategyMap, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT, + BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, + INITIAL_LIGHT_SENSOR_RATE, applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG : 0, + applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG : 0, + applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0, + applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0, + RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, + mAmbientBrightnessThresholds, mScreenBrightnessThresholds, + mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, mBrightnessThrottler, - userLux, userNits, mLightSensorController, mBrightnessClamperController + useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1, + useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits, + mBrightnessClamperController ); when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn( @@ -166,15 +186,20 @@ public class AutomaticBrightnessControllerTest { @Test public void testNoHysteresisAtMinBrightness() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.02f as a brightness value float lux1 = 100.0f; // Brightness as float (from 0.0f to 1.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); @@ -185,31 +210,39 @@ public class AutomaticBrightnessControllerTest { .thenReturn(1.0f); // Send new sensor value and verify - listener.onAmbientLuxChange(lux1); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1)); assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON); // Set up system to return 0.0f (minimum possible brightness) 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.onAmbientLuxChange(lux2); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2)); assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON); } @Test public void testNoHysteresisAtMaxBrightness() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.98f 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); @@ -220,30 +253,35 @@ public class AutomaticBrightnessControllerTest { .thenReturn(1.1f); // Send new sensor value and verify - listener.onAmbientLuxChange(lux1); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1)); assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON); // Set up system to return 1.0f as a brightness value (brightness_max) 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.onAmbientLuxChange(lux2); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2)); assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON); } @Test public void testUserAddUserDataPoint() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, @@ -260,11 +298,12 @@ public class AutomaticBrightnessControllerTest { public void testRecalculateSplines() throws Exception { // Enabling the light sensor, and setting the ambient lux to 1000 int currentLux = 1000; - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); - listener.onAmbientLuxChange(currentLux); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, currentLux)); // User sets brightness to 0.5f when(mBrightnessMappingStrategy.getBrightness(currentLux, @@ -294,13 +333,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testShortTermModelTimesOut() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0, @@ -314,7 +354,7 @@ public class AutomaticBrightnessControllerTest { 123f, 0.5f)).thenReturn(true); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); mTestLooper.moveTimeForward( mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000); mTestLooper.dispatchAll(); @@ -333,13 +373,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testShortTermModelDoesntTimeOut() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, 0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */, @@ -358,7 +399,7 @@ public class AutomaticBrightnessControllerTest { mTestLooper.dispatchAll(); // Sensor reads 100000 lux, - listener.onAmbientLuxChange(678910); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910)); mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT); // Verify short term model is not reset. @@ -372,13 +413,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0, @@ -398,7 +440,7 @@ public class AutomaticBrightnessControllerTest { 123f, 0.5f)).thenReturn(true); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); mTestLooper.moveTimeForward( mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000); mTestLooper.dispatchAll(); @@ -417,13 +459,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testShortTermModelNotRestoredAfterTimeout() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0, @@ -445,7 +488,7 @@ public class AutomaticBrightnessControllerTest { 123f, 0.5f)).thenReturn(true); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); // Do not fast-forward time. mTestLooper.dispatchAll(); @@ -463,13 +506,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testSwitchBetweenModesNoUserInteractions() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 123 lux, - listener.onAmbientLuxChange(123); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L); when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn( PowerManager.BRIGHTNESS_INVALID_FLOAT); @@ -485,7 +529,7 @@ public class AutomaticBrightnessControllerTest { BrightnessMappingStrategy.INVALID_LUX); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); // Do not fast-forward time. mTestLooper.dispatchAll(); @@ -501,19 +545,14 @@ public class AutomaticBrightnessControllerTest { @Test public void testSwitchToIdleMappingStrategy() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); - clearInvocations(mBrightnessMappingStrategy); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Sensor reads 1000 lux, - listener.onAmbientLuxChange(1000); - - - verify(mBrightnessMappingStrategy).getBrightness(anyFloat(), any(), anyInt()); - - clearInvocations(mBrightnessMappingStrategy); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); // User sets brightness to 100 mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, @@ -522,19 +561,22 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // There should be a user data point added to the mapper. - verify(mBrightnessMappingStrategy).addUserDataPoint(/* lux= */ 1000f, + verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f, /* brightness= */ 0.5f); - verify(mBrightnessMappingStrategy).setBrightnessConfiguration(any()); - verify(mBrightnessMappingStrategy).getBrightness(anyFloat(), any(), anyInt()); + verify(mBrightnessMappingStrategy, times(2)).setBrightnessConfiguration(any()); + verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt()); - clearInvocations(mBrightnessMappingStrategy); // Now let's do the same for idle mode mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE); - - verify(mBrightnessMappingStrategy).getMode(); - verify(mBrightnessMappingStrategy).getShortTermModelTimeout(); - verify(mBrightnessMappingStrategy).getUserBrightness(); - verify(mBrightnessMappingStrategy).getUserLux(); + // Called once when switching, + // setAmbientLux() is called twice and once in updateAutoBrightness(), + // nextAmbientLightBrighteningTransition() and nextAmbientLightDarkeningTransition() are + // called twice each. + verify(mBrightnessMappingStrategy, times(8)).getMode(); + // Called when switching. + verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout(); + verify(mBrightnessMappingStrategy, times(1)).getUserBrightness(); + verify(mBrightnessMappingStrategy, times(1)).getUserLux(); // Ensure, after switching, original BMS is not used anymore verifyNoMoreInteractions(mBrightnessMappingStrategy); @@ -546,25 +588,154 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // Ensure we use the correct mapping strategy - verify(mIdleBrightnessMappingStrategy).addUserDataPoint(/* lux= */ 1000f, + verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f, /* brightness= */ 0.5f); } @Test + public void testAmbientLightHorizon() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + 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(mLightSensor, 0)); + + // t = 500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + + // t = 1000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 1500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2000 + // ensure that our reading is at 0. + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // first 10000 lux sensor event reading + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 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(mLightSensor, 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(mLightSensor, 10000)); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 4000 + // stay high + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 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(mLightSensor, 10000)); + verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 5000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 5500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 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(mLightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // only the values within the horizon should be kept + assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(), + EPSILON); + assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000}, + mController.getLastSensorTimestamps()); + } + + @Test + public void testHysteresisLevels() { + float[] ambientBrighteningThresholds = {50, 100}; + float[] ambientDarkeningThresholds = {10, 20}; + float[] ambientThresholdLevels = {0, 500}; + float ambientDarkeningMinChangeThreshold = 3.0f; + float ambientBrighteningMinChangeThreshold = 1.5f; + HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds, + ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels, + ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold); + + // test low, activate minimum change thresholds. + assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON); + assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON); + assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON); + + // test max + // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater + assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2); + assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON); + + // test just below threshold + assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON); + assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON); + + // test at (considered above) threshold + assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON); + assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON); + } + + @Test public void testBrightnessGetsThrottled() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return max brightness at 100 lux final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT; final float lux = 100.0f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)) + .thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)) + .thenReturn(lux); when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt())) .thenReturn(normalizedBrightness); // Sensor reads 100 lux. We should get max brightness. - listener.onAmbientLuxChange(lux); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f); assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f); @@ -592,6 +763,94 @@ public class AutomaticBrightnessControllerTest { } @Test + public void testGetSensorReadings() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Choose values such that the ring buffer's capacity is extended and the buffer is pruned + int increment = 11; + int lux = 5000; + for (int i = 0; i < 1000; i++) { + lux += increment; + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + } + + int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1); + float[] sensorValues = mController.getLastSensorValues(); + long[] sensorTimestamps = mController.getLastSensorTimestamps(); + + // Only the values within the horizon should be kept + assertEquals(valuesCount, sensorValues.length); + assertEquals(valuesCount, sensorTimestamps.length); + + long sensorTimestamp = mClock.now(); + for (int i = valuesCount - 1; i >= 1; i--) { + assertEquals(lux, sensorValues[i], EPSILON); + assertEquals(sensorTimestamp, sensorTimestamps[i]); + lux -= increment; + sensorTimestamp -= increment; + } + assertEquals(lux, sensorValues[0], EPSILON); + assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]); + } + + @Test + public void testGetSensorReadingsFullBuffer() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + int initialCapacity = 150; + + // Choose values such that the ring buffer is pruned + int increment1 = 200; + int lux = 5000; + for (int i = 0; i < 20; i++) { + lux += increment1; + mClock.fastForward(increment1); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + } + + int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1); + + // Choose values such that the buffer becomes full + int increment2 = 1; + for (int i = 0; i < initialCapacity - valuesCount; i++) { + lux += increment2; + mClock.fastForward(increment2); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + } + + float[] sensorValues = mController.getLastSensorValues(); + long[] sensorTimestamps = mController.getLastSensorTimestamps(); + + // The buffer should be full + assertEquals(initialCapacity, sensorValues.length); + assertEquals(initialCapacity, sensorTimestamps.length); + + long sensorTimestamp = mClock.now(); + for (int i = initialCapacity - 1; i >= 1; i--) { + assertEquals(lux, sensorValues[i], EPSILON); + assertEquals(sensorTimestamp, sensorTimestamps[i]); + + if (i >= valuesCount) { + lux -= increment2; + sensorTimestamp -= increment2; + } else { + lux -= increment1; + sensorTimestamp -= increment1; + } + } + assertEquals(lux, sensorValues[0], EPSILON); + assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]); + } + + @Test public void testResetShortTermModelWhenConfigChanges() { when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true); @@ -616,22 +875,179 @@ public class AutomaticBrightnessControllerTest { float userNits = 500; float userBrightness = 0.3f; when(mBrightnessMappingStrategy.getBrightnessFromNits(userNits)).thenReturn(userBrightness); - setupController(userLux, userNits); + setupController(userLux, userNits, /* applyDebounce= */ true, + /* useHorizon= */ false); verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness); } @Test + public void testBrighteningLightDebounce() throws Exception { + clearInvocations(mSensorManager); + setupController(BrightnessMappingStrategy.INVALID_LUX, + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1000 + // Lux isn't steady yet + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux is steady now + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testDarkeningLightDebounce() throws Exception { + clearInvocations(mSensorManager); + when(mAmbientBrightnessThresholds.getBrighteningThreshold(anyFloat())) + .thenReturn(10000f); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(anyFloat())) + .thenReturn(10000f); + setupController(BrightnessMappingStrategy.INVALID_LUX, + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2000 + // Lux isn't steady yet + mClock.fastForward(2000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 4500 + // Lux is steady now + mClock.fastForward(2000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testBrighteningLightDebounceIdle() throws Exception { + clearInvocations(mSensorManager); + setupController(BrightnessMappingStrategy.INVALID_LUX, + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1500 + // Lux is steady now + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testDarkeningLightDebounceIdle() throws Exception { + clearInvocations(mSensorManager); + when(mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(anyFloat())) + .thenReturn(10000f); + when(mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(anyFloat())) + .thenReturn(10000f); + setupController(BrightnessMappingStrategy.INVALID_LUX, + BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 1000 + // Lux isn't steady yet + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux is steady now + mClock.fastForward(1500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + } + + @Test public void testBrightnessBasedOnLastObservedLux() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + // Set up system to return 0.3f as a brightness value float lux = 100.0f; // Brightness as float (from 0.0f to 1.0f) float normalizedBrightness = 0.3f; - when(mLightSensorController.getLastObservedLux()).thenReturn(lux); + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); // Send a new sensor value, disable the sensor and verify + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); mController.configure(AUTO_BRIGHTNESS_DISABLED, /* configuration= */ null, /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0, /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON, @@ -643,19 +1059,21 @@ public class AutomaticBrightnessControllerTest { @Test public void testAutoBrightnessInDoze() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.3f as a brightness value float lux = 100.0f; // Brightness as float (from 0.0f to 1.0f) float normalizedBrightness = 0.3f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); - when(mLightSensorController.getLastObservedLux()).thenReturn(lux); // Set policy to DOZE mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, @@ -664,7 +1082,7 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // Send a new sensor value - listener.onAmbientLuxChange(lux); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); // The brightness should be scaled by the doze factor assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR, @@ -677,19 +1095,21 @@ public class AutomaticBrightnessControllerTest { @Test public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.3f as a brightness value float lux = 100.0f; // Brightness as float (from 0.0f to 1.0f) float normalizedBrightness = 0.3f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); - when(mLightSensorController.getLastObservedLux()).thenReturn(lux); // Switch mode to DOZE mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE); @@ -701,7 +1121,7 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // Send a new sensor value - listener.onAmbientLuxChange(lux); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); // The brightness should not be scaled by the doze factor assertEquals(normalizedBrightness, @@ -713,19 +1133,21 @@ public class AutomaticBrightnessControllerTest { @Test public void testAutoBrightnessInDoze_ShouldNotScaleIfScreenOn() throws Exception { - ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor = - ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class); - verify(mLightSensorController).setListener(listenerCaptor.capture()); - LightSensorController.LightSensorListener listener = listenerCaptor.getValue(); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); // Set up system to return 0.3f as a brightness value float lux = 100.0f; // Brightness as float (from 0.0f to 1.0f) float normalizedBrightness = 0.3f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); - when(mLightSensorController.getLastObservedLux()).thenReturn(lux); // Set policy to DOZE mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, @@ -734,7 +1156,7 @@ public class AutomaticBrightnessControllerTest { /* shouldResetShortTermModel= */ true); // Send a new sensor value - listener.onAmbientLuxChange(lux); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); // The brightness should not be scaled by the doze factor assertEquals(normalizedBrightness, diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index a4d1f5c9d95c..740ffc90d785 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; @@ -78,8 +79,6 @@ import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.BrightnessReason; -import com.android.server.display.brightness.LightSensorController; -import com.android.server.display.brightness.TestUtilsKt; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.color.ColorDisplayService; @@ -1162,19 +1161,30 @@ public final class DisplayPowerControllerTest { any(AutomaticBrightnessController.Callbacks.class), any(Looper.class), eq(mSensorManagerMock), + /* lightSensor= */ any(), /* brightnessMappingStrategyMap= */ any(SparseArray.class), + /* lightSensorWarmUpTime= */ anyInt(), /* brightnessMin= */ anyFloat(), /* brightnessMax= */ anyFloat(), /* dozeScaleFactor */ anyFloat(), + /* lightSensorRate= */ anyInt(), + /* initialLightSensorRate= */ anyInt(), + /* brighteningLightDebounceConfig */ anyLong(), + /* darkeningLightDebounceConfig */ anyLong(), + /* brighteningLightDebounceConfigIdle= */ anyLong(), + /* darkeningLightDebounceConfigIdle= */ anyLong(), + /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(), + any(HysteresisLevels.class), + any(HysteresisLevels.class), any(HysteresisLevels.class), any(HysteresisLevels.class), eq(mContext), any(BrightnessRangeController.class), any(BrightnessThrottler.class), + /* ambientLightHorizonShort= */ anyInt(), + /* ambientLightHorizonLong= */ anyInt(), eq(lux), eq(nits), - eq(DISPLAY_ID), - any(LightSensorController.LightSensorControllerConfig.class), any(BrightnessClamperController.class) ); } @@ -2097,22 +2107,22 @@ public final class DisplayPowerControllerTest { } @Override - LightSensorController.LightSensorControllerConfig getLightSensorControllerConfig( - Context context, DisplayDeviceConfig displayDeviceConfig) { - return TestUtilsKt.createLightSensorControllerConfig(); - } - - @Override AutomaticBrightnessController getAutomaticBrightnessController( AutomaticBrightnessController.Callbacks callbacks, Looper looper, - SensorManager sensorManager, + SensorManager sensorManager, Sensor lightSensor, SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap, - float brightnessMin, float brightnessMax, float dozeScaleFactor, + int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, + float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, + long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, + boolean resetAmbientLuxAfterWarmUpConfig, + HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, + HysteresisLevels ambientBrightnessThresholdsIdle, HysteresisLevels screenBrightnessThresholdsIdle, Context context, BrightnessRangeController brightnessRangeController, - BrightnessThrottler brightnessThrottler, float userLux, float userNits, - int displayId, LightSensorController.LightSensorControllerConfig config, + BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort, + int ambientLightHorizonLong, float userLux, float userNits, BrightnessClamperController brightnessClamperController) { return mAutomaticBrightnessController; } @@ -2125,12 +2135,18 @@ public final class DisplayPowerControllerTest { } @Override - HysteresisLevels getBrightnessThresholdsIdleHysteresisLevels(DisplayDeviceConfig ddc) { + HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, + float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, + float[] darkeningThresholdLevels, float minDarkeningThreshold, + float minBrighteningThreshold) { return mHysteresisLevels; } @Override - HysteresisLevels getBrightnessThresholdsHysteresisLevels(DisplayDeviceConfig ddc) { + HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, + float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, + float[] darkeningThresholdLevels, float minDarkeningThreshold, + float minBrighteningThreshold, boolean potentialOldBrightnessRange) { return mHysteresisLevels; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt b/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt deleted file mode 100644 index 02d6946aa30c..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2024 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 androidx.test.filters.SmallTest -import com.android.server.display.brightness.createHysteresisLevels -import kotlin.test.assertEquals -import org.junit.Test - -private const val FLOAT_TOLERANCE = 0.001f -@SmallTest -class HysteresisLevelsTest { - @Test - fun `test hysteresis levels`() { - val hysteresisLevels = createHysteresisLevels( - brighteningThresholdsPercentages = floatArrayOf(50f, 100f), - darkeningThresholdsPercentages = floatArrayOf(10f, 20f), - brighteningThresholdLevels = floatArrayOf(0f, 500f), - darkeningThresholdLevels = floatArrayOf(0f, 500f), - minDarkeningThreshold = 3f, - minBrighteningThreshold = 1.5f - ) - - // test low, activate minimum change thresholds. - assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), FLOAT_TOLERANCE) - assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), FLOAT_TOLERANCE) - assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), FLOAT_TOLERANCE) - - // test max - // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater - assertEquals( - 20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), FLOAT_TOLERANCE * 2) - assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), FLOAT_TOLERANCE) - - // test just below threshold - assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), FLOAT_TOLERANCE) - assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), FLOAT_TOLERANCE) - - // test at (considered above) threshold - assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), FLOAT_TOLERANCE) - assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), FLOAT_TOLERANCE) - } -}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt deleted file mode 100644 index 5fe91786eb24..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2024 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.brightness - -import androidx.test.filters.SmallTest -import com.android.internal.os.Clock -import com.android.server.display.brightness.LightSensorController.AmbientLightRingBuffer -import com.google.common.truth.Truth.assertThat -import org.junit.Assert.assertThrows -import org.junit.Test -import org.mockito.kotlin.mock - - -private const val BUFFER_INITIAL_CAPACITY = 3 - -@SmallTest -class AmbientLightRingBufferTest { - - private val buffer = AmbientLightRingBuffer(BUFFER_INITIAL_CAPACITY, mock<Clock>()) - - @Test - fun `test created empty`() { - assertThat(buffer.size()).isEqualTo(0) - } - - @Test - fun `test push to empty buffer`() { - buffer.push(1000, 0.5f) - - assertThat(buffer.size()).isEqualTo(1) - assertThat(buffer.getLux(0)).isEqualTo(0.5f) - assertThat(buffer.getTime(0)).isEqualTo(1000) - } - - @Test - fun `test prune keeps youngest outside horizon and sets time to horizon`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - - buffer.prune(2500) - - assertThat(buffer.size()).isEqualTo(2) - - assertThat(buffer.getLux(0)).isEqualTo(0.6f) - assertThat(buffer.getTime(0)).isEqualTo(2500) - } - - @Test - fun `test prune keeps inside horizon`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - - buffer.prune(2500) - - assertThat(buffer.size()).isEqualTo(2) - - assertThat(buffer.getLux(1)).isEqualTo(0.7f) - assertThat(buffer.getTime(1)).isEqualTo(3000) - } - - - @Test - fun `test pushes correctly after prune`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - buffer.prune(2500) - - buffer.push(4000, 0.8f) - - assertThat(buffer.size()).isEqualTo(3) - - assertThat(buffer.getLux(0)).isEqualTo(0.6f) - assertThat(buffer.getTime(0)).isEqualTo(2500) - assertThat(buffer.getLux(1)).isEqualTo(0.7f) - assertThat(buffer.getTime(1)).isEqualTo(3000) - assertThat(buffer.getLux(2)).isEqualTo(0.8f) - assertThat(buffer.getTime(2)).isEqualTo(4000) - } - - @Test - fun `test increase buffer size`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - - buffer.push(4000, 0.8f) - - assertThat(buffer.size()).isEqualTo(4) - - assertThat(buffer.getLux(0)).isEqualTo(0.5f) - assertThat(buffer.getTime(0)).isEqualTo(1000) - assertThat(buffer.getLux(1)).isEqualTo(0.6f) - assertThat(buffer.getTime(1)).isEqualTo(2000) - assertThat(buffer.getLux(2)).isEqualTo(0.7f) - assertThat(buffer.getTime(2)).isEqualTo(3000) - assertThat(buffer.getLux(3)).isEqualTo(0.8f) - assertThat(buffer.getTime(3)).isEqualTo(4000) - } - - @Test - fun `test buffer clear`() { - buffer.push(1000, 0.5f) - buffer.push(2000, 0.6f) - buffer.push(3000, 0.7f) - - buffer.clear() - - assertThat(buffer.size()).isEqualTo(0) - assertThrows(ArrayIndexOutOfBoundsException::class.java) { - buffer.getLux(0) - } - } -}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt deleted file mode 100644 index 966134aa2b77..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (C) 2024 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.brightness - -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.os.Handler -import androidx.test.filters.SmallTest -import com.android.internal.os.Clock -import com.android.server.display.TestUtils -import com.android.server.display.brightness.LightSensorController.Injector -import com.android.server.display.brightness.LightSensorController.LightSensorControllerConfig -import com.android.server.testutils.OffsettableClock -import com.android.server.testutils.TestHandler -import com.google.common.truth.Truth.assertThat -import kotlin.math.ceil -import kotlin.test.assertEquals -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -private const val FLOAT_TOLERANCE = 0.001f - -@SmallTest -class LightSensorControllerTest { - - private val testHandler = TestHandler(null) - private val testInjector = TestInjector() - - @Test - fun `test ambient light horizon`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - brighteningLightDebounceConfig = 0, - darkeningLightDebounceConfig = 0, - ambientLightHorizonShort = 1000, - ambientLightHorizonLong = 2000 - ), testInjector, testHandler) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - val timeIncrement = 500L - // set ambient lux to low - // t = 0 - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - - // t = 500 - // - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - - // t = 1000 - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 1500 - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 2000 - // ensure that our reading is at 0. - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 2500 - // first 10000 lux sensor event reading - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 3000 - // lux reading should still not yet be 10000. - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertTrue(reportedAmbientLux > 0) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 3500 - testInjector.clock.fastForward(timeIncrement) - // at short horizon, first value outside will be used in calculation (t = 2000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 4000 - // lux has been high (10000) for more than 1000ms. - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertEquals(10_000f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 4500 - testInjector.clock.fastForward(timeIncrement) - // short horizon is high, long horizon is high too - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f)) - assertEquals(10_000f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t = 5000 - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 5500 - testInjector.clock.fastForward(timeIncrement) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 6000 - testInjector.clock.fastForward(timeIncrement) - // at short horizon, first value outside will be used in calculation (t = 4500) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertTrue(reportedAmbientLux > 0f) - assertTrue(reportedAmbientLux < 10_000f) - - // t = 6500 - testInjector.clock.fastForward(timeIncrement) - // ambient lux goes to 0 - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f)) - assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE) - - // only the values within the horizon should be kept - assertArrayEquals(floatArrayOf(10_000f, 0f, 0f, 0f, 0f), - lightSensorController.lastSensorValues, FLOAT_TOLERANCE) - assertArrayEquals(longArrayOf(4_500, 5_000, 5_500, 6_000, 6_500), - lightSensorController.lastSensorTimestamps) - } - - @Test - fun `test brightening debounce`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - brighteningLightDebounceConfig = 1500, - ambientLightHorizonShort = 0, // only last value will be used for lux calculation - ambientLightHorizonLong = 10_000, - // brightening threshold is set to previous lux value - ambientBrightnessThresholds = createHysteresisLevels( - brighteningThresholdLevels = floatArrayOf(), - brighteningThresholdsPercentages = floatArrayOf(), - minBrighteningThreshold = 0f - ) - ), testInjector, testHandler) - lightSensorController.setIdleMode(false) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - // t0 (0) - // Initial lux, initial brightening threshold - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t1 (1000) - // Lux increase, first brightening event - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1800f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t2 (2000) (t2 - t1 < brighteningLightDebounceConfig) - // Lux increase, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2000f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t3 (3000) (t3 - t1 < brighteningLightDebounceConfig) - // Lux increase, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2200f)) - assertEquals(2200f, reportedAmbientLux, FLOAT_TOLERANCE) - } - - @Test - fun `test sensor readings`() { - val ambientLightHorizonLong = 2_500 - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - ambientLightHorizonLong = ambientLightHorizonLong - ), testInjector, testHandler) - lightSensorController.setListener { } - lightSensorController.setIdleMode(false) - lightSensorController.enableLightSensorIfNeeded() - - // Choose values such that the ring buffer's capacity is extended and the buffer is pruned - val increment = 11 - var lux = 5000 - for (i in 0 until 1000) { - lux += increment - testInjector.clock.fastForward(increment.toLong()) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(lux.toFloat())) - } - - val valuesCount = ceil(ambientLightHorizonLong.toDouble() / increment + 1).toInt() - val sensorValues = lightSensorController.lastSensorValues - val sensorTimestamps = lightSensorController.lastSensorTimestamps - - // Only the values within the horizon should be kept - assertEquals(valuesCount, sensorValues.size) - assertEquals(valuesCount, sensorTimestamps.size) - - var sensorTimestamp = testInjector.clock.now() - for (i in valuesCount - 1 downTo 1) { - assertEquals(lux.toFloat(), sensorValues[i], FLOAT_TOLERANCE) - assertEquals(sensorTimestamp, sensorTimestamps[i]) - lux -= increment - sensorTimestamp -= increment - } - assertEquals(lux.toFloat(), sensorValues[0], FLOAT_TOLERANCE) - assertEquals(testInjector.clock.now() - ambientLightHorizonLong, sensorTimestamps[0]) - } - - @Test - fun `test darkening debounce`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - darkeningLightDebounceConfig = 1500, - ambientLightHorizonShort = 0, // only last value will be used for lux calculation - ambientLightHorizonLong = 10_000, - // darkening threshold is set to previous lux value - ambientBrightnessThresholds = createHysteresisLevels( - darkeningThresholdLevels = floatArrayOf(), - darkeningThresholdsPercentages = floatArrayOf(), - minDarkeningThreshold = 0f - ) - ), testInjector, testHandler) - - lightSensorController.setIdleMode(false) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - // t0 (0) - // Initial lux, initial darkening threshold - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t1 (1000) - // Lux decreased, first darkening event - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(800f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t2 (2000) (t2 - t1 < darkeningLightDebounceConfig) - // Lux decreased, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(500f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t3 (3000) (t3 - t1 < darkeningLightDebounceConfig) - // Lux decreased, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(400f)) - assertEquals(400f, reportedAmbientLux, FLOAT_TOLERANCE) - } - - @Test - fun `test brightening debounce in idle mode`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - brighteningLightDebounceConfigIdle = 1500, - ambientLightHorizonShort = 0, // only last value will be used for lux calculation - ambientLightHorizonLong = 10_000, - // brightening threshold is set to previous lux value - ambientBrightnessThresholdsIdle = createHysteresisLevels( - brighteningThresholdLevels = floatArrayOf(), - brighteningThresholdsPercentages = floatArrayOf(), - minBrighteningThreshold = 0f - ) - ), testInjector, testHandler) - lightSensorController.setIdleMode(true) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - // t0 (0) - // Initial lux, initial brightening threshold - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t1 (1000) - // Lux increase, first brightening event - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1800f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t2 (2000) (t2 - t1 < brighteningLightDebounceConfigIdle) - // Lux increase, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2000f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t3 (3000) (t3 - t1 < brighteningLightDebounceConfigIdle) - // Lux increase, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2200f)) - assertEquals(2200f, reportedAmbientLux, FLOAT_TOLERANCE) - } - - @Test - fun `test darkening debounce in idle mode`() { - val lightSensorController = LightSensorController( - createLightSensorControllerConfig( - lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event - darkeningLightDebounceConfigIdle = 1500, - ambientLightHorizonShort = 0, // only last value will be used for lux calculation - ambientLightHorizonLong = 10_000, - // darkening threshold is set to previous lux value - ambientBrightnessThresholdsIdle = createHysteresisLevels( - darkeningThresholdLevels = floatArrayOf(), - darkeningThresholdsPercentages = floatArrayOf(), - minDarkeningThreshold = 0f - ) - ), testInjector, testHandler) - - lightSensorController.setIdleMode(true) - - var reportedAmbientLux = 0f - lightSensorController.setListener { lux -> - reportedAmbientLux = lux - } - lightSensorController.enableLightSensorIfNeeded() - - assertThat(testInjector.sensorEventListener).isNotNull() - - // t0 (0) - // Initial lux, initial darkening threshold - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t1 (1000) - // Lux decreased, first darkening event - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(800f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t2 (2000) (t2 - t1 < darkeningLightDebounceConfigIdle) - // Lux decreased, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(500f)) - assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE) - - // t3 (3000) (t3 - t1 < darkeningLightDebounceConfigIdle) - // Lux decreased, but isn't steady yet - testInjector.clock.fastForward(1000) - testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(400f)) - assertEquals(400f, reportedAmbientLux, FLOAT_TOLERANCE) - } - - - private fun sensorEvent(value: Float) = SensorEvent( - testInjector.testSensor, 0, 0, floatArrayOf(value) - ) - - private class TestInjector : Injector { - val testSensor: Sensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor") - val clock: OffsettableClock = OffsettableClock.Stopped() - - var sensorEventListener: SensorEventListener? = null - override fun getClock(): Clock { - return object : Clock() { - override fun uptimeMillis(): Long { - return clock.now() - } - } - } - - override fun getLightSensor(config: LightSensorControllerConfig): Sensor { - return testSensor - } - - override fun registerLightSensorListener( - listener: SensorEventListener, - sensor: Sensor, - rate: Int, - handler: Handler - ): Boolean { - sensorEventListener = listener - return true - } - - override fun unregisterLightSensorListener(listener: SensorEventListener) { - sensorEventListener = null - } - - override fun getTag(): String { - return "LightSensorControllerTest" - } - } -}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt deleted file mode 100644 index 1328f5fa0e90..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2024 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.brightness - -import com.android.server.display.HysteresisLevels -import com.android.server.display.config.SensorData - -@JvmOverloads -fun createLightSensorControllerConfig( - initialSensorRate: Int = 1, - normalSensorRate: Int = 2, - resetAmbientLuxAfterWarmUpConfig: Boolean = true, - ambientLightHorizonShort: Int = 1, - ambientLightHorizonLong: Int = 10_000, - lightSensorWarmUpTimeConfig: Int = 0, - weightingIntercept: Int = 10_000, - ambientBrightnessThresholds: HysteresisLevels = createHysteresisLevels(), - ambientBrightnessThresholdsIdle: HysteresisLevels = createHysteresisLevels(), - brighteningLightDebounceConfig: Long = 100_000, - darkeningLightDebounceConfig: Long = 100_000, - brighteningLightDebounceConfigIdle: Long = 100_000, - darkeningLightDebounceConfigIdle: Long = 100_000, - ambientLightSensor: SensorData = SensorData() -) = LightSensorController.LightSensorControllerConfig( - initialSensorRate, - normalSensorRate, - resetAmbientLuxAfterWarmUpConfig, - ambientLightHorizonShort, - ambientLightHorizonLong, - lightSensorWarmUpTimeConfig, - weightingIntercept, - ambientBrightnessThresholds, - ambientBrightnessThresholdsIdle, - brighteningLightDebounceConfig, - darkeningLightDebounceConfig, - brighteningLightDebounceConfigIdle, - darkeningLightDebounceConfigIdle, - ambientLightSensor -) - -fun createHysteresisLevels( - brighteningThresholdsPercentages: FloatArray = floatArrayOf(), - darkeningThresholdsPercentages: FloatArray = floatArrayOf(), - brighteningThresholdLevels: FloatArray = floatArrayOf(), - darkeningThresholdLevels: FloatArray = floatArrayOf(), - minDarkeningThreshold: Float = 0f, - minBrighteningThreshold: Float = 0f, - potentialOldBrightnessRange: Boolean = false -) = HysteresisLevels( - brighteningThresholdsPercentages, - darkeningThresholdsPercentages, - brighteningThresholdLevels, - darkeningThresholdLevels, - minDarkeningThreshold, - minBrighteningThreshold, - potentialOldBrightnessRange -)
\ No newline at end of file |