AutomaticBrightnessController.java revision 6fc42f549a6a67fe48e6e8e368dc2b164030d7c3
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
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
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package com.android.server.display;
18
19import com.android.server.EventLogTags;
20import com.android.server.LocalServices;
21import com.android.server.twilight.TwilightListener;
22import com.android.server.twilight.TwilightManager;
23import com.android.server.twilight.TwilightState;
24
25import android.hardware.Sensor;
26import android.hardware.SensorEvent;
27import android.hardware.SensorEventListener;
28import android.hardware.SensorManager;
29import android.os.Handler;
30import android.os.Looper;
31import android.os.Message;
32import android.os.PowerManager;
33import android.os.SystemClock;
34import android.text.format.DateUtils;
35import android.util.EventLog;
36import android.util.MathUtils;
37import android.util.Spline;
38import android.util.Slog;
39import android.util.TimeUtils;
40
41import java.io.PrintWriter;
42
43class AutomaticBrightnessController {
44    private static final String TAG = "AutomaticBrightnessController";
45
46    private static final boolean DEBUG = false;
47    private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
48
49    // If true, enables the use of the screen auto-brightness adjustment setting.
50    private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = true;
51
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;
57
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;
62
63    // If true, enables the use of the current time as an auto-brightness adjustment.
64    // The basic idea here is to expand the dynamic range of auto-brightness
65    // when it is especially dark outside.  The light sensor tends to perform
66    // poorly at low light levels so we compensate for it by making an
67    // assumption about the environment.
68    private static final boolean USE_TWILIGHT_ADJUSTMENT =
69            PowerManager.useTwilightAdjustmentFeature();
70
71    // Specifies the maximum magnitude of the time of day adjustment.
72    private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1.5f;
73
74    // The amount of time after or before sunrise over which to start adjusting
75    // the gamma.  We want the change to happen gradually so that it is below the
76    // threshold of perceptibility and so that the adjustment has maximum effect
77    // well after dusk.
78    private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2;
79
80    // Debounce for sampling user-initiated changes in display brightness to ensure
81    // the user is satisfied with the result before storing the sample.
82    private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000;
83
84    private static final int MSG_UPDATE_AMBIENT_LUX = 1;
85    private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
86
87    // Callbacks for requesting updates to the the display's power state
88    private final Callbacks mCallbacks;
89
90    // The sensor manager.
91    private final SensorManager mSensorManager;
92
93    // The light sensor, or null if not available or needed.
94    private final Sensor mLightSensor;
95
96    // The twilight service.
97    private final TwilightManager mTwilight;
98
99    // The auto-brightness spline adjustment.
100    // The brightness values have been scaled to a range of 0..1.
101    private final Spline mScreenAutoBrightnessSpline;
102
103    // The minimum and maximum screen brightnesses.
104    private final int mScreenBrightnessRangeMinimum;
105    private final int mScreenBrightnessRangeMaximum;
106    private final float mDozeScaleFactor;
107
108    // Light sensor event rate in milliseconds.
109    private final int mLightSensorRate;
110
111    // Stability requirements in milliseconds for accepting a new brightness level.  This is used
112    // for debouncing the light sensor.  Different constants are used to debounce the light sensor
113    // when adapting to brighter or darker environments.  This parameter controls how quickly
114    // brightness changes occur in response to an observed change in light level that exceeds the
115    // hysteresis threshold.
116    private final long mBrighteningLightDebounceConfig;
117    private final long mDarkeningLightDebounceConfig;
118
119    // If true immediately after the screen is turned on the controller will try to adjust the
120    // brightness based on the current sensor reads. If false, the controller will collect more data
121    // and only then decide whether to change brightness.
122    private final boolean mResetAmbientLuxAfterWarmUpConfig;
123
124    // Period of time in which to consider light samples in milliseconds.
125    private final int mAmbientLightHorizon;
126
127    // The intercept used for the weighting calculation. This is used in order to keep all possible
128    // weighting values positive.
129    private final int mWeightingIntercept;
130
131    // Amount of time to delay auto-brightness after screen on while waiting for
132    // the light sensor to warm-up in milliseconds.
133    // May be 0 if no warm-up is required.
134    private int mLightSensorWarmUpTimeConfig;
135
136    // Set to true if the light sensor is enabled.
137    private boolean mLightSensorEnabled;
138
139    // The time when the light sensor was enabled.
140    private long mLightSensorEnableTime;
141
142    // The currently accepted nominal ambient light level.
143    private float mAmbientLux;
144
145    // True if mAmbientLux holds a valid value.
146    private boolean mAmbientLuxValid;
147
148    // The ambient light level threshold at which to brighten or darken the screen.
149    private float mBrighteningLuxThreshold;
150    private float mDarkeningLuxThreshold;
151
152    // The most recent light sample.
153    private float mLastObservedLux;
154
155    // The time of the most light recent sample.
156    private long mLastObservedLuxTime;
157
158    // The number of light samples collected since the light sensor was enabled.
159    private int mRecentLightSamples;
160
161    // A ring buffer containing all of the recent ambient light sensor readings.
162    private AmbientLightRingBuffer mAmbientLightRingBuffer;
163
164    // The handler
165    private AutomaticBrightnessHandler mHandler;
166
167    // The screen brightness level that has been chosen by the auto-brightness
168    // algorithm.  The actual brightness should ramp towards this value.
169    // We preserve this value even when we stop using the light sensor so
170    // that we can quickly revert to the previous auto-brightness level
171    // while the light sensor warms up.
172    // Use -1 if there is no current auto-brightness value available.
173    private int mScreenAutoBrightness = -1;
174
175    // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter)
176    private float mScreenAutoBrightnessAdjustment = 0.0f;
177
178    // The maximum range of gamma adjustment possible using the screen
179    // auto-brightness adjustment setting.
180    private float mScreenAutoBrightnessAdjustmentMaxGamma;
181
182    // The last screen auto-brightness gamma.  (For printing in dump() only.)
183    private float mLastScreenAutoBrightnessGamma = 1.0f;
184
185    // Are we going to adjust brightness while dozing.
186    private boolean mDozing;
187
188    // True if we are collecting a brightness adjustment sample, along with some data
189    // for the initial state of the sample.
190    private boolean mBrightnessAdjustmentSamplePending;
191    private float mBrightnessAdjustmentSampleOldAdjustment;
192    private float mBrightnessAdjustmentSampleOldLux;
193    private int mBrightnessAdjustmentSampleOldBrightness;
194    private float mBrightnessAdjustmentSampleOldGamma;
195
196    public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
197            SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime,
198            int brightnessMin, int brightnessMax, float dozeScaleFactor,
199            int lightSensorRate, long brighteningLightDebounceConfig,
200            long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
201            int ambientLightHorizon, float autoBrightnessAdjustmentMaxGamma ) {
202        mCallbacks = callbacks;
203        mTwilight = LocalServices.getService(TwilightManager.class);
204        mSensorManager = sensorManager;
205        mScreenAutoBrightnessSpline = autoBrightnessSpline;
206        mScreenBrightnessRangeMinimum = brightnessMin;
207        mScreenBrightnessRangeMaximum = brightnessMax;
208        mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
209        mDozeScaleFactor = dozeScaleFactor;
210        mLightSensorRate = lightSensorRate;
211        mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
212        mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
213        mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
214        mAmbientLightHorizon = ambientLightHorizon;
215        mWeightingIntercept = ambientLightHorizon;
216        mScreenAutoBrightnessAdjustmentMaxGamma = autoBrightnessAdjustmentMaxGamma;
217
218        mHandler = new AutomaticBrightnessHandler(looper);
219        mAmbientLightRingBuffer = new AmbientLightRingBuffer(mLightSensorRate, mAmbientLightHorizon);
220
221        if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
222            mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
223        }
224
225        if (USE_TWILIGHT_ADJUSTMENT) {
226            mTwilight.registerListener(mTwilightListener, mHandler);
227        }
228    }
229
230    public int getAutomaticScreenBrightness() {
231        if (mDozing) {
232            return (int) (mScreenAutoBrightness * mDozeScaleFactor);
233        }
234        return mScreenAutoBrightness;
235    }
236
237    public void configure(boolean enable, float adjustment, boolean dozing,
238            boolean userInitiatedChange) {
239        // While dozing, the application processor may be suspended which will prevent us from
240        // receiving new information from the light sensor. On some devices, we may be able to
241        // switch to a wake-up light sensor instead but for now we will simply disable the sensor
242        // and hold onto the last computed screen auto brightness.  We save the dozing flag for
243        // debugging purposes.
244        mDozing = dozing;
245        boolean changed = setLightSensorEnabled(enable && !dozing);
246        changed |= setScreenAutoBrightnessAdjustment(adjustment);
247        if (changed) {
248            updateAutoBrightness(false /*sendUpdate*/);
249        }
250        if (enable && !dozing && userInitiatedChange) {
251            prepareBrightnessAdjustmentSample();
252        }
253    }
254
255    public void dump(PrintWriter pw) {
256        pw.println();
257        pw.println("Automatic Brightness Controller Configuration:");
258        pw.println("  mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline);
259        pw.println("  mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
260        pw.println("  mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
261        pw.println("  mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
262        pw.println("  mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
263        pw.println("  mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
264        pw.println("  mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
265
266        pw.println();
267        pw.println("Automatic Brightness Controller State:");
268        pw.println("  mLightSensor=" + mLightSensor);
269        pw.println("  mTwilight.getCurrentState()=" + mTwilight.getCurrentState());
270        pw.println("  mLightSensorEnabled=" + mLightSensorEnabled);
271        pw.println("  mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
272        pw.println("  mAmbientLux=" + mAmbientLux);
273        pw.println("  mAmbientLightHorizon=" + mAmbientLightHorizon);
274        pw.println("  mBrighteningLuxThreshold=" + mBrighteningLuxThreshold);
275        pw.println("  mDarkeningLuxThreshold=" + mDarkeningLuxThreshold);
276        pw.println("  mLastObservedLux=" + mLastObservedLux);
277        pw.println("  mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
278        pw.println("  mRecentLightSamples=" + mRecentLightSamples);
279        pw.println("  mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
280        pw.println("  mScreenAutoBrightness=" + mScreenAutoBrightness);
281        pw.println("  mScreenAutoBrightnessAdjustment=" + mScreenAutoBrightnessAdjustment);
282        pw.println("  mScreenAutoBrightnessAdjustmentMaxGamma=" + mScreenAutoBrightnessAdjustmentMaxGamma);
283        pw.println("  mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma);
284        pw.println("  mDozing=" + mDozing);
285    }
286
287    private boolean setLightSensorEnabled(boolean enable) {
288        if (enable) {
289            if (!mLightSensorEnabled) {
290                mLightSensorEnabled = true;
291                mLightSensorEnableTime = SystemClock.uptimeMillis();
292                mSensorManager.registerListener(mLightSensorListener, mLightSensor,
293                        mLightSensorRate * 1000, mHandler);
294                return true;
295            }
296        } else {
297            if (mLightSensorEnabled) {
298                mLightSensorEnabled = false;
299                mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig;
300                mRecentLightSamples = 0;
301                mAmbientLightRingBuffer.clear();
302                mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
303                mSensorManager.unregisterListener(mLightSensorListener);
304            }
305        }
306        return false;
307    }
308
309    private void handleLightSensorEvent(long time, float lux) {
310        mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
311
312        applyLightSensorMeasurement(time, lux);
313        updateAmbientLux(time);
314    }
315
316    private void applyLightSensorMeasurement(long time, float lux) {
317        mRecentLightSamples++;
318        mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon);
319        mAmbientLightRingBuffer.push(time, lux);
320
321        // Remember this sample value.
322        mLastObservedLux = lux;
323        mLastObservedLuxTime = time;
324    }
325
326    private boolean setScreenAutoBrightnessAdjustment(float adjustment) {
327        if (adjustment != mScreenAutoBrightnessAdjustment) {
328            mScreenAutoBrightnessAdjustment = adjustment;
329            return true;
330        }
331        return false;
332    }
333
334    private void setAmbientLux(float lux) {
335        mAmbientLux = lux;
336        mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
337        mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
338    }
339
340    private float calculateAmbientLux(long now) {
341        final int N = mAmbientLightRingBuffer.size();
342        if (N == 0) {
343            Slog.e(TAG, "calculateAmbientLux: No ambient light readings available");
344            return -1;
345        }
346        float sum = 0;
347        float totalWeight = 0;
348        long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
349        for (int i = N - 1; i >= 0; i--) {
350            long startTime = (mAmbientLightRingBuffer.getTime(i) - now);
351            float weight = calculateWeight(startTime, endTime);
352            float lux = mAmbientLightRingBuffer.getLux(i);
353            if (DEBUG) {
354                Slog.d(TAG, "calculateAmbientLux: [" +
355                        (startTime) + ", " +
356                        (endTime) + "]: lux=" + lux + ", weight=" + weight);
357            }
358            totalWeight += weight;
359            sum += mAmbientLightRingBuffer.getLux(i) * weight;
360            endTime = startTime;
361        }
362        if (DEBUG) {
363            Slog.d(TAG, "calculateAmbientLux: totalWeight=" + totalWeight +
364                    ", newAmbientLux=" + (sum / totalWeight));
365        }
366        return sum / totalWeight;
367    }
368
369    private float calculateWeight(long startDelta, long endDelta) {
370        return weightIntegral(endDelta) - weightIntegral(startDelta);
371    }
372
373    // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the
374    // horizon we're looking at and provides a non-linear weighting for light samples.
375    private float weightIntegral(long x) {
376        return x * (x * 0.5f + mWeightingIntercept);
377    }
378
379    private long nextAmbientLightBrighteningTransition(long time) {
380        final int N = mAmbientLightRingBuffer.size();
381        long earliestValidTime = time;
382        for (int i = N - 1; i >= 0; i--) {
383            if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) {
384                break;
385            }
386            earliestValidTime = mAmbientLightRingBuffer.getTime(i);
387        }
388        return earliestValidTime + mBrighteningLightDebounceConfig;
389    }
390
391    private long nextAmbientLightDarkeningTransition(long time) {
392        final int N = mAmbientLightRingBuffer.size();
393        long earliestValidTime = time;
394        for (int i = N - 1; i >= 0; i--) {
395            if (mAmbientLightRingBuffer.getLux(i) >= mDarkeningLuxThreshold) {
396                break;
397            }
398            earliestValidTime = mAmbientLightRingBuffer.getTime(i);
399        }
400        return earliestValidTime + mDarkeningLightDebounceConfig;
401    }
402
403    private void updateAmbientLux() {
404        long time = SystemClock.uptimeMillis();
405        mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon);
406        updateAmbientLux(time);
407    }
408
409    private void updateAmbientLux(long time) {
410        // If the light sensor was just turned on then immediately update our initial
411        // estimate of the current ambient light level.
412        if (!mAmbientLuxValid) {
413            final long timeWhenSensorWarmedUp =
414                mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
415            if (time < timeWhenSensorWarmedUp) {
416                if (DEBUG) {
417                    Slog.d(TAG, "updateAmbientLux: Sensor not  ready yet: "
418                            + "time=" + time
419                            + ", timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp);
420                }
421                mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX,
422                        timeWhenSensorWarmedUp);
423                return;
424            }
425            setAmbientLux(calculateAmbientLux(time));
426            mAmbientLuxValid = true;
427            if (DEBUG) {
428                Slog.d(TAG, "updateAmbientLux: Initializing: "
429                        + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer
430                        + ", mAmbientLux=" + mAmbientLux);
431            }
432            updateAutoBrightness(true);
433        }
434
435        long nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
436        long nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
437        float ambientLux = calculateAmbientLux(time);
438
439        if (ambientLux >= mBrighteningLuxThreshold && nextBrightenTransition <= time
440                || ambientLux <= mDarkeningLuxThreshold && nextDarkenTransition <= time) {
441            setAmbientLux(ambientLux);
442            if (DEBUG) {
443                Slog.d(TAG, "updateAmbientLux: "
444                        + ((ambientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
445                        + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
446                        + ", mAmbientLightRingBuffer=" + mAmbientLightRingBuffer
447                        + ", mAmbientLux=" + mAmbientLux);
448            }
449            updateAutoBrightness(true);
450            nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
451            nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
452        }
453        long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition);
454        // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't
455        // exceed the necessary threshold, then it's possible we'll get a transition time prior to
456        // now. Rather than continually checking to see whether the weighted lux exceeds the
457        // threshold, schedule an update for when we'd normally expect another light sample, which
458        // should be enough time to decide whether we should actually transition to the new
459        // weighted ambient lux or not.
460        nextTransitionTime =
461                nextTransitionTime > time ? nextTransitionTime : time + mLightSensorRate;
462        if (DEBUG) {
463            Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for "
464                    + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
465        }
466        mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime);
467    }
468
469    private void updateAutoBrightness(boolean sendUpdate) {
470        if (!mAmbientLuxValid) {
471            return;
472        }
473
474        float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux);
475        float gamma = 1.0f;
476
477        if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT
478                && mScreenAutoBrightnessAdjustment != 0.0f) {
479            final float adjGamma = MathUtils.pow(mScreenAutoBrightnessAdjustmentMaxGamma,
480                    Math.min(1.0f, Math.max(-1.0f, -mScreenAutoBrightnessAdjustment)));
481            gamma *= adjGamma;
482            if (DEBUG) {
483                Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma);
484            }
485        }
486
487        if (USE_TWILIGHT_ADJUSTMENT) {
488            TwilightState state = mTwilight.getCurrentState();
489            if (state != null && state.isNight()) {
490                final long now = System.currentTimeMillis();
491                final float earlyGamma =
492                        getTwilightGamma(now, state.getYesterdaySunset(), state.getTodaySunrise());
493                final float lateGamma =
494                        getTwilightGamma(now, state.getTodaySunset(), state.getTomorrowSunrise());
495                gamma *= earlyGamma * lateGamma;
496                if (DEBUG) {
497                    Slog.d(TAG, "updateAutoBrightness: earlyGamma=" + earlyGamma
498                            + ", lateGamma=" + lateGamma);
499                }
500            }
501        }
502
503        if (gamma != 1.0f) {
504            final float in = value;
505            value = MathUtils.pow(value, gamma);
506            if (DEBUG) {
507                Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma
508                        + ", in=" + in + ", out=" + value);
509            }
510        }
511
512        int newScreenAutoBrightness =
513                clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
514        if (mScreenAutoBrightness != newScreenAutoBrightness) {
515            if (DEBUG) {
516                Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness="
517                        + mScreenAutoBrightness + ", newScreenAutoBrightness="
518                        + newScreenAutoBrightness);
519            }
520
521            mScreenAutoBrightness = newScreenAutoBrightness;
522            mLastScreenAutoBrightnessGamma = gamma;
523            if (sendUpdate) {
524                mCallbacks.updateBrightness();
525            }
526        }
527    }
528
529    private int clampScreenBrightness(int value) {
530        return MathUtils.constrain(value,
531                mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
532    }
533
534    private void prepareBrightnessAdjustmentSample() {
535        if (!mBrightnessAdjustmentSamplePending) {
536            mBrightnessAdjustmentSamplePending = true;
537            mBrightnessAdjustmentSampleOldAdjustment = mScreenAutoBrightnessAdjustment;
538            mBrightnessAdjustmentSampleOldLux = mAmbientLuxValid ? mAmbientLux : -1;
539            mBrightnessAdjustmentSampleOldBrightness = mScreenAutoBrightness;
540            mBrightnessAdjustmentSampleOldGamma = mLastScreenAutoBrightnessGamma;
541        } else {
542            mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE);
543        }
544
545        mHandler.sendEmptyMessageDelayed(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE,
546                BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS);
547    }
548
549    private void cancelBrightnessAdjustmentSample() {
550        if (mBrightnessAdjustmentSamplePending) {
551            mBrightnessAdjustmentSamplePending = false;
552            mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE);
553        }
554    }
555
556    private void collectBrightnessAdjustmentSample() {
557        if (mBrightnessAdjustmentSamplePending) {
558            mBrightnessAdjustmentSamplePending = false;
559            if (mAmbientLuxValid && mScreenAutoBrightness >= 0) {
560                if (DEBUG) {
561                    Slog.d(TAG, "Auto-brightness adjustment changed by user: "
562                            + "adj=" + mScreenAutoBrightnessAdjustment
563                            + ", lux=" + mAmbientLux
564                            + ", brightness=" + mScreenAutoBrightness
565                            + ", gamma=" + mLastScreenAutoBrightnessGamma
566                            + ", ring=" + mAmbientLightRingBuffer);
567                }
568
569                EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ,
570                        mBrightnessAdjustmentSampleOldAdjustment,
571                        mBrightnessAdjustmentSampleOldLux,
572                        mBrightnessAdjustmentSampleOldBrightness,
573                        mBrightnessAdjustmentSampleOldGamma,
574                        mScreenAutoBrightnessAdjustment,
575                        mAmbientLux,
576                        mScreenAutoBrightness,
577                        mLastScreenAutoBrightnessGamma);
578            }
579        }
580    }
581
582    private static float getTwilightGamma(long now, long lastSunset, long nextSunrise) {
583        if (lastSunset < 0 || nextSunrise < 0
584                || now < lastSunset || now > nextSunrise) {
585            return 1.0f;
586        }
587
588        if (now < lastSunset + TWILIGHT_ADJUSTMENT_TIME) {
589            return MathUtils.lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
590                    (float)(now - lastSunset) / TWILIGHT_ADJUSTMENT_TIME);
591        }
592
593        if (now > nextSunrise - TWILIGHT_ADJUSTMENT_TIME) {
594            return MathUtils.lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
595                    (float)(nextSunrise - now) / TWILIGHT_ADJUSTMENT_TIME);
596        }
597
598        return TWILIGHT_ADJUSTMENT_MAX_GAMMA;
599    }
600
601    private final class AutomaticBrightnessHandler extends Handler {
602        public AutomaticBrightnessHandler(Looper looper) {
603            super(looper, null, true /*async*/);
604        }
605
606        @Override
607        public void handleMessage(Message msg) {
608            switch (msg.what) {
609                case MSG_UPDATE_AMBIENT_LUX:
610                    updateAmbientLux();
611                    break;
612
613                case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE:
614                    collectBrightnessAdjustmentSample();
615                    break;
616            }
617        }
618    }
619
620    private final SensorEventListener mLightSensorListener = new SensorEventListener() {
621        @Override
622        public void onSensorChanged(SensorEvent event) {
623            if (mLightSensorEnabled) {
624                final long time = SystemClock.uptimeMillis();
625                final float lux = event.values[0];
626                handleLightSensorEvent(time, lux);
627            }
628        }
629
630        @Override
631        public void onAccuracyChanged(Sensor sensor, int accuracy) {
632            // Not used.
633        }
634    };
635
636    private final TwilightListener mTwilightListener = new TwilightListener() {
637        @Override
638        public void onTwilightStateChanged() {
639            updateAutoBrightness(true /*sendUpdate*/);
640        }
641    };
642
643    /** Callbacks to request updates to the display's power state. */
644    interface Callbacks {
645        void updateBrightness();
646    }
647
648    private static final class AmbientLightRingBuffer{
649        // Proportional extra capacity of the buffer beyond the expected number of light samples
650        // in the horizon
651        private static final float BUFFER_SLACK = 1.5f;
652        private float[] mRingLux;
653        private long[] mRingTime;
654        private int mCapacity;
655
656        // The first valid element and the next open slot.
657        // Note that if mCount is zero then there are no valid elements.
658        private int mStart;
659        private int mEnd;
660        private int mCount;
661
662        public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
663            mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
664            mRingLux = new float[mCapacity];
665            mRingTime = new long[mCapacity];
666        }
667
668        public float getLux(int index) {
669            return mRingLux[offsetOf(index)];
670        }
671
672        public long getTime(int index) {
673            return mRingTime[offsetOf(index)];
674        }
675
676        public void push(long time, float lux) {
677            int next = mEnd;
678            if (mCount == mCapacity) {
679                int newSize = mCapacity * 2;
680
681                float[] newRingLux = new float[newSize];
682                long[] newRingTime = new long[newSize];
683                int length = mCapacity - mStart;
684                System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
685                System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
686                if (mStart != 0) {
687                    System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
688                    System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
689                }
690                mRingLux = newRingLux;
691                mRingTime = newRingTime;
692
693                next = mCapacity;
694                mCapacity = newSize;
695                mStart = 0;
696            }
697            mRingTime[next] = time;
698            mRingLux[next] = lux;
699            mEnd = next + 1;
700            if (mEnd == mCapacity) {
701                mEnd = 0;
702            }
703            mCount++;
704        }
705
706        public void prune(long horizon) {
707            if (mCount == 0) {
708                return;
709            }
710
711            while (mCount > 1) {
712                int next = mStart + 1;
713                if (next >= mCapacity) {
714                    next -= mCapacity;
715                }
716                if (mRingTime[next] > horizon) {
717                    // Some light sensors only produce data upon a change in the ambient light
718                    // levels, so we need to consider the previous measurement as the ambient light
719                    // level for all points in time up until we receive a new measurement. Thus, we
720                    // always want to keep the youngest element that would be removed from the
721                    // buffer and just set its measurement time to the horizon time since at that
722                    // point it is the ambient light level, and to remove it would be to drop a
723                    // valid data point within our horizon.
724                    break;
725                }
726                mStart = next;
727                mCount -= 1;
728            }
729
730            if (mRingTime[mStart] < horizon) {
731                mRingTime[mStart] = horizon;
732            }
733        }
734
735        public int size() {
736            return mCount;
737        }
738
739        public void clear() {
740            mStart = 0;
741            mEnd = 0;
742            mCount = 0;
743        }
744
745        @Override
746        public String toString() {
747            StringBuffer buf = new StringBuffer();
748            buf.append('[');
749            for (int i = 0; i < mCount; i++) {
750                final long next = i + 1 < mCount ? getTime(i + 1) : SystemClock.uptimeMillis();
751                if (i != 0) {
752                    buf.append(", ");
753                }
754                buf.append(getLux(i));
755                buf.append(" / ");
756                buf.append(next - getTime(i));
757                buf.append("ms");
758            }
759            buf.append(']');
760            return buf.toString();
761        }
762
763        private int offsetOf(int index) {
764            if (index >= mCount || index < 0) {
765                throw new ArrayIndexOutOfBoundsException(index);
766            }
767            index += mStart;
768            if (index >= mCapacity) {
769                index -= mCapacity;
770            }
771            return index;
772        }
773    }
774}
775