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