2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.server.display;
19 import com.android.server.EventLogTags;
20 import com.android.server.LocalServices;
21 import com.android.server.twilight.TwilightListener;
22 import com.android.server.twilight.TwilightManager;
23 import com.android.server.twilight.TwilightState;
25 import android.hardware.Sensor;
26 import android.hardware.SensorEvent;
27 import android.hardware.SensorEventListener;
28 import android.hardware.SensorManager;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.PowerManager;
33 import android.os.SystemClock;
34 import android.text.format.DateUtils;
35 import android.util.EventLog;
36 import android.util.MathUtils;
37 import android.util.Spline;
38 import android.util.Slog;
39 import android.util.TimeUtils;
41 import java.io.PrintWriter;
43 class AutomaticBrightnessController {
44 private static final String TAG = "AutomaticBrightnessController";
46 private static final boolean DEBUG = false;
47 private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
49 // If true, enables the use of the screen auto-brightness adjustment setting.
50 private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = true;
52 // Hysteresis constraints for brightening or darkening.
53 // The recent lux must have changed by at least this fraction relative to the
54 // current ambient lux before a change will be considered.
55 private static final float BRIGHTENING_LIGHT_HYSTERESIS = 0.10f;
56 private static final float DARKENING_LIGHT_HYSTERESIS = 0.20f;
58 // How long the current sensor reading is assumed to be valid beyond the current time.
59 // This provides a bit of prediction, as well as ensures that the weight for the last sample is
60 // non-zero, which in turn ensures that the total weight is non-zero.
61 private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100;
63 // Specifies the maximum magnitude of the time of day adjustment.
64 private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1f;
66 // Debounce for sampling user-initiated changes in display brightness to ensure
67 // the user is satisfied with the result before storing the sample.
68 private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000;
70 private static final int MSG_UPDATE_AMBIENT_LUX = 1;
71 private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
73 // Callbacks for requesting updates to the the display's power state
74 private final Callbacks mCallbacks;
76 // The sensor manager.
77 private final SensorManager mSensorManager;
79 // The light sensor, or null if not available or needed.
80 private final Sensor mLightSensor;
82 // The twilight service.
83 private final TwilightManager mTwilight;
85 // The auto-brightness spline adjustment.
86 // The brightness values have been scaled to a range of 0..1.
87 private final Spline mScreenAutoBrightnessSpline;
89 // The minimum and maximum screen brightnesses.
90 private final int mScreenBrightnessRangeMinimum;
91 private final int mScreenBrightnessRangeMaximum;
92 private final float mDozeScaleFactor;
94 // Light sensor event rate in milliseconds.
95 private final int mLightSensorRate;
97 // Stability requirements in milliseconds for accepting a new brightness level. This is used
98 // for debouncing the light sensor. Different constants are used to debounce the light sensor
99 // when adapting to brighter or darker environments. This parameter controls how quickly
100 // brightness changes occur in response to an observed change in light level that exceeds the
101 // hysteresis threshold.
102 private final long mBrighteningLightDebounceConfig;
103 private final long mDarkeningLightDebounceConfig;
105 // If true immediately after the screen is turned on the controller will try to adjust the
106 // brightness based on the current sensor reads. If false, the controller will collect more data
107 // and only then decide whether to change brightness.
108 private final boolean mResetAmbientLuxAfterWarmUpConfig;
110 // Period of time in which to consider light samples in milliseconds.
111 private final int mAmbientLightHorizon;
113 // The intercept used for the weighting calculation. This is used in order to keep all possible
114 // weighting values positive.
115 private final int mWeightingIntercept;
117 // Amount of time to delay auto-brightness after screen on while waiting for
118 // the light sensor to warm-up in milliseconds.
119 // May be 0 if no warm-up is required.
120 private int mLightSensorWarmUpTimeConfig;
122 // Set to true if the light sensor is enabled.
123 private boolean mLightSensorEnabled;
125 // The time when the light sensor was enabled.
126 private long mLightSensorEnableTime;
128 // The currently accepted nominal ambient light level.
129 private float mAmbientLux;
131 // True if mAmbientLux holds a valid value.
132 private boolean mAmbientLuxValid;
134 // The ambient light level threshold at which to brighten or darken the screen.
135 private float mBrighteningLuxThreshold;
136 private float mDarkeningLuxThreshold;
138 // The most recent light sample.
139 private float mLastObservedLux;
141 // The time of the most light recent sample.
142 private long mLastObservedLuxTime;
144 // The number of light samples collected since the light sensor was enabled.
145 private int mRecentLightSamples;
147 // A ring buffer containing all of the recent ambient light sensor readings.
148 private AmbientLightRingBuffer mAmbientLightRingBuffer;
150 // A ring buffer containing the light sensor readings for the initial horizon period.
151 private AmbientLightRingBuffer mInitialHorizonAmbientLightRingBuffer;
154 private AutomaticBrightnessHandler mHandler;
156 // The screen brightness level that has been chosen by the auto-brightness
157 // algorithm. The actual brightness should ramp towards this value.
158 // We preserve this value even when we stop using the light sensor so
159 // that we can quickly revert to the previous auto-brightness level
160 // while the light sensor warms up.
161 // Use -1 if there is no current auto-brightness value available.
162 private int mScreenAutoBrightness = -1;
164 // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter)
165 private float mScreenAutoBrightnessAdjustment = 0.0f;
167 // The maximum range of gamma adjustment possible using the screen
168 // auto-brightness adjustment setting.
169 private float mScreenAutoBrightnessAdjustmentMaxGamma;
171 // The last screen auto-brightness gamma. (For printing in dump() only.)
172 private float mLastScreenAutoBrightnessGamma = 1.0f;
174 // Are we going to adjust brightness while dozing.
175 private boolean mDozing;
177 // True if we are collecting a brightness adjustment sample, along with some data
178 // for the initial state of the sample.
179 private boolean mBrightnessAdjustmentSamplePending;
180 private float mBrightnessAdjustmentSampleOldAdjustment;
181 private float mBrightnessAdjustmentSampleOldLux;
182 private int mBrightnessAdjustmentSampleOldBrightness;
183 private float mBrightnessAdjustmentSampleOldGamma;
185 private boolean mUseTwilight;
187 public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
188 SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime,
189 int brightnessMin, int brightnessMax, float dozeScaleFactor,
190 int lightSensorRate, long brighteningLightDebounceConfig,
191 long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
192 int ambientLightHorizon, float autoBrightnessAdjustmentMaxGamma ) {
193 mCallbacks = callbacks;
194 mTwilight = LocalServices.getService(TwilightManager.class);
195 mSensorManager = sensorManager;
196 mScreenAutoBrightnessSpline = autoBrightnessSpline;
197 mScreenBrightnessRangeMinimum = brightnessMin;
198 mScreenBrightnessRangeMaximum = brightnessMax;
199 mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
200 mDozeScaleFactor = dozeScaleFactor;
201 mLightSensorRate = lightSensorRate;
202 mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
203 mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
204 mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
205 mAmbientLightHorizon = ambientLightHorizon;
206 mWeightingIntercept = ambientLightHorizon;
207 mScreenAutoBrightnessAdjustmentMaxGamma = autoBrightnessAdjustmentMaxGamma;
209 mHandler = new AutomaticBrightnessHandler(looper);
210 mAmbientLightRingBuffer =
211 new AmbientLightRingBuffer(mLightSensorRate, mAmbientLightHorizon);
212 mInitialHorizonAmbientLightRingBuffer =
213 new AmbientLightRingBuffer(mLightSensorRate, mAmbientLightHorizon);
215 if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
216 mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
220 public int getAutomaticScreenBrightness() {
222 return (int) (mScreenAutoBrightness * mDozeScaleFactor);
224 return mScreenAutoBrightness;
227 public void configure(boolean enable, float adjustment, boolean dozing,
228 boolean userInitiatedChange, boolean useTwilight) {
229 // While dozing, the application processor may be suspended which will prevent us from
230 // receiving new information from the light sensor. On some devices, we may be able to
231 // switch to a wake-up light sensor instead but for now we will simply disable the sensor
232 // and hold onto the last computed screen auto brightness. We save the dozing flag for
233 // debugging purposes.
235 boolean changed = setLightSensorEnabled(enable && !dozing);
236 changed |= setScreenAutoBrightnessAdjustment(adjustment);
237 changed |= setUseTwilight(useTwilight);
239 updateAutoBrightness(false /*sendUpdate*/);
241 if (enable && !dozing && userInitiatedChange) {
242 prepareBrightnessAdjustmentSample();
246 private boolean setUseTwilight(boolean useTwilight) {
247 if (mUseTwilight == useTwilight) return false;
249 mTwilight.registerListener(mTwilightListener, mHandler);
251 mTwilight.unregisterListener(mTwilightListener);
253 mUseTwilight = useTwilight;
257 public void dump(PrintWriter pw) {
259 pw.println("Automatic Brightness Controller Configuration:");
260 pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline);
261 pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
262 pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
263 pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
264 pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
265 pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
266 pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
269 pw.println("Automatic Brightness Controller State:");
270 pw.println(" mLightSensor=" + mLightSensor);
271 pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState());
272 pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
273 pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
274 pw.println(" mAmbientLux=" + mAmbientLux);
275 pw.println(" mAmbientLightHorizon=" + mAmbientLightHorizon);
276 pw.println(" mBrighteningLuxThreshold=" + mBrighteningLuxThreshold);
277 pw.println(" mDarkeningLuxThreshold=" + mDarkeningLuxThreshold);
278 pw.println(" mLastObservedLux=" + mLastObservedLux);
279 pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
280 pw.println(" mRecentLightSamples=" + mRecentLightSamples);
281 pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
282 pw.println(" mInitialHorizonAmbientLightRingBuffer=" +
283 mInitialHorizonAmbientLightRingBuffer);
284 pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness);
285 pw.println(" mScreenAutoBrightnessAdjustment=" + mScreenAutoBrightnessAdjustment);
286 pw.println(" mScreenAutoBrightnessAdjustmentMaxGamma=" + mScreenAutoBrightnessAdjustmentMaxGamma);
287 pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma);
288 pw.println(" mDozing=" + mDozing);
291 private boolean setLightSensorEnabled(boolean enable) {
293 if (!mLightSensorEnabled) {
294 mLightSensorEnabled = true;
295 mLightSensorEnableTime = SystemClock.uptimeMillis();
296 mSensorManager.registerListener(mLightSensorListener, mLightSensor,
297 mLightSensorRate * 1000, mHandler);
301 if (mLightSensorEnabled) {
302 mLightSensorEnabled = false;
303 mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig;
304 mRecentLightSamples = 0;
305 mAmbientLightRingBuffer.clear();
306 mInitialHorizonAmbientLightRingBuffer.clear();
307 mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
308 mSensorManager.unregisterListener(mLightSensorListener);
314 private void handleLightSensorEvent(long time, float lux) {
315 mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
317 applyLightSensorMeasurement(time, lux);
318 updateAmbientLux(time);
321 private void applyLightSensorMeasurement(long time, float lux) {
322 mRecentLightSamples++;
323 // Store all of the light measurements for the intial horizon period. This is to help
324 // diagnose dim wake ups and slow responses in b/27951906.
325 if (time <= mLightSensorEnableTime + mAmbientLightHorizon) {
326 mInitialHorizonAmbientLightRingBuffer.push(time, lux);
328 mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon);
329 mAmbientLightRingBuffer.push(time, lux);
331 // Remember this sample value.
332 mLastObservedLux = lux;
333 mLastObservedLuxTime = time;
336 private boolean setScreenAutoBrightnessAdjustment(float adjustment) {
337 if (adjustment != mScreenAutoBrightnessAdjustment) {
338 mScreenAutoBrightnessAdjustment = adjustment;
344 private void setAmbientLux(float lux) {
346 mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
347 mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
350 private float calculateAmbientLux(long now) {
351 final int N = mAmbientLightRingBuffer.size();
353 Slog.e(TAG, "calculateAmbientLux: No ambient light readings available");
357 float totalWeight = 0;
358 long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
359 for (int i = N - 1; i >= 0; i--) {
360 long startTime = (mAmbientLightRingBuffer.getTime(i) - now);
361 float weight = calculateWeight(startTime, endTime);
362 float lux = mAmbientLightRingBuffer.getLux(i);
364 Slog.d(TAG, "calculateAmbientLux: [" +
366 (endTime) + "]: lux=" + lux + ", weight=" + weight);
368 totalWeight += weight;
369 sum += mAmbientLightRingBuffer.getLux(i) * weight;
373 Slog.d(TAG, "calculateAmbientLux: totalWeight=" + totalWeight +
374 ", newAmbientLux=" + (sum / totalWeight));
376 return sum / totalWeight;
379 private float calculateWeight(long startDelta, long endDelta) {
380 return weightIntegral(endDelta) - weightIntegral(startDelta);
383 // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the
384 // horizon we're looking at and provides a non-linear weighting for light samples.
385 private float weightIntegral(long x) {
386 return x * (x * 0.5f + mWeightingIntercept);
389 private long nextAmbientLightBrighteningTransition(long time) {
390 final int N = mAmbientLightRingBuffer.size();
391 long earliestValidTime = time;
392 for (int i = N - 1; i >= 0; i--) {
393 if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) {
396 earliestValidTime = mAmbientLightRingBuffer.getTime(i);
398 return earliestValidTime + mBrighteningLightDebounceConfig;
401 private long nextAmbientLightDarkeningTransition(long time) {
402 final int N = mAmbientLightRingBuffer.size();
403 long earliestValidTime = time;
404 for (int i = N - 1; i >= 0; i--) {
405 if (mAmbientLightRingBuffer.getLux(i) >= mDarkeningLuxThreshold) {
408 earliestValidTime = mAmbientLightRingBuffer.getTime(i);
410 return earliestValidTime + mDarkeningLightDebounceConfig;
413 private void updateAmbientLux() {
414 long time = SystemClock.uptimeMillis();
415 mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon);
416 updateAmbientLux(time);
419 private void updateAmbientLux(long time) {
420 // If the light sensor was just turned on then immediately update our initial
421 // estimate of the current ambient light level.
422 if (!mAmbientLuxValid) {
423 final long timeWhenSensorWarmedUp =
424 mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
425 if (time < timeWhenSensorWarmedUp) {
427 Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: "
429 + ", timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp);
431 mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX,
432 timeWhenSensorWarmedUp);
435 setAmbientLux(calculateAmbientLux(time));
436 mAmbientLuxValid = true;
438 Slog.d(TAG, "updateAmbientLux: Initializing: "
439 + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer
440 + ", mAmbientLux=" + mAmbientLux);
442 updateAutoBrightness(true);
445 long nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
446 long nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
447 float ambientLux = calculateAmbientLux(time);
449 if (ambientLux >= mBrighteningLuxThreshold && nextBrightenTransition <= time
450 || ambientLux <= mDarkeningLuxThreshold && nextDarkenTransition <= time) {
451 setAmbientLux(ambientLux);
453 Slog.d(TAG, "updateAmbientLux: "
454 + ((ambientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
455 + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
456 + ", mAmbientLightRingBuffer=" + mAmbientLightRingBuffer
457 + ", mAmbientLux=" + mAmbientLux);
459 updateAutoBrightness(true);
460 nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
461 nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
463 long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition);
464 // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't
465 // exceed the necessary threshold, then it's possible we'll get a transition time prior to
466 // now. Rather than continually checking to see whether the weighted lux exceeds the
467 // threshold, schedule an update for when we'd normally expect another light sample, which
468 // should be enough time to decide whether we should actually transition to the new
469 // weighted ambient lux or not.
471 nextTransitionTime > time ? nextTransitionTime : time + mLightSensorRate;
473 Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for "
474 + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
476 mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime);
479 private void updateAutoBrightness(boolean sendUpdate) {
480 if (!mAmbientLuxValid) {
484 float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux);
487 if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT
488 && mScreenAutoBrightnessAdjustment != 0.0f) {
489 final float adjGamma = MathUtils.pow(mScreenAutoBrightnessAdjustmentMaxGamma,
490 Math.min(1.0f, Math.max(-1.0f, -mScreenAutoBrightnessAdjustment)));
493 Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma);
498 TwilightState state = mTwilight.getCurrentState();
499 if (state != null && state.isNight()) {
500 final long now = System.currentTimeMillis();
501 gamma *= 1 + state.getAmount() * TWILIGHT_ADJUSTMENT_MAX_GAMMA;
503 Slog.d(TAG, "updateAutoBrightness: twilight amount=" + state.getAmount());
509 final float in = value;
510 value = MathUtils.pow(value, gamma);
512 Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma
513 + ", in=" + in + ", out=" + value);
517 int newScreenAutoBrightness =
518 clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
519 if (mScreenAutoBrightness != newScreenAutoBrightness) {
521 Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness="
522 + mScreenAutoBrightness + ", newScreenAutoBrightness="
523 + newScreenAutoBrightness);
526 mScreenAutoBrightness = newScreenAutoBrightness;
527 mLastScreenAutoBrightnessGamma = gamma;
529 mCallbacks.updateBrightness();
534 private int clampScreenBrightness(int value) {
535 return MathUtils.constrain(value,
536 mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
539 private void prepareBrightnessAdjustmentSample() {
540 if (!mBrightnessAdjustmentSamplePending) {
541 mBrightnessAdjustmentSamplePending = true;
542 mBrightnessAdjustmentSampleOldAdjustment = mScreenAutoBrightnessAdjustment;
543 mBrightnessAdjustmentSampleOldLux = mAmbientLuxValid ? mAmbientLux : -1;
544 mBrightnessAdjustmentSampleOldBrightness = mScreenAutoBrightness;
545 mBrightnessAdjustmentSampleOldGamma = mLastScreenAutoBrightnessGamma;
547 mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE);
550 mHandler.sendEmptyMessageDelayed(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE,
551 BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS);
554 private void cancelBrightnessAdjustmentSample() {
555 if (mBrightnessAdjustmentSamplePending) {
556 mBrightnessAdjustmentSamplePending = false;
557 mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE);
561 private void collectBrightnessAdjustmentSample() {
562 if (mBrightnessAdjustmentSamplePending) {
563 mBrightnessAdjustmentSamplePending = false;
564 if (mAmbientLuxValid && mScreenAutoBrightness >= 0) {
566 Slog.d(TAG, "Auto-brightness adjustment changed by user: "
567 + "adj=" + mScreenAutoBrightnessAdjustment
568 + ", lux=" + mAmbientLux
569 + ", brightness=" + mScreenAutoBrightness
570 + ", gamma=" + mLastScreenAutoBrightnessGamma
571 + ", ring=" + mAmbientLightRingBuffer);
574 EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ,
575 mBrightnessAdjustmentSampleOldAdjustment,
576 mBrightnessAdjustmentSampleOldLux,
577 mBrightnessAdjustmentSampleOldBrightness,
578 mBrightnessAdjustmentSampleOldGamma,
579 mScreenAutoBrightnessAdjustment,
581 mScreenAutoBrightness,
582 mLastScreenAutoBrightnessGamma);
587 private final class AutomaticBrightnessHandler extends Handler {
588 public AutomaticBrightnessHandler(Looper looper) {
589 super(looper, null, true /*async*/);
593 public void handleMessage(Message msg) {
595 case MSG_UPDATE_AMBIENT_LUX:
599 case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE:
600 collectBrightnessAdjustmentSample();
606 private final SensorEventListener mLightSensorListener = new SensorEventListener() {
608 public void onSensorChanged(SensorEvent event) {
609 if (mLightSensorEnabled) {
610 final long time = SystemClock.uptimeMillis();
611 final float lux = event.values[0];
612 handleLightSensorEvent(time, lux);
617 public void onAccuracyChanged(Sensor sensor, int accuracy) {
622 private final TwilightListener mTwilightListener = new TwilightListener() {
624 public void onTwilightStateChanged() {
625 updateAutoBrightness(true /*sendUpdate*/);
629 /** Callbacks to request updates to the display's power state. */
630 interface Callbacks {
631 void updateBrightness();
634 private static final class AmbientLightRingBuffer {
635 // Proportional extra capacity of the buffer beyond the expected number of light samples
637 private static final float BUFFER_SLACK = 1.5f;
638 private float[] mRingLux;
639 private long[] mRingTime;
640 private int mCapacity;
642 // The first valid element and the next open slot.
643 // Note that if mCount is zero then there are no valid elements.
648 public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
649 mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
650 mRingLux = new float[mCapacity];
651 mRingTime = new long[mCapacity];
654 public float getLux(int index) {
655 return mRingLux[offsetOf(index)];
658 public long getTime(int index) {
659 return mRingTime[offsetOf(index)];
662 public void push(long time, float lux) {
664 if (mCount == mCapacity) {
665 int newSize = mCapacity * 2;
667 float[] newRingLux = new float[newSize];
668 long[] newRingTime = new long[newSize];
669 int length = mCapacity - mStart;
670 System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
671 System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
673 System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
674 System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
676 mRingLux = newRingLux;
677 mRingTime = newRingTime;
683 mRingTime[next] = time;
684 mRingLux[next] = lux;
686 if (mEnd == mCapacity) {
692 public void prune(long horizon) {
698 int next = mStart + 1;
699 if (next >= mCapacity) {
702 if (mRingTime[next] > horizon) {
703 // Some light sensors only produce data upon a change in the ambient light
704 // levels, so we need to consider the previous measurement as the ambient light
705 // level for all points in time up until we receive a new measurement. Thus, we
706 // always want to keep the youngest element that would be removed from the
707 // buffer and just set its measurement time to the horizon time since at that
708 // point it is the ambient light level, and to remove it would be to drop a
709 // valid data point within our horizon.
716 if (mRingTime[mStart] < horizon) {
717 mRingTime[mStart] = horizon;
725 public void clear() {
732 public String toString() {
733 StringBuffer buf = new StringBuffer();
735 for (int i = 0; i < mCount; i++) {
736 final long next = i + 1 < mCount ? getTime(i + 1) : SystemClock.uptimeMillis();
740 buf.append(getLux(i));
742 buf.append(next - getTime(i));
746 return buf.toString();
749 private int offsetOf(int index) {
750 if (index >= mCount || index < 0) {
751 throw new ArrayIndexOutOfBoundsException(index);
754 if (index >= mCapacity) {