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