From 41a5cdf9862d4fa77153656e677567e707aa1d45 Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Wed, 13 Nov 2013 16:18:32 -0800 Subject: [PATCH 1/2] New ambient light level filtering for automatic brightness This new filtering method maintains a ring buffer of all light sensor measurements within the last ten seconds. Once all of the measurements are either above the brightening threshold or below the darkening threshold, adjust the screen brightness to the average of the recorded measurements. Change-Id: I012bd99f86149e02f03b05f19722aef9a07aba31 --- .../server/power/DisplayPowerController.java | 473 ++++++++++++------ 1 file changed, 326 insertions(+), 147 deletions(-) diff --git a/services/core/java/com/android/server/power/DisplayPowerController.java b/services/core/java/com/android/server/power/DisplayPowerController.java index b63f625596f5a..c18f284c4b8aa 100644 --- a/services/core/java/com/android/server/power/DisplayPowerController.java +++ b/services/core/java/com/android/server/power/DisplayPowerController.java @@ -36,11 +36,13 @@ import android.os.PowerManager; import android.os.SystemClock; import android.text.format.DateUtils; import android.util.FloatMath; +import android.util.MathUtils; import android.util.Slog; import android.util.Spline; import android.util.TimeUtils; import java.io.PrintWriter; +import java.util.Arrays; /** * Controls the power state of the display. @@ -67,7 +69,7 @@ import java.io.PrintWriter; final class DisplayPowerController { private static final String TAG = "DisplayPowerController"; - private static boolean DEBUG = false; + private static boolean DEBUG = true; private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false; private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; @@ -110,7 +112,7 @@ final class DisplayPowerController { private static final int MSG_UPDATE_POWER_STATE = 1; private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2; - private static final int MSG_LIGHT_SENSOR_DEBOUNCED = 3; + private static final int MSG_UPDATE_AMBIENT_LUX = 3; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -126,31 +128,22 @@ final class DisplayPowerController { // Light sensor event rate in milliseconds. private static final int LIGHT_SENSOR_RATE_MILLIS = 1000; - // A rate for generating synthetic light sensor events in the case where the light - // sensor hasn't reported any new data in a while and we need it to update the - // debounce filter. We only synthesize light sensor measurements when needed. - private static final int SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS = - LIGHT_SENSOR_RATE_MILLIS * 2; + // Period of time in which to consider light samples in milliseconds. + private static final int AMBIENT_LIGHT_HORIZON = 10000; + + private static final int WEIGHTING_INTERCEPT = AMBIENT_LIGHT_HORIZON; // Brightness animation ramp rate in brightness units per second. private static final int BRIGHTNESS_RAMP_RATE_FAST = 200; private static final int BRIGHTNESS_RAMP_RATE_SLOW = 40; - // IIR filter time constants in milliseconds for computing two moving averages of - // the light samples. One is a long-term average and the other is a short-term average. - // We can use these filters to assess trends in ambient brightness. - // The short term average gives us a filtered but relatively low latency measurement. - // The long term average informs us about the overall trend. - private static final long SHORT_TERM_AVERAGE_LIGHT_TIME_CONSTANT = 1000; - private static final long LONG_TERM_AVERAGE_LIGHT_TIME_CONSTANT = 5000; - - // 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 static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000; - private static final long DARKENING_LIGHT_DEBOUNCE = 8000; + // 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 static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000; + private static final long DARKENING_LIGHT_DEBOUNCE = 8000; // Hysteresis constraints for brightening or darkening. // The recent lux must have changed by at least this fraction relative to the @@ -308,18 +301,7 @@ final class DisplayPowerController { // The number of light samples collected since the light sensor was enabled. private int mRecentLightSamples; - // The long-term and short-term filtered light measurements. - private float mRecentShortTermAverageLux; - private float mRecentLongTermAverageLux; - - // The direction in which the average lux is moving relative to the current ambient lux. - // 0 if not changing or within hysteresis threshold. - // 1 if brightening beyond hysteresis threshold. - // -1 if darkening beyond hysteresis threshold. - private int mDebounceLuxDirection; - - // The time when the average lux last changed direction. - private long mDebounceLuxTime; + private AmbientLightRingBuffer mAmbientLightRingBuffer; // The screen brightness level that has been chosen by the auto-brightness // algorithm. The actual brightness should ramp towards this value. @@ -398,6 +380,8 @@ final class DisplayPowerController { com.android.internal.R.integer.config_lightSensorWarmupTime); } + mAmbientLightRingBuffer = new AmbientLightRingBuffer(); + mScreenBrightnessRangeMinimum = clampAbsoluteBrightness(screenBrightnessMinimum); mScreenBrightnessRangeMaximum = PowerManager.BRIGHTNESS_ON; @@ -916,7 +900,8 @@ final class DisplayPowerController { mLightSensorEnabled = false; mAmbientLuxValid = false; mRecentLightSamples = 0; - mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED); + mAmbientLightRingBuffer.clear(); + mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); mSensorManager.unregisterListener(mLightSensorListener); } } @@ -926,25 +911,16 @@ final class DisplayPowerController { } private void handleLightSensorEvent(long time, float lux) { - mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED); + mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); applyLightSensorMeasurement(time, lux); updateAmbientLux(time); } private void applyLightSensorMeasurement(long time, float lux) { - // Update our filters. - mRecentLightSamples += 1; - if (mRecentLightSamples == 1) { - mRecentShortTermAverageLux = lux; - mRecentLongTermAverageLux = lux; - } else { - final long timeDelta = time - mLastObservedLuxTime; - mRecentShortTermAverageLux += (lux - mRecentShortTermAverageLux) - * timeDelta / (SHORT_TERM_AVERAGE_LIGHT_TIME_CONSTANT + timeDelta); - mRecentLongTermAverageLux += (lux - mRecentLongTermAverageLux) - * timeDelta / (LONG_TERM_AVERAGE_LIGHT_TIME_CONSTANT + timeDelta); - } + mRecentLightSamples++; + mAmbientLightRingBuffer.prune(time - AMBIENT_LIGHT_HORIZON); + mAmbientLightRingBuffer.push(time, lux); // Remember this sample value. mLastObservedLux = lux; @@ -957,6 +933,130 @@ final class DisplayPowerController { mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS); } + private float calculateAmbientLux(long now) { + final int N = mAmbientLightRingBuffer.size(); + if (N == 0) { + Slog.e(TAG, "calculateAmbientLux: No ambient light readings available"); + return 0; + } + float sum = 0; + float totalWeight = 0; + long endTime = 1; + for (int i = N - 1; i >= 0; i--) { + long startTime = (mAmbientLightRingBuffer.getTime(i) - now); + float weight = calculateWeight(startTime, endTime); + if (DEBUG) { + Slog.d(TAG, "calculateAmbientLux: [" + + (startTime) + ", " + + (endTime) + "]: " + weight); + } + totalWeight += weight; + sum += mAmbientLightRingBuffer.getLux(i) * weight; + endTime = startTime; + } + if (DEBUG) { + Slog.d(TAG, "calculateAmbientLux: totalWeight=" + totalWeight + + ", newAmbientLux=" + (sum / totalWeight)); + } + return sum / totalWeight; + } + + private static float calculateWeight(long startDelta, long endDelta) { + return weightIntegral(endDelta) - weightIntegral(startDelta); + } + + // Calculates the integral of y = x + WEIGHTING_INTERCEPT from 0 to x + private static float weightIntegral(long x) { + return x * (x * 0.5f + WEIGHTING_INTERCEPT); + } + + private long nextAmbientLightBrighteningTransition(long time) { + final int N = mAmbientLightRingBuffer.size(); + for (int i = 0; i < N; i++) { + if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) { + long nextCheck; + if (i + 1 < N) { + nextCheck = mAmbientLightRingBuffer.getTime(i+1) + AMBIENT_LIGHT_HORIZON; + } else { + nextCheck = time + BRIGHTENING_LIGHT_DEBOUNCE; + } + if (DEBUG) { + Slog.e(TAG, "{lux= " + mAmbientLightRingBuffer.getLux(i) + + ", time= " + mAmbientLightRingBuffer.getTime(i) + + "} is below brightening threshold, checking again in " + + TimeUtils.formatUptime(nextCheck)); + } + return nextCheck; + } + } + if (N == 0) { + long nextCheck = time + BRIGHTENING_LIGHT_DEBOUNCE; + if (DEBUG) { + Slog.e(TAG, "No data, checking again in " + TimeUtils.formatUptime(nextCheck)); + } + return nextCheck; + } + + if (time - mAmbientLightRingBuffer.getTime(0) < BRIGHTENING_LIGHT_DEBOUNCE) { + long nextCheck = mAmbientLightRingBuffer.getTime(0) + BRIGHTENING_LIGHT_DEBOUNCE; + if (DEBUG) { + Slog.e(TAG, "{lux= " + mAmbientLightRingBuffer.getLux(0) + + ", time= " + mAmbientLightRingBuffer.getTime(0) + + "} hasn't passed debounce threshold, checking again in " + + TimeUtils.formatUptime(nextCheck)); + } + return nextCheck; + } + + return 0; + } + + private long nextAmbientLightDarkeningTransition(long time) { + final int N = mAmbientLightRingBuffer.size(); + for (int i = 0; i < N; i ++) { + if (mAmbientLightRingBuffer.getLux(i) >= mDarkeningLuxThreshold) { + long nextCheck; + if (i + 1 < N) { + nextCheck = mAmbientLightRingBuffer.getTime(i+1) + AMBIENT_LIGHT_HORIZON; + } else { + nextCheck = time + DARKENING_LIGHT_DEBOUNCE; + } + if (DEBUG) { + Slog.e(TAG, "{lux= " + mAmbientLightRingBuffer.getLux(i) + + ", time= " + mAmbientLightRingBuffer.getTime(i) + + "} is above darkening threshold, checking again in " + + TimeUtils.formatUptime(nextCheck)); + } + return nextCheck; + } + } + if (N == 0) { + long nextCheck = time + DARKENING_LIGHT_DEBOUNCE; + if (DEBUG) { + Slog.e(TAG, "No data, checking again in " + TimeUtils.formatUptime(nextCheck)); + } + return nextCheck; + } + + if (time - mAmbientLightRingBuffer.getTime(0) < DARKENING_LIGHT_DEBOUNCE) { + long nextCheck = mAmbientLightRingBuffer.getTime(0) + DARKENING_LIGHT_DEBOUNCE; + if (DEBUG) { + Slog.e(TAG, "{lux= " + mAmbientLightRingBuffer.getLux(0) + + ", time= " + mAmbientLightRingBuffer.getTime(0) + + "} hasn't passed debounce threshold, checking again in " + + TimeUtils.formatUptime(nextCheck)); + } + return nextCheck; + } + return 0; + } + + private void updateAmbientLux() { + long time = SystemClock.uptimeMillis(); + mAmbientLightRingBuffer.prune(time - AMBIENT_LIGHT_HORIZON); + 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. @@ -964,121 +1064,56 @@ final class DisplayPowerController { final long timeWhenSensorWarmedUp = mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; if (time < timeWhenSensorWarmedUp) { - mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: " + + "time=" + time + + ", timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); + } + mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, timeWhenSensorWarmedUp); return; } - setAmbientLux(mRecentShortTermAverageLux); + setAmbientLux(calculateAmbientLux(time)); mAmbientLuxValid = true; - mDebounceLuxDirection = 0; - mDebounceLuxTime = time; if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Initializing: " - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", mAmbientLux=" + mAmbientLux); } updateAutoBrightness(true); - } else if (mRecentShortTermAverageLux > mBrighteningLuxThreshold - && mRecentLongTermAverageLux > mBrighteningLuxThreshold) { - // The ambient environment appears to be brightening. - if (mDebounceLuxDirection <= 0) { - mDebounceLuxDirection = 1; - mDebounceLuxTime = time; - if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Possibly brightened, waiting for " - + BRIGHTENING_LIGHT_DEBOUNCE + " ms: " - + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux - + ", mAmbientLux=" + mAmbientLux); - } - } - long debounceTime = mDebounceLuxTime + BRIGHTENING_LIGHT_DEBOUNCE; - if (time < debounceTime) { - mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime); - return; - } - setAmbientLux(mRecentShortTermAverageLux); + } + + long nextBrightenTransition = nextAmbientLightBrighteningTransition(time); + long nextDarkenTransition = nextAmbientLightDarkeningTransition(time); + + if (nextBrightenTransition == 0) { + setAmbientLux(calculateAmbientLux(time)); if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Brightened: " + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", mAmbientLux=" + mAmbientLux); } updateAutoBrightness(true); - } else if (mRecentShortTermAverageLux < mDarkeningLuxThreshold - && mRecentLongTermAverageLux < mDarkeningLuxThreshold) { - // The ambient environment appears to be darkening. - if (mDebounceLuxDirection >= 0) { - mDebounceLuxDirection = -1; - mDebounceLuxTime = time; - if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Possibly darkened, waiting for " - + DARKENING_LIGHT_DEBOUNCE + " ms: " - + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux - + ", mAmbientLux=" + mAmbientLux); - } - } - long debounceTime = mDebounceLuxTime + DARKENING_LIGHT_DEBOUNCE; - if (time < debounceTime) { - mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime); - return; - } - // Be conservative about reducing the brightness, only reduce it a little bit - // at a time to avoid having to bump it up again soon. - setAmbientLux(Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux)); + nextBrightenTransition = nextAmbientLightBrighteningTransition(time); + } else if (nextDarkenTransition == 0) { + setAmbientLux(calculateAmbientLux(time)); if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Darkened: " + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", mAmbientLux=" + mAmbientLux); } updateAutoBrightness(true); - } else if (mDebounceLuxDirection != 0) { - // No change or change is within the hysteresis thresholds. - mDebounceLuxDirection = 0; - mDebounceLuxTime = time; + nextDarkenTransition = nextAmbientLightDarkeningTransition(time); + } + long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition); + if (nextTransitionTime > time) { if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Canceled debounce: " - + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold - + ", mDarkeningLuxThreshold=" + mDarkeningLuxThreshold - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux - + ", mAmbientLux=" + mAmbientLux); + Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " + + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime)); } - } - - // Now that we've done all of that, we haven't yet posted a debounce - // message. So consider the case where current lux is beyond the - // threshold. It's possible that the light sensor may not report values - // if the light level does not change, so we need to occasionally - // synthesize sensor readings in order to make sure the brightness is - // adjusted accordingly. Note these thresholds may have changed since - // we entered the function because we called setAmbientLux and - // updateAutoBrightness along the way. - if (mLastObservedLux > mBrighteningLuxThreshold - || mLastObservedLux < mDarkeningLuxThreshold) { - mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, - time + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS); - } - } - - private void debounceLightSensor() { - if (mLightSensorEnabled) { - long time = SystemClock.uptimeMillis(); - if (time >= mLastObservedLuxTime + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS) { - if (DEBUG) { - Slog.d(TAG, "debounceLightSensor: Synthesizing light sensor measurement " - + "after " + (time - mLastObservedLuxTime) + " ms."); - } - applyLightSensorMeasurement(time, mLastObservedLux); - } - updateAmbientLux(time); + mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime); } } @@ -1092,7 +1127,7 @@ final class DisplayPowerController { if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT && mPowerRequest.screenAutoBrightnessAdjustment != 0.0f) { - final float adjGamma = FloatMath.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA, + final float adjGamma = MathUtils.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA, Math.min(1.0f, Math.max(-1.0f, -mPowerRequest.screenAutoBrightnessAdjustment))); gamma *= adjGamma; @@ -1260,10 +1295,7 @@ final class DisplayPowerController { pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime)); pw.println(" mRecentLightSamples=" + mRecentLightSamples); - pw.println(" mRecentShortTermAverageLux=" + mRecentShortTermAverageLux); - pw.println(" mRecentLongTermAverageLux=" + mRecentLongTermAverageLux); - pw.println(" mDebounceLuxDirection=" + mDebounceLuxDirection); - pw.println(" mDebounceLuxTime=" + TimeUtils.formatUptime(mDebounceLuxTime)); + pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer); pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness); pw.println(" mUsingScreenAutoBrightness=" + mUsingScreenAutoBrightness); pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma); @@ -1330,8 +1362,8 @@ final class DisplayPowerController { debounceProximitySensor(); break; - case MSG_LIGHT_SENSOR_DEBOUNCED: - debounceLightSensor(); + case MSG_UPDATE_AMBIENT_LUX: + updateAmbientLux(); break; } } @@ -1377,4 +1409,151 @@ final class DisplayPowerController { updatePowerState(); } }; + + private static 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; + + public AmbientLightRingBuffer() { + this((int) (AMBIENT_LIGHT_HORIZON / LIGHT_SENSOR_RATE_MILLIS * BUFFER_SLACK)); + } + + public AmbientLightRingBuffer(int initialCapacity) { + mCapacity = initialCapacity; + mRingLux = new float[mCapacity]; + mRingTime = new long[mCapacity]; + mStart = 0; + mEnd = 0; + mCount = 0; + } + + public float getLux(int index) { + if (index >= size() || index < 0) { + throw new ArrayIndexOutOfBoundsException(index); + } + index += mStart; + if (index >= mCapacity) { + index -= mCapacity; + } + return mRingLux[index]; + } + + public long getTime(int index) { + if (index >= size() || index < 0) { + throw new ArrayIndexOutOfBoundsException(index); + } + index += mStart; + if (index >= mCapacity) { + index -= mCapacity; + } + return mRingTime[index]; + } + + 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, mCapacity - length); + System.arraycopy(mRingTime, 0, newRingTime, length, mCapacity - length); + } + 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) { + int removalCount = 0; + while(removalCount < size() && getTime(removalCount) <= horizon) { + removalCount++; + } + + if (removalCount > 0) { + // 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. + removalCount--; + + mCount -= removalCount; + mStart += removalCount; + if (mStart >= mCapacity) { + mStart -= mCapacity; + } + + if (getTime(0) < horizon) { + mRingTime[mStart] = horizon; + } + } + } + + public int size() { + return mCount; + } + + public boolean isEmpty() { + return mCount == 0; + } + + public void clear() { + mStart = 0; + mEnd = 0; + mCount = 0; + } + + @Override + public String toString() { + final int length = mCapacity - mStart; + float[] lux = new float[mCount]; + long[] time = new long[mCount]; + + if (mCount <= length) { + System.arraycopy(mRingLux, mStart, lux, 0, mCount); + System.arraycopy(mRingTime, mStart, time, 0, mCount); + } else { + System.arraycopy(mRingLux, mStart, lux, 0, length); + System.arraycopy(mRingLux, 0, lux, length, mCount - length); + + System.arraycopy(mRingTime, mStart, time, 0, length); + System.arraycopy(mRingTime, 0, time, length, mCount - length); + } + return "AmbientLightRingBuffer{mCapacity=" + mCapacity + + ", mStart=" + mStart + + ", mEnd=" + mEnd + + ", mCount=" + mCount + + ", mRingLux=" + Arrays.toString(lux) + + ", mRingTime=" + Arrays.toString(time) + + "}"; + } + } } From 639c8becc6d0efe9c205f3abf1e9347464a95020 Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Fri, 17 Jan 2014 18:29:12 -0800 Subject: [PATCH 2/2] Extract automatic brightness strategy Pull the strategy for determining the automatic brightness levels into its own class. This cleans up DisplayPowerController a bit and lets us experiment with different automatic brightness strategies more easily. Change-Id: I8f4249da511e38f63260dbef81f97f5507aac948 --- .../power/AutomaticBrightnessController.java | 687 ++++++++++++++++ .../server/power/DisplayPowerController.java | 753 ++---------------- .../server/power/DisplayPowerRequest.java | 9 + 3 files changed, 766 insertions(+), 683 deletions(-) create mode 100644 services/core/java/com/android/server/power/AutomaticBrightnessController.java diff --git a/services/core/java/com/android/server/power/AutomaticBrightnessController.java b/services/core/java/com/android/server/power/AutomaticBrightnessController.java new file mode 100644 index 0000000000000..b24c57220997a --- /dev/null +++ b/services/core/java/com/android/server/power/AutomaticBrightnessController.java @@ -0,0 +1,687 @@ +/* + * Copyright (C) 2014 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.power; + +import com.android.server.twilight.TwilightListener; +import com.android.server.twilight.TwilightManager; +import com.android.server.twilight.TwilightState; + +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.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.text.format.DateUtils; +import android.util.MathUtils; +import android.util.Spline; +import android.util.Slog; +import android.util.TimeUtils; + +import java.io.PrintWriter; +import java.util.Arrays; + +class AutomaticBrightnessController { + private static final String TAG = "AutomaticBrightnessController"; + + private static final boolean DEBUG = false; + private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; + + // If true, enables the use of the screen auto-brightness adjustment setting. + private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = + PowerManager.useScreenAutoBrightnessAdjustmentFeature(); + + // The maximum range of gamma adjustment possible using the screen + // auto-brightness adjustment setting. + private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f; + + // Light sensor event rate in milliseconds. + private static final int LIGHT_SENSOR_RATE_MILLIS = 1000; + + // Period of time in which to consider light samples in milliseconds. + private static final int AMBIENT_LIGHT_HORIZON = 10000; + + // 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 static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000; + private static final long DARKENING_LIGHT_DEBOUNCE = 8000; + + // Hysteresis constraints for brightening or darkening. + // The recent lux must have changed by at least this fraction relative to the + // current ambient lux before a change will be considered. + private static final float BRIGHTENING_LIGHT_HYSTERESIS = 0.10f; + private static final float DARKENING_LIGHT_HYSTERESIS = 0.20f; + + // The intercept used for the weighting calculation. This is used in order to keep all possible + // weighting values positive. + private static final int WEIGHTING_INTERCEPT = AMBIENT_LIGHT_HORIZON; + + // 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; + + // If true, enables the use of the current time as an auto-brightness adjustment. + // The basic idea here is to expand the dynamic range of auto-brightness + // when it is especially dark outside. The light sensor tends to perform + // poorly at low light levels so we compensate for it by making an + // assumption about the environment. + private static final boolean USE_TWILIGHT_ADJUSTMENT = + PowerManager.useTwilightAdjustmentFeature(); + + // Specifies the maximum magnitude of the time of day adjustment. + private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1.5f; + + // The amount of time after or before sunrise over which to start adjusting + // the gamma. We want the change to happen gradually so that it is below the + // threshold of perceptibility and so that the adjustment has maximum effect + // well after dusk. + private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2; + + private static final int MSG_UPDATE_AMBIENT_LUX = 1; + + // Callbacks for requesting updates to the 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 twilight service. + private final TwilightManager mTwilight; + + // The auto-brightness spline adjustment. + // The brightness values have been scaled to a range of 0..1. + private final Spline mScreenAutoBrightnessSpline; + + // The minimum and maximum screen brightnesses. + private final int mScreenBrightnessRangeMinimum; + private final int mScreenBrightnessRangeMaximum; + + // 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; + + // 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 mBrighteningLuxThreshold; + private float mDarkeningLuxThreshold; + + // The most recent light sample. + private float mLastObservedLux; + + // 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; + + // The screen brightness level that has been chosen by the auto-brightness + // algorithm. The actual brightness should ramp towards this value. + // We preserve this value even when we stop using the light sensor so + // that we can quickly revert to the previous auto-brightness level + // while the light sensor warms up. + // Use -1 if there is no current auto-brightness value available. + private int mScreenAutoBrightness = -1; + + // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter) + private float mScreenAutoBrightnessAdjustment = 0.0f; + + // The last screen auto-brightness gamma. (For printing in dump() only.) + private float mLastScreenAutoBrightnessGamma = 1.0f; + + public AutomaticBrightnessController(Callbacks callbacks, Looper looper, + TwilightManager twilight, SensorManager sensorManager, Spline autoBrightnessSpline, + int lightSensorWarmUpTime, int brightnessMin, int brightnessMax) { + mCallbacks = callbacks; + mTwilight = twilight; + mSensorManager = sensorManager; + mScreenAutoBrightnessSpline = autoBrightnessSpline; + mScreenBrightnessRangeMinimum = brightnessMin; + mScreenBrightnessRangeMaximum = brightnessMax; + mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime; + + mHandler = new AutomaticBrightnessHandler(looper); + mAmbientLightRingBuffer = new AmbientLightRingBuffer(); + + if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { + mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + } + + if (USE_TWILIGHT_ADJUSTMENT) { + mTwilight.registerListener(mTwilightListener, mHandler); + } + } + + public int getAutomaticScreenBrightness() { + return mScreenAutoBrightness; + } + + public void updatePowerState(DisplayPowerRequest request) { + if (setScreenAutoBrightnessAdjustment(request.screenAutoBrightnessAdjustment) + || setLightSensorEnabled(request.useAutoBrightness + && DisplayPowerRequest.wantScreenOn(request.screenState))) { + updateAutoBrightness(false /*sendUpdate*/); + } + } + + public void dump(PrintWriter pw) { + pw.println(); + pw.println("Automatic Brightness Controller Configuration:"); + pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline); + pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); + pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); + pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); + + pw.println(); + pw.println("Automatic Brightness Controller State:"); + pw.println(" mLightSensor=" + mLightSensor); + pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState()); + pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); + pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime)); + pw.println(" mAmbientLux=" + mAmbientLux); + pw.println(" mBrighteningLuxThreshold=" + mBrighteningLuxThreshold); + pw.println(" mDarkeningLuxThreshold=" + mDarkeningLuxThreshold); + pw.println(" mLastObservedLux=" + mLastObservedLux); + pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime)); + pw.println(" mRecentLightSamples=" + mRecentLightSamples); + pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer); + pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness); + pw.println(" mScreenAutoBrightnessAdjustment=" + mScreenAutoBrightnessAdjustment); + pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma); + } + + private boolean setLightSensorEnabled(boolean enable) { + if (enable) { + if (!mLightSensorEnabled) { + mLightSensorEnabled = true; + mLightSensorEnableTime = SystemClock.uptimeMillis(); + mSensorManager.registerListener(mLightSensorListener, mLightSensor, + LIGHT_SENSOR_RATE_MILLIS * 1000, mHandler); + return true; + } + } else { + if (mLightSensorEnabled) { + mLightSensorEnabled = false; + mAmbientLuxValid = false; + mRecentLightSamples = 0; + mAmbientLightRingBuffer.clear(); + mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); + mSensorManager.unregisterListener(mLightSensorListener); + } + } + return false; + } + + private void handleLightSensorEvent(long time, float lux) { + mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); + + applyLightSensorMeasurement(time, lux); + updateAmbientLux(time); + } + + private void applyLightSensorMeasurement(long time, float lux) { + mRecentLightSamples++; + mAmbientLightRingBuffer.prune(time - AMBIENT_LIGHT_HORIZON); + mAmbientLightRingBuffer.push(time, lux); + + // Remember this sample value. + mLastObservedLux = lux; + mLastObservedLuxTime = time; + } + + private boolean setScreenAutoBrightnessAdjustment(float adjustment) { + if (adjustment != mScreenAutoBrightnessAdjustment) { + mScreenAutoBrightnessAdjustment = adjustment; + return true; + } + return false; + } + + private void setAmbientLux(float lux) { + mAmbientLux = lux; + mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS); + mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS); + } + + private float calculateAmbientLux(long now) { + final int N = mAmbientLightRingBuffer.size(); + if (N == 0) { + Slog.e(TAG, "calculateAmbientLux: No ambient light readings available"); + return -1; + } + float sum = 0; + float totalWeight = 0; + long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS; + for (int i = N - 1; i >= 0; i--) { + long startTime = (mAmbientLightRingBuffer.getTime(i) - now); + float weight = calculateWeight(startTime, endTime); + float lux = mAmbientLightRingBuffer.getLux(i); + if (DEBUG) { + Slog.d(TAG, "calculateAmbientLux: [" + + (startTime) + ", " + + (endTime) + "]: lux=" + lux + ", weight=" + weight); + } + totalWeight += weight; + sum += mAmbientLightRingBuffer.getLux(i) * weight; + endTime = startTime; + } + if (DEBUG) { + Slog.d(TAG, "calculateAmbientLux: totalWeight=" + totalWeight + + ", newAmbientLux=" + (sum / totalWeight)); + } + return sum / totalWeight; + } + + private static float calculateWeight(long startDelta, long endDelta) { + return weightIntegral(endDelta) - weightIntegral(startDelta); + } + + // Evaluates the integral of y = x + WEIGHTING_INTERCEPT. This is always positive for the + // horizon we're looking at and provides a non-linear weighting for light samples. + private static float weightIntegral(long x) { + return x * (x * 0.5f + WEIGHTING_INTERCEPT); + } + + 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) <= mBrighteningLuxThreshold) { + break; + } + earliestValidTime = mAmbientLightRingBuffer.getTime(i); + } + return earliestValidTime + BRIGHTENING_LIGHT_DEBOUNCE; + } + + 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) >= mDarkeningLuxThreshold) { + break; + } + earliestValidTime = mAmbientLightRingBuffer.getTime(i); + } + return earliestValidTime + DARKENING_LIGHT_DEBOUNCE; + } + + private void updateAmbientLux() { + long time = SystemClock.uptimeMillis(); + mAmbientLightRingBuffer.prune(time - AMBIENT_LIGHT_HORIZON); + 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 (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: " + + "time=" + time + + ", timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); + } + mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, + timeWhenSensorWarmedUp); + return; + } + setAmbientLux(calculateAmbientLux(time)); + mAmbientLuxValid = true; + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Initializing: " + + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + + ", mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true); + } + + long nextBrightenTransition = nextAmbientLightBrighteningTransition(time); + long nextDarkenTransition = nextAmbientLightDarkeningTransition(time); + float ambientLux = calculateAmbientLux(time); + + if (ambientLux >= mBrighteningLuxThreshold && nextBrightenTransition <= time + || ambientLux <= mDarkeningLuxThreshold && nextDarkenTransition <= time) { + setAmbientLux(ambientLux); + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: " + + ((ambientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + + ", mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + + ", mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true); + 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 + LIGHT_SENSOR_RATE_MILLIS; + if (DEBUG) { + 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) { + if (!mAmbientLuxValid) { + return; + } + + float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux); + float gamma = 1.0f; + + if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT + && mScreenAutoBrightnessAdjustment != 0.0f) { + final float adjGamma = MathUtils.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA, + Math.min(1.0f, Math.max(-1.0f, -mScreenAutoBrightnessAdjustment))); + gamma *= adjGamma; + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma); + } + } + + if (USE_TWILIGHT_ADJUSTMENT) { + TwilightState state = mTwilight.getCurrentState(); + if (state != null && state.isNight()) { + final long now = System.currentTimeMillis(); + final float earlyGamma = + getTwilightGamma(now, state.getYesterdaySunset(), state.getTodaySunrise()); + final float lateGamma = + getTwilightGamma(now, state.getTodaySunset(), state.getTomorrowSunrise()); + gamma *= earlyGamma * lateGamma; + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: earlyGamma=" + earlyGamma + + ", lateGamma=" + lateGamma); + } + } + } + + if (gamma != 1.0f) { + final float in = value; + value = MathUtils.pow(value, gamma); + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma + + ", in=" + in + ", out=" + value); + } + } + + int newScreenAutoBrightness = + clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON)); + if (mScreenAutoBrightness != newScreenAutoBrightness) { + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness=" + + mScreenAutoBrightness + ", newScreenAutoBrightness=" + + newScreenAutoBrightness); + } + + mScreenAutoBrightness = newScreenAutoBrightness; + mLastScreenAutoBrightnessGamma = gamma; + if (sendUpdate) { + mCallbacks.updateBrightness(); + } + } + } + + private int clampScreenBrightness(int value) { + return MathUtils.constrain(value, + mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum); + } + + private static float getTwilightGamma(long now, long lastSunset, long nextSunrise) { + if (lastSunset < 0 || nextSunrise < 0 + || now < lastSunset || now > nextSunrise) { + return 1.0f; + } + + if (now < lastSunset + TWILIGHT_ADJUSTMENT_TIME) { + return MathUtils.lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA, + (float)(now - lastSunset) / TWILIGHT_ADJUSTMENT_TIME); + } + + if (now > nextSunrise - TWILIGHT_ADJUSTMENT_TIME) { + return MathUtils.lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA, + (float)(nextSunrise - now) / TWILIGHT_ADJUSTMENT_TIME); + } + + return TWILIGHT_ADJUSTMENT_MAX_GAMMA; + } + + private final class AutomaticBrightnessHandler extends Handler { + public AutomaticBrightnessHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_AMBIENT_LUX: + updateAmbientLux(); + break; + } + } + } + + private final SensorEventListener mLightSensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + if (mLightSensorEnabled) { + final long time = SystemClock.uptimeMillis(); + final float lux = event.values[0]; + handleLightSensorEvent(time, lux); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Not used. + } + }; + + private final TwilightListener mTwilightListener = new TwilightListener() { + @Override + public void onTwilightStateChanged() { + updateAutoBrightness(true /*sendUpdate*/); + } + }; + + /** Callbacks to request updates to the display's power state. */ + interface Callbacks { + void updateBrightness(); + } + + 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 static final int DEFAULT_CAPACITY = + (int) Math.ceil(AMBIENT_LIGHT_HORIZON * BUFFER_SLACK / LIGHT_SENSOR_RATE_MILLIS); + 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; + + public AmbientLightRingBuffer() { + this(DEFAULT_CAPACITY); + } + + public AmbientLightRingBuffer(int initialCapacity) { + mCapacity = initialCapacity; + mRingLux = new float[mCapacity]; + mRingTime = new long[mCapacity]; + } + + public float getLux(int index) { + return mRingLux[offsetOf(index)]; + } + + public long getTime(int index) { + return mRingTime[offsetOf(index)]; + } + + 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 boolean isEmpty() { + return mCount == 0; + } + + public void clear() { + mStart = 0; + mEnd = 0; + mCount = 0; + } + + @Override + public String toString() { + final int length = mCapacity - mStart; + float[] lux = new float[mCount]; + long[] time = new long[mCount]; + + if (mCount <= length) { + System.arraycopy(mRingLux, mStart, lux, 0, mCount); + System.arraycopy(mRingTime, mStart, time, 0, mCount); + } else { + System.arraycopy(mRingLux, mStart, lux, 0, length); + System.arraycopy(mRingLux, 0, lux, length, mCount - length); + + System.arraycopy(mRingTime, mStart, time, 0, length); + System.arraycopy(mRingTime, 0, time, length, mCount - length); + } + return "AmbientLightRingBuffer{mCapacity=" + mCapacity + + ", mStart=" + mStart + + ", mEnd=" + mEnd + + ", mCount=" + mCount + + ", mRingLux=" + Arrays.toString(lux) + + ", mRingTime=" + Arrays.toString(time) + + "}"; + } + + private int offsetOf(int index) { + if (index >= mCount || index < 0) { + throw new ArrayIndexOutOfBoundsException(index); + } + index += mStart; + if (index >= mCapacity) { + index -= mCapacity; + } + return index; + } + } +} diff --git a/services/core/java/com/android/server/power/DisplayPowerController.java b/services/core/java/com/android/server/power/DisplayPowerController.java index c18f284c4b8aa..291bb64728a79 100644 --- a/services/core/java/com/android/server/power/DisplayPowerController.java +++ b/services/core/java/com/android/server/power/DisplayPowerController.java @@ -35,14 +35,12 @@ import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; import android.text.format.DateUtils; -import android.util.FloatMath; import android.util.MathUtils; import android.util.Slog; import android.util.Spline; import android.util.TimeUtils; import java.io.PrintWriter; -import java.util.Arrays; /** * Controls the power state of the display. @@ -66,12 +64,11 @@ import java.util.Arrays; * For debugging, you can make the electron beam and brightness animations run * slower by changing the "animator duration scale" option in Development Settings. */ -final class DisplayPowerController { +final class DisplayPowerController implements AutomaticBrightnessController.Callbacks { private static final String TAG = "DisplayPowerController"; - private static boolean DEBUG = true; + private static boolean DEBUG = false; private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false; - private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; // If true, uses the electron beam on animation. // We might want to turn this off if we cannot get a guarantee that the screen @@ -79,13 +76,6 @@ final class DisplayPowerController { // screen state returns. Playing the animation can also be somewhat slow. private static final boolean USE_ELECTRON_BEAM_ON_ANIMATION = false; - // If true, enables the use of the screen auto-brightness adjustment setting. - private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = - PowerManager.useScreenAutoBrightnessAdjustmentFeature(); - - // The maximum range of gamma adjustment possible using the screen - // auto-brightness adjustment setting. - private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f; // The minimum reduction in brightness when dimmed. private static final int SCREEN_DIM_MINIMUM_REDUCTION = 10; @@ -112,7 +102,6 @@ final class DisplayPowerController { private static final int MSG_UPDATE_POWER_STATE = 1; private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2; - private static final int MSG_UPDATE_AMBIENT_LUX = 3; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -125,32 +114,10 @@ final class DisplayPowerController { // Trigger proximity if distance is less than 5 cm. private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f; - // Light sensor event rate in milliseconds. - private static final int LIGHT_SENSOR_RATE_MILLIS = 1000; - - // Period of time in which to consider light samples in milliseconds. - private static final int AMBIENT_LIGHT_HORIZON = 10000; - - private static final int WEIGHTING_INTERCEPT = AMBIENT_LIGHT_HORIZON; - // Brightness animation ramp rate in brightness units per second. private static final int BRIGHTNESS_RAMP_RATE_FAST = 200; private static final int BRIGHTNESS_RAMP_RATE_SLOW = 40; - // 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 static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000; - private static final long DARKENING_LIGHT_DEBOUNCE = 8000; - - // Hysteresis constraints for brightening or darkening. - // The recent lux must have changed by at least this fraction relative to the - // current ambient lux before a change will be considered. - private static final float BRIGHTENING_LIGHT_HYSTERESIS = 0.10f; - private static final float DARKENING_LIGHT_HYSTERESIS = 0.20f; - private final Object mLock = new Object(); // Notifier for sending asynchronous notifications. @@ -174,18 +141,12 @@ final class DisplayPowerController { // The lights service. private final LightsManager mLights; - // The twilight service. - private final TwilightManager mTwilight; - // The sensor manager. private final SensorManager mSensorManager; // The proximity sensor, or null if not available or needed. private Sensor mProximitySensor; - // The light sensor, or null if not available or needed. - private Sensor mLightSensor; - // The dim screen brightness. private final int mScreenBrightnessDimConfig; @@ -198,15 +159,6 @@ final class DisplayPowerController { // True if auto-brightness should be used. private boolean mUseSoftwareAutoBrightnessConfig; - // The auto-brightness spline adjustment. - // The brightness values have been scaled to a range of 0..1. - private Spline mScreenAutoBrightnessSpline; - - // 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; - // True if we should fade the screen while turning it off, false if we should play // a stylish electron beam animation instead. private boolean mElectronBeamFadesConfig; @@ -276,56 +228,18 @@ final class DisplayPowerController { // The elapsed real time when the screen on was blocked. private long mScreenOnBlockStartRealTime; - // 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; - - // 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 mBrighteningLuxThreshold; - private float mDarkeningLuxThreshold; - - // The most recent light sample. - private float mLastObservedLux; - - // 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; - - private AmbientLightRingBuffer mAmbientLightRingBuffer; - - // The screen brightness level that has been chosen by the auto-brightness - // algorithm. The actual brightness should ramp towards this value. - // We preserve this value even when we stop using the light sensor so - // that we can quickly revert to the previous auto-brightness level - // while the light sensor warms up. - // Use -1 if there is no current auto-brightness value available. - private int mScreenAutoBrightness = -1; - - // The last screen auto-brightness gamma. (For printing in dump() only.) - private float mLastScreenAutoBrightnessGamma = 1.0f; - // True if the screen auto-brightness value is actually being used to // set the display brightness. private boolean mUsingScreenAutoBrightness; + // The controller for the automatic brightness level. + private AutomaticBrightnessController mAutomaticBrightnessController; + // Animators. private ObjectAnimator mElectronBeamOnAnimator; private ObjectAnimator mElectronBeamOffAnimator; private RampAnimator mScreenBrightnessRampAnimator; - // Twilight changed. We might recalculate auto-brightness values. - private boolean mTwilightChanged; - /** * Creates the display power controller. */ @@ -341,7 +255,6 @@ final class DisplayPowerController { mCallbackHandler = callbackHandler; mLights = lights; - mTwilight = twilight; mSensorManager = sensorManager; final Resources resources = context.getResources(); @@ -349,9 +262,11 @@ final class DisplayPowerController { mScreenBrightnessDimConfig = clampAbsoluteBrightness(resources.getInteger( com.android.internal.R.integer.config_screenBrightnessDim)); - int screenBrightnessMinimum = Math.min(resources.getInteger( + int screenBrightnessRangeMinimum = clampAbsoluteBrightness(Math.min(resources.getInteger( com.android.internal.R.integer.config_screenBrightnessSettingMinimum), - mScreenBrightnessDimConfig); + mScreenBrightnessDimConfig)); + + mScreenBrightnessRangeMaximum = PowerManager.BRIGHTNESS_ON; mUseSoftwareAutoBrightnessConfig = resources.getBoolean( com.android.internal.R.bool.config_automatic_brightness_available); @@ -360,9 +275,11 @@ final class DisplayPowerController { com.android.internal.R.array.config_autoBrightnessLevels); int[] screenBrightness = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); + int lightSensorWarmUpTimeConfig = resources.getInteger( + com.android.internal.R.integer.config_lightSensorWarmupTime); - mScreenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness); - if (mScreenAutoBrightnessSpline == null) { + Spline screenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness); + if (screenAutoBrightnessSpline == null) { Slog.e(TAG, "Error in config.xml. config_autoBrightnessLcdBacklightValues " + "(size " + screenBrightness.length + ") " + "must be monotic and have exactly one more entry than " @@ -371,19 +288,17 @@ final class DisplayPowerController { + "Auto-brightness will be disabled."); mUseSoftwareAutoBrightnessConfig = false; } else { - if (screenBrightness[0] < screenBrightnessMinimum) { - screenBrightnessMinimum = screenBrightness[0]; + if (screenBrightness[0] < screenBrightnessRangeMinimum) { + screenBrightnessRangeMinimum = clampAbsoluteBrightness(screenBrightness[0]); } + mAutomaticBrightnessController = new AutomaticBrightnessController(this, looper, + twilight, sensorManager, screenAutoBrightnessSpline, + lightSensorWarmUpTimeConfig, screenBrightnessRangeMinimum, + mScreenBrightnessRangeMaximum); } - - mLightSensorWarmUpTimeConfig = resources.getInteger( - com.android.internal.R.integer.config_lightSensorWarmupTime); } - mAmbientLightRingBuffer = new AmbientLightRingBuffer(); - - mScreenBrightnessRangeMinimum = clampAbsoluteBrightness(screenBrightnessMinimum); - mScreenBrightnessRangeMaximum = PowerManager.BRIGHTNESS_ON; + mScreenBrightnessRangeMinimum = screenBrightnessRangeMinimum; mElectronBeamFadesConfig = resources.getBoolean( com.android.internal.R.bool.config_animateScreenLights); @@ -396,39 +311,6 @@ final class DisplayPowerController { } } - if (mUseSoftwareAutoBrightnessConfig - && !DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { - mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); - } - - if (mUseSoftwareAutoBrightnessConfig && USE_TWILIGHT_ADJUSTMENT) { - mTwilight.registerListener(mTwilightListener, mHandler); - } - } - - private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) { - try { - final int n = brightness.length; - float[] x = new float[n]; - float[] y = new float[n]; - y[0] = normalizeAbsoluteBrightness(brightness[0]); - for (int i = 1; i < n; i++) { - x[i] = lux[i - 1]; - y[i] = normalizeAbsoluteBrightness(brightness[i]); - } - - Spline spline = Spline.createMonotoneCubicSpline(x, y); - if (DEBUG) { - Slog.d(TAG, "Auto-brightness spline: " + spline); - for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) { - Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v))); - } - } - return spline; - } catch (IllegalArgumentException ex) { - Slog.e(TAG, "Could not create auto-brightness spline.", ex); - return null; - } } /** @@ -550,9 +432,7 @@ final class DisplayPowerController { // Update the power state request. final boolean mustNotify; boolean mustInitialize = false; - boolean updateAutoBrightness = mTwilightChanged; boolean wasDim = false; - mTwilightChanged = false; synchronized (mLock) { mPendingUpdatePowerStateLocked = false; @@ -567,10 +447,6 @@ final class DisplayPowerController { mPendingRequestChangedLocked = false; mustInitialize = true; } else if (mPendingRequestChangedLocked) { - if (mPowerRequest.screenAutoBrightnessAdjustment - != mPendingRequestLocked.screenAutoBrightnessAdjustment) { - updateAutoBrightness = true; - } wasDim = (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DIM); mPowerRequest.copyFrom(mPendingRequestLocked); mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked; @@ -617,18 +493,19 @@ final class DisplayPowerController { } // Turn on the light sensor if needed. - if (mLightSensor != null) { - setLightSensorEnabled(mPowerRequest.useAutoBrightness - && wantScreenOn(mPowerRequest.screenState), updateAutoBrightness); + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.updatePowerState(mPowerRequest); } // Set the screen brightness. - if (wantScreenOn(mPowerRequest.screenState)) { + if (DisplayPowerRequest.wantScreenOn(mPowerRequest.screenState)) { int target; boolean slow; - if (mScreenAutoBrightness >= 0 && mLightSensorEnabled) { + int screenAutoBrightness = mAutomaticBrightnessController != null ? + mAutomaticBrightnessController.getAutomaticScreenBrightness() : -1; + if (screenAutoBrightness >= 0) { // Use current auto-brightness value. - target = mScreenAutoBrightness; + target = screenAutoBrightness; slow = mUsingScreenAutoBrightness; mUsingScreenAutoBrightness = true; } else { @@ -658,7 +535,7 @@ final class DisplayPowerController { // Animate the screen on or off. if (!mScreenOffBecauseOfProximity) { - if (wantScreenOn(mPowerRequest.screenState)) { + if (DisplayPowerRequest.wantScreenOn(mPowerRequest.screenState)) { // Want screen on. // Wait for previous off animation to complete beforehand. // It is relatively short but if we cancel it and switch to the @@ -735,6 +612,11 @@ final class DisplayPowerController { } } + @Override + public void updateBrightness() { + sendUpdatePowerState(); + } + private void blockScreenOn() { if (!mScreenOnWasBlocked) { mScreenOnWasBlocked = true; @@ -767,25 +649,8 @@ final class DisplayPowerController { } private int clampScreenBrightness(int value) { - return clamp(value, mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum); - } - - private static int clampAbsoluteBrightness(int value) { - return clamp(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); - } - - private static int clamp(int value, int min, int max) { - if (value <= min) { - return min; - } - if (value >= max) { - return max; - } - return value; - } - - private static float normalizeAbsoluteBrightness(int value) { - return (float)clampAbsoluteBrightness(value) / PowerManager.BRIGHTNESS_ON; + return MathUtils.constrain( + value, mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum); } private void animateScreenBrightness(int target, int rate) { @@ -886,321 +751,6 @@ final class DisplayPowerController { mPendingProximityDebounceTime = debounceTime; } - private void setLightSensorEnabled(boolean enable, boolean updateAutoBrightness) { - if (enable) { - if (!mLightSensorEnabled) { - updateAutoBrightness = true; - mLightSensorEnabled = true; - mLightSensorEnableTime = SystemClock.uptimeMillis(); - mSensorManager.registerListener(mLightSensorListener, mLightSensor, - LIGHT_SENSOR_RATE_MILLIS * 1000, mHandler); - } - } else { - if (mLightSensorEnabled) { - mLightSensorEnabled = false; - mAmbientLuxValid = false; - mRecentLightSamples = 0; - mAmbientLightRingBuffer.clear(); - mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); - mSensorManager.unregisterListener(mLightSensorListener); - } - } - if (updateAutoBrightness) { - updateAutoBrightness(false); - } - } - - private void handleLightSensorEvent(long time, float lux) { - mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); - - applyLightSensorMeasurement(time, lux); - updateAmbientLux(time); - } - - private void applyLightSensorMeasurement(long time, float lux) { - mRecentLightSamples++; - mAmbientLightRingBuffer.prune(time - AMBIENT_LIGHT_HORIZON); - mAmbientLightRingBuffer.push(time, lux); - - // Remember this sample value. - mLastObservedLux = lux; - mLastObservedLuxTime = time; - } - - private void setAmbientLux(float lux) { - mAmbientLux = lux; - mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS); - mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS); - } - - private float calculateAmbientLux(long now) { - final int N = mAmbientLightRingBuffer.size(); - if (N == 0) { - Slog.e(TAG, "calculateAmbientLux: No ambient light readings available"); - return 0; - } - float sum = 0; - float totalWeight = 0; - long endTime = 1; - for (int i = N - 1; i >= 0; i--) { - long startTime = (mAmbientLightRingBuffer.getTime(i) - now); - float weight = calculateWeight(startTime, endTime); - if (DEBUG) { - Slog.d(TAG, "calculateAmbientLux: [" + - (startTime) + ", " + - (endTime) + "]: " + weight); - } - totalWeight += weight; - sum += mAmbientLightRingBuffer.getLux(i) * weight; - endTime = startTime; - } - if (DEBUG) { - Slog.d(TAG, "calculateAmbientLux: totalWeight=" + totalWeight + - ", newAmbientLux=" + (sum / totalWeight)); - } - return sum / totalWeight; - } - - private static float calculateWeight(long startDelta, long endDelta) { - return weightIntegral(endDelta) - weightIntegral(startDelta); - } - - // Calculates the integral of y = x + WEIGHTING_INTERCEPT from 0 to x - private static float weightIntegral(long x) { - return x * (x * 0.5f + WEIGHTING_INTERCEPT); - } - - private long nextAmbientLightBrighteningTransition(long time) { - final int N = mAmbientLightRingBuffer.size(); - for (int i = 0; i < N; i++) { - if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) { - long nextCheck; - if (i + 1 < N) { - nextCheck = mAmbientLightRingBuffer.getTime(i+1) + AMBIENT_LIGHT_HORIZON; - } else { - nextCheck = time + BRIGHTENING_LIGHT_DEBOUNCE; - } - if (DEBUG) { - Slog.e(TAG, "{lux= " + mAmbientLightRingBuffer.getLux(i) + - ", time= " + mAmbientLightRingBuffer.getTime(i) + - "} is below brightening threshold, checking again in " + - TimeUtils.formatUptime(nextCheck)); - } - return nextCheck; - } - } - if (N == 0) { - long nextCheck = time + BRIGHTENING_LIGHT_DEBOUNCE; - if (DEBUG) { - Slog.e(TAG, "No data, checking again in " + TimeUtils.formatUptime(nextCheck)); - } - return nextCheck; - } - - if (time - mAmbientLightRingBuffer.getTime(0) < BRIGHTENING_LIGHT_DEBOUNCE) { - long nextCheck = mAmbientLightRingBuffer.getTime(0) + BRIGHTENING_LIGHT_DEBOUNCE; - if (DEBUG) { - Slog.e(TAG, "{lux= " + mAmbientLightRingBuffer.getLux(0) + - ", time= " + mAmbientLightRingBuffer.getTime(0) + - "} hasn't passed debounce threshold, checking again in " + - TimeUtils.formatUptime(nextCheck)); - } - return nextCheck; - } - - return 0; - } - - private long nextAmbientLightDarkeningTransition(long time) { - final int N = mAmbientLightRingBuffer.size(); - for (int i = 0; i < N; i ++) { - if (mAmbientLightRingBuffer.getLux(i) >= mDarkeningLuxThreshold) { - long nextCheck; - if (i + 1 < N) { - nextCheck = mAmbientLightRingBuffer.getTime(i+1) + AMBIENT_LIGHT_HORIZON; - } else { - nextCheck = time + DARKENING_LIGHT_DEBOUNCE; - } - if (DEBUG) { - Slog.e(TAG, "{lux= " + mAmbientLightRingBuffer.getLux(i) + - ", time= " + mAmbientLightRingBuffer.getTime(i) + - "} is above darkening threshold, checking again in " + - TimeUtils.formatUptime(nextCheck)); - } - return nextCheck; - } - } - if (N == 0) { - long nextCheck = time + DARKENING_LIGHT_DEBOUNCE; - if (DEBUG) { - Slog.e(TAG, "No data, checking again in " + TimeUtils.formatUptime(nextCheck)); - } - return nextCheck; - } - - if (time - mAmbientLightRingBuffer.getTime(0) < DARKENING_LIGHT_DEBOUNCE) { - long nextCheck = mAmbientLightRingBuffer.getTime(0) + DARKENING_LIGHT_DEBOUNCE; - if (DEBUG) { - Slog.e(TAG, "{lux= " + mAmbientLightRingBuffer.getLux(0) + - ", time= " + mAmbientLightRingBuffer.getTime(0) + - "} hasn't passed debounce threshold, checking again in " + - TimeUtils.formatUptime(nextCheck)); - } - return nextCheck; - } - return 0; - } - - private void updateAmbientLux() { - long time = SystemClock.uptimeMillis(); - mAmbientLightRingBuffer.prune(time - AMBIENT_LIGHT_HORIZON); - 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 (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: " - + "time=" + time - + ", timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); - } - mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, - timeWhenSensorWarmedUp); - return; - } - setAmbientLux(calculateAmbientLux(time)); - mAmbientLuxValid = true; - if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Initializing: " - + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer - + ", mAmbientLux=" + mAmbientLux); - } - updateAutoBrightness(true); - } - - long nextBrightenTransition = nextAmbientLightBrighteningTransition(time); - long nextDarkenTransition = nextAmbientLightDarkeningTransition(time); - - if (nextBrightenTransition == 0) { - setAmbientLux(calculateAmbientLux(time)); - if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Brightened: " - + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold - + ", mAmbientLightRingBuffer=" + mAmbientLightRingBuffer - + ", mAmbientLux=" + mAmbientLux); - } - updateAutoBrightness(true); - nextBrightenTransition = nextAmbientLightBrighteningTransition(time); - } else if (nextDarkenTransition == 0) { - setAmbientLux(calculateAmbientLux(time)); - if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Darkened: " - + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold - + ", mAmbientLightRingBuffer=" + mAmbientLightRingBuffer - + ", mAmbientLux=" + mAmbientLux); - } - updateAutoBrightness(true); - nextDarkenTransition = nextAmbientLightDarkeningTransition(time); - } - long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition); - if (nextTransitionTime > time) { - if (DEBUG) { - 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) { - if (!mAmbientLuxValid) { - return; - } - - float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux); - float gamma = 1.0f; - - if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT - && mPowerRequest.screenAutoBrightnessAdjustment != 0.0f) { - final float adjGamma = MathUtils.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA, - Math.min(1.0f, Math.max(-1.0f, - -mPowerRequest.screenAutoBrightnessAdjustment))); - gamma *= adjGamma; - if (DEBUG) { - Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma); - } - } - - if (USE_TWILIGHT_ADJUSTMENT) { - TwilightState state = mTwilight.getCurrentState(); - if (state != null && state.isNight()) { - final long now = System.currentTimeMillis(); - final float earlyGamma = - getTwilightGamma(now, state.getYesterdaySunset(), state.getTodaySunrise()); - final float lateGamma = - getTwilightGamma(now, state.getTodaySunset(), state.getTomorrowSunrise()); - gamma *= earlyGamma * lateGamma; - if (DEBUG) { - Slog.d(TAG, "updateAutoBrightness: earlyGamma=" + earlyGamma - + ", lateGamma=" + lateGamma); - } - } - } - - if (gamma != 1.0f) { - final float in = value; - value = FloatMath.pow(value, gamma); - if (DEBUG) { - Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma - + ", in=" + in + ", out=" + value); - } - } - - int newScreenAutoBrightness = clampScreenBrightness( - Math.round(value * PowerManager.BRIGHTNESS_ON)); - if (mScreenAutoBrightness != newScreenAutoBrightness) { - if (DEBUG) { - Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness=" - + mScreenAutoBrightness + ", newScreenAutoBrightness=" - + newScreenAutoBrightness); - } - - mScreenAutoBrightness = newScreenAutoBrightness; - mLastScreenAutoBrightnessGamma = gamma; - if (sendUpdate) { - sendUpdatePowerState(); - } - } - } - - private static float getTwilightGamma(long now, long lastSunset, long nextSunrise) { - if (lastSunset < 0 || nextSunrise < 0 - || now < lastSunset || now > nextSunrise) { - return 1.0f; - } - - if (now < lastSunset + TWILIGHT_ADJUSTMENT_TIME) { - return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA, - (float)(now - lastSunset) / TWILIGHT_ADJUSTMENT_TIME); - } - - if (now > nextSunrise - TWILIGHT_ADJUSTMENT_TIME) { - return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA, - (float)(nextSunrise - now) / TWILIGHT_ADJUSTMENT_TIME); - } - - return TWILIGHT_ADJUSTMENT_MAX_GAMMA; - } - - private static float lerp(float x, float y, float alpha) { - return x + (y - x) * alpha; - } - private void sendOnStateChangedWithWakelock() { mDisplaySuspendBlocker.acquire(); mCallbackHandler.post(mOnStateChangedRunnable); @@ -1259,8 +809,6 @@ final class DisplayPowerController { pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig); - pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline); - pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); mHandler.runWithScissors(new Runnable() { @Override @@ -1284,22 +832,7 @@ final class DisplayPowerController { pw.println(" mPendingProximityDebounceTime=" + TimeUtils.formatUptime(mPendingProximityDebounceTime)); pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity); - - pw.println(" mLightSensor=" + mLightSensor); - pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); - pw.println(" mLightSensorEnableTime=" - + TimeUtils.formatUptime(mLightSensorEnableTime)); - pw.println(" mAmbientLux=" + mAmbientLux); - pw.println(" mAmbientLuxValid=" + mAmbientLuxValid); - pw.println(" mLastObservedLux=" + mLastObservedLux); - pw.println(" mLastObservedLuxTime=" - + TimeUtils.formatUptime(mLastObservedLuxTime)); - pw.println(" mRecentLightSamples=" + mRecentLightSamples); - pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer); - pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness); pw.println(" mUsingScreenAutoBrightness=" + mUsingScreenAutoBrightness); - pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma); - pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState()); if (mElectronBeamOnAnimator != null) { pw.println(" mElectronBeamOnAnimator.isStarted()=" + @@ -1313,6 +846,11 @@ final class DisplayPowerController { if (mPowerState != null) { mPowerState.dump(pw); } + + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.dump(pw); + } + } private static String proximityToString(int state) { @@ -1328,13 +866,37 @@ final class DisplayPowerController { } } - private static boolean wantScreenOn(int state) { - switch (state) { - case DisplayPowerRequest.SCREEN_STATE_BRIGHT: - case DisplayPowerRequest.SCREEN_STATE_DIM: - return true; + private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) { + try { + final int n = brightness.length; + float[] x = new float[n]; + float[] y = new float[n]; + y[0] = normalizeAbsoluteBrightness(brightness[0]); + for (int i = 1; i < n; i++) { + x[i] = lux[i - 1]; + y[i] = normalizeAbsoluteBrightness(brightness[i]); + } + + Spline spline = Spline.createMonotoneCubicSpline(x, y); + if (DEBUG) { + Slog.d(TAG, "Auto-brightness spline: " + spline); + for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) { + Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v))); + } + } + return spline; + } catch (IllegalArgumentException ex) { + Slog.e(TAG, "Could not create auto-brightness spline.", ex); + return null; } - return false; + } + + private static float normalizeAbsoluteBrightness(int value) { + return (float)clampAbsoluteBrightness(value) / PowerManager.BRIGHTNESS_ON; + } + + private static int clampAbsoluteBrightness(int value) { + return MathUtils.constrain(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); } /** @@ -1361,10 +923,6 @@ final class DisplayPowerController { case MSG_PROXIMITY_SENSOR_DEBOUNCED: debounceProximitySensor(); break; - - case MSG_UPDATE_AMBIENT_LUX: - updateAmbientLux(); - break; } } } @@ -1385,175 +943,4 @@ final class DisplayPowerController { // Not used. } }; - - private final SensorEventListener mLightSensorListener = new SensorEventListener() { - @Override - public void onSensorChanged(SensorEvent event) { - if (mLightSensorEnabled) { - final long time = SystemClock.uptimeMillis(); - final float lux = event.values[0]; - handleLightSensorEvent(time, lux); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // Not used. - } - }; - - private final TwilightListener mTwilightListener = new TwilightListener() { - @Override - public void onTwilightStateChanged() { - mTwilightChanged = true; - updatePowerState(); - } - }; - - private static 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; - - public AmbientLightRingBuffer() { - this((int) (AMBIENT_LIGHT_HORIZON / LIGHT_SENSOR_RATE_MILLIS * BUFFER_SLACK)); - } - - public AmbientLightRingBuffer(int initialCapacity) { - mCapacity = initialCapacity; - mRingLux = new float[mCapacity]; - mRingTime = new long[mCapacity]; - mStart = 0; - mEnd = 0; - mCount = 0; - } - - public float getLux(int index) { - if (index >= size() || index < 0) { - throw new ArrayIndexOutOfBoundsException(index); - } - index += mStart; - if (index >= mCapacity) { - index -= mCapacity; - } - return mRingLux[index]; - } - - public long getTime(int index) { - if (index >= size() || index < 0) { - throw new ArrayIndexOutOfBoundsException(index); - } - index += mStart; - if (index >= mCapacity) { - index -= mCapacity; - } - return mRingTime[index]; - } - - 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, mCapacity - length); - System.arraycopy(mRingTime, 0, newRingTime, length, mCapacity - length); - } - 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) { - int removalCount = 0; - while(removalCount < size() && getTime(removalCount) <= horizon) { - removalCount++; - } - - if (removalCount > 0) { - // 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. - removalCount--; - - mCount -= removalCount; - mStart += removalCount; - if (mStart >= mCapacity) { - mStart -= mCapacity; - } - - if (getTime(0) < horizon) { - mRingTime[mStart] = horizon; - } - } - } - - public int size() { - return mCount; - } - - public boolean isEmpty() { - return mCount == 0; - } - - public void clear() { - mStart = 0; - mEnd = 0; - mCount = 0; - } - - @Override - public String toString() { - final int length = mCapacity - mStart; - float[] lux = new float[mCount]; - long[] time = new long[mCount]; - - if (mCount <= length) { - System.arraycopy(mRingLux, mStart, lux, 0, mCount); - System.arraycopy(mRingTime, mStart, time, 0, mCount); - } else { - System.arraycopy(mRingLux, mStart, lux, 0, length); - System.arraycopy(mRingLux, 0, lux, length, mCount - length); - - System.arraycopy(mRingTime, mStart, time, 0, length); - System.arraycopy(mRingTime, 0, time, length, mCount - length); - } - return "AmbientLightRingBuffer{mCapacity=" + mCapacity - + ", mStart=" + mStart - + ", mEnd=" + mEnd - + ", mCount=" + mCount - + ", mRingLux=" + Arrays.toString(lux) - + ", mRingTime=" + Arrays.toString(time) - + "}"; - } - } } diff --git a/services/core/java/com/android/server/power/DisplayPowerRequest.java b/services/core/java/com/android/server/power/DisplayPowerRequest.java index 22f17d77ef71d..7626f76b6b7a5 100644 --- a/services/core/java/com/android/server/power/DisplayPowerRequest.java +++ b/services/core/java/com/android/server/power/DisplayPowerRequest.java @@ -114,4 +114,13 @@ final class DisplayPowerRequest { + ", useAutoBrightness=" + useAutoBrightness + ", blockScreenOn=" + blockScreenOn; } + + public static boolean wantScreenOn(int state) { + switch(state) { + case SCREEN_STATE_DIM: + case SCREEN_STATE_BRIGHT: + return true; + } + return false; + } }