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    // Specifies the maximum magnitude of the time of day adjustment.
64    private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1f;
65
66    // Debounce for sampling user-initiated changes in display brightness to ensure
67    // the user is satisfied with the result before storing the sample.
68    private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000;
69
70    private static final int MSG_UPDATE_AMBIENT_LUX = 1;
71    private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
72
73    // Callbacks for requesting updates to the the display's power state
74    private final Callbacks mCallbacks;
75
76    // The sensor manager.
77    private final SensorManager mSensorManager;
78
79    // The light sensor, or null if not available or needed.
80    private final Sensor mLightSensor;
81
82    // The twilight service.
83    private final TwilightManager mTwilight;
84
85    // The auto-brightness spline adjustment.
86    // The brightness values have been scaled to a range of 0..1.
87    private final Spline mScreenAutoBrightnessSpline;
88
89    // The minimum and maximum screen brightnesses.
90    private final int mScreenBrightnessRangeMinimum;
91    private final int mScreenBrightnessRangeMaximum;
92    private final float mDozeScaleFactor;
93
94    // Light sensor event rate in milliseconds.
95    private final int mLightSensorRate;
96
97    // Stability requirements in milliseconds for accepting a new brightness level.  This is used
98    // for debouncing the light sensor.  Different constants are used to debounce the light sensor
99    // when adapting to brighter or darker environments.  This parameter controls how quickly
100    // brightness changes occur in response to an observed change in light level that exceeds the
101    // hysteresis threshold.
102    private final long mBrighteningLightDebounceConfig;
103    private final long mDarkeningLightDebounceConfig;
104
105    // If true immediately after the screen is turned on the controller will try to adjust the
106    // brightness based on the current sensor reads. If false, the controller will collect more data
107    // and only then decide whether to change brightness.
108    private final boolean mResetAmbientLuxAfterWarmUpConfig;
109
110    // Period of time in which to consider light samples in milliseconds.
111    private final int mAmbientLightHorizon;
112
113    // The intercept used for the weighting calculation. This is used in order to keep all possible
114    // weighting values positive.
115    private final int mWeightingIntercept;
116
117    // Amount of time to delay auto-brightness after screen on while waiting for
118    // the light sensor to warm-up in milliseconds.
119    // May be 0 if no warm-up is required.
120    private int mLightSensorWarmUpTimeConfig;
121
122    // Set to true if the light sensor is enabled.
123    private boolean mLightSensorEnabled;
124
125    // The time when the light sensor was enabled.
126    private long mLightSensorEnableTime;
127
128    // The currently accepted nominal ambient light level.
129    private float mAmbientLux;
130
131    // True if mAmbientLux holds a valid value.
132    private boolean mAmbientLuxValid;
133
134    // The ambient light level threshold at which to brighten or darken the screen.
135    private float mBrighteningLuxThreshold;
136    private float mDarkeningLuxThreshold;
137
138    // The most recent light sample.
139    private float mLastObservedLux;
140
141    // The time of the most light recent sample.
142    private long mLastObservedLuxTime;
143
144    // The number of light samples collected since the light sensor was enabled.
145    private int mRecentLightSamples;
146
147    // A ring buffer containing all of the recent ambient light sensor readings.
148    private AmbientLightRingBuffer mAmbientLightRingBuffer;
149
150    // A ring buffer containing the light sensor readings for the initial horizon period.
151    private AmbientLightRingBuffer mInitialHorizonAmbientLightRingBuffer;
152
153    // The handler
154    private AutomaticBrightnessHandler mHandler;
155
156    // The screen brightness level that has been chosen by the auto-brightness
157    // algorithm.  The actual brightness should ramp towards this value.
158    // We preserve this value even when we stop using the light sensor so
159    // that we can quickly revert to the previous auto-brightness level
160    // while the light sensor warms up.
161    // Use -1 if there is no current auto-brightness value available.
162    private int mScreenAutoBrightness = -1;
163
164    // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter)
165    private float mScreenAutoBrightnessAdjustment = 0.0f;
166
167    // The maximum range of gamma adjustment possible using the screen
168    // auto-brightness adjustment setting.
169    private float mScreenAutoBrightnessAdjustmentMaxGamma;
170
171    // The last screen auto-brightness gamma.  (For printing in dump() only.)
172    private float mLastScreenAutoBrightnessGamma = 1.0f;
173
174    // Are we going to adjust brightness while dozing.
175    private boolean mDozing;
176
177    // True if we are collecting a brightness adjustment sample, along with some data
178    // for the initial state of the sample.
179    private boolean mBrightnessAdjustmentSamplePending;
180    private float mBrightnessAdjustmentSampleOldAdjustment;
181    private float mBrightnessAdjustmentSampleOldLux;
182    private int mBrightnessAdjustmentSampleOldBrightness;
183    private float mBrightnessAdjustmentSampleOldGamma;
184
185    private boolean mUseTwilight;
186
187    public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
188            SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime,
189            int brightnessMin, int brightnessMax, float dozeScaleFactor,
190            int lightSensorRate, long brighteningLightDebounceConfig,
191            long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
192            int ambientLightHorizon, float autoBrightnessAdjustmentMaxGamma ) {
193        mCallbacks = callbacks;
194        mTwilight = LocalServices.getService(TwilightManager.class);
195        mSensorManager = sensorManager;
196        mScreenAutoBrightnessSpline = autoBrightnessSpline;
197        mScreenBrightnessRangeMinimum = brightnessMin;
198        mScreenBrightnessRangeMaximum = brightnessMax;
199        mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
200        mDozeScaleFactor = dozeScaleFactor;
201        mLightSensorRate = lightSensorRate;
202        mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
203        mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
204        mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
205        mAmbientLightHorizon = ambientLightHorizon;
206        mWeightingIntercept = ambientLightHorizon;
207        mScreenAutoBrightnessAdjustmentMaxGamma = autoBrightnessAdjustmentMaxGamma;
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.getCurrentState()=" + mTwilight.getCurrentState());
272        pw.println("  mLightSensorEnabled=" + mLightSensorEnabled);
273        pw.println("  mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
274        pw.println("  mAmbientLux=" + mAmbientLux);
275        pw.println("  mAmbientLightHorizon=" + mAmbientLightHorizon);
276        pw.println("  mBrighteningLuxThreshold=" + mBrighteningLuxThreshold);
277        pw.println("  mDarkeningLuxThreshold=" + mDarkeningLuxThreshold);
278        pw.println("  mLastObservedLux=" + mLastObservedLux);
279        pw.println("  mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
280        pw.println("  mRecentLightSamples=" + mRecentLightSamples);
281        pw.println("  mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
282        pw.println("  mInitialHorizonAmbientLightRingBuffer=" +
283                mInitialHorizonAmbientLightRingBuffer);
284        pw.println("  mScreenAutoBrightness=" + mScreenAutoBrightness);
285        pw.println("  mScreenAutoBrightnessAdjustment=" + mScreenAutoBrightnessAdjustment);
286        pw.println("  mScreenAutoBrightnessAdjustmentMaxGamma=" + mScreenAutoBrightnessAdjustmentMaxGamma);
287        pw.println("  mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma);
288        pw.println("  mDozing=" + mDozing);
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 = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
347        mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
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.getCurrentState();
499            if (state != null && state.isNight()) {
500                final long now = System.currentTimeMillis();
501                gamma *= 1 + state.getAmount() * TWILIGHT_ADJUSTMENT_MAX_GAMMA;
502                if (DEBUG) {
503                    Slog.d(TAG, "updateAutoBrightness: twilight amount=" + state.getAmount());
504                }
505            }
506        }
507
508        if (gamma != 1.0f) {
509            final float in = value;
510            value = MathUtils.pow(value, gamma);
511            if (DEBUG) {
512                Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma
513                        + ", in=" + in + ", out=" + value);
514            }
515        }
516
517        int newScreenAutoBrightness =
518                clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
519        if (mScreenAutoBrightness != newScreenAutoBrightness) {
520            if (DEBUG) {
521                Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness="
522                        + mScreenAutoBrightness + ", newScreenAutoBrightness="
523                        + newScreenAutoBrightness);
524            }
525
526            mScreenAutoBrightness = newScreenAutoBrightness;
527            mLastScreenAutoBrightnessGamma = gamma;
528            if (sendUpdate) {
529                mCallbacks.updateBrightness();
530            }
531        }
532    }
533
534    private int clampScreenBrightness(int value) {
535        return MathUtils.constrain(value,
536                mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
537    }
538
539    private void prepareBrightnessAdjustmentSample() {
540        if (!mBrightnessAdjustmentSamplePending) {
541            mBrightnessAdjustmentSamplePending = true;
542            mBrightnessAdjustmentSampleOldAdjustment = mScreenAutoBrightnessAdjustment;
543            mBrightnessAdjustmentSampleOldLux = mAmbientLuxValid ? mAmbientLux : -1;
544            mBrightnessAdjustmentSampleOldBrightness = mScreenAutoBrightness;
545            mBrightnessAdjustmentSampleOldGamma = mLastScreenAutoBrightnessGamma;
546        } else {
547            mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE);
548        }
549
550        mHandler.sendEmptyMessageDelayed(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE,
551                BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS);
552    }
553
554    private void cancelBrightnessAdjustmentSample() {
555        if (mBrightnessAdjustmentSamplePending) {
556            mBrightnessAdjustmentSamplePending = false;
557            mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE);
558        }
559    }
560
561    private void collectBrightnessAdjustmentSample() {
562        if (mBrightnessAdjustmentSamplePending) {
563            mBrightnessAdjustmentSamplePending = false;
564            if (mAmbientLuxValid && mScreenAutoBrightness >= 0) {
565                if (DEBUG) {
566                    Slog.d(TAG, "Auto-brightness adjustment changed by user: "
567                            + "adj=" + mScreenAutoBrightnessAdjustment
568                            + ", lux=" + mAmbientLux
569                            + ", brightness=" + mScreenAutoBrightness
570                            + ", gamma=" + mLastScreenAutoBrightnessGamma
571                            + ", ring=" + mAmbientLightRingBuffer);
572                }
573
574                EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ,
575                        mBrightnessAdjustmentSampleOldAdjustment,
576                        mBrightnessAdjustmentSampleOldLux,
577                        mBrightnessAdjustmentSampleOldBrightness,
578                        mBrightnessAdjustmentSampleOldGamma,
579                        mScreenAutoBrightnessAdjustment,
580                        mAmbientLux,
581                        mScreenAutoBrightness,
582                        mLastScreenAutoBrightnessGamma);
583            }
584        }
585    }
586
587    private final class AutomaticBrightnessHandler extends Handler {
588        public AutomaticBrightnessHandler(Looper looper) {
589            super(looper, null, true /*async*/);
590        }
591
592        @Override
593        public void handleMessage(Message msg) {
594            switch (msg.what) {
595                case MSG_UPDATE_AMBIENT_LUX:
596                    updateAmbientLux();
597                    break;
598
599                case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE:
600                    collectBrightnessAdjustmentSample();
601                    break;
602            }
603        }
604    }
605
606    private final SensorEventListener mLightSensorListener = new SensorEventListener() {
607        @Override
608        public void onSensorChanged(SensorEvent event) {
609            if (mLightSensorEnabled) {
610                final long time = SystemClock.uptimeMillis();
611                final float lux = event.values[0];
612                handleLightSensorEvent(time, lux);
613            }
614        }
615
616        @Override
617        public void onAccuracyChanged(Sensor sensor, int accuracy) {
618            // Not used.
619        }
620    };
621
622    private final TwilightListener mTwilightListener = new TwilightListener() {
623        @Override
624        public void onTwilightStateChanged() {
625            updateAutoBrightness(true /*sendUpdate*/);
626        }
627    };
628
629    /** Callbacks to request updates to the display's power state. */
630    interface Callbacks {
631        void updateBrightness();
632    }
633
634    private static final class AmbientLightRingBuffer {
635        // Proportional extra capacity of the buffer beyond the expected number of light samples
636        // in the horizon
637        private static final float BUFFER_SLACK = 1.5f;
638        private float[] mRingLux;
639        private long[] mRingTime;
640        private int mCapacity;
641
642        // The first valid element and the next open slot.
643        // Note that if mCount is zero then there are no valid elements.
644        private int mStart;
645        private int mEnd;
646        private int mCount;
647
648        public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
649            mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
650            mRingLux = new float[mCapacity];
651            mRingTime = new long[mCapacity];
652        }
653
654        public float getLux(int index) {
655            return mRingLux[offsetOf(index)];
656        }
657
658        public long getTime(int index) {
659            return mRingTime[offsetOf(index)];
660        }
661
662        public void push(long time, float lux) {
663            int next = mEnd;
664            if (mCount == mCapacity) {
665                int newSize = mCapacity * 2;
666
667                float[] newRingLux = new float[newSize];
668                long[] newRingTime = new long[newSize];
669                int length = mCapacity - mStart;
670                System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
671                System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
672                if (mStart != 0) {
673                    System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
674                    System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
675                }
676                mRingLux = newRingLux;
677                mRingTime = newRingTime;
678
679                next = mCapacity;
680                mCapacity = newSize;
681                mStart = 0;
682            }
683            mRingTime[next] = time;
684            mRingLux[next] = lux;
685            mEnd = next + 1;
686            if (mEnd == mCapacity) {
687                mEnd = 0;
688            }
689            mCount++;
690        }
691
692        public void prune(long horizon) {
693            if (mCount == 0) {
694                return;
695            }
696
697            while (mCount > 1) {
698                int next = mStart + 1;
699                if (next >= mCapacity) {
700                    next -= mCapacity;
701                }
702                if (mRingTime[next] > horizon) {
703                    // Some light sensors only produce data upon a change in the ambient light
704                    // levels, so we need to consider the previous measurement as the ambient light
705                    // level for all points in time up until we receive a new measurement. Thus, we
706                    // always want to keep the youngest element that would be removed from the
707                    // buffer and just set its measurement time to the horizon time since at that
708                    // point it is the ambient light level, and to remove it would be to drop a
709                    // valid data point within our horizon.
710                    break;
711                }
712                mStart = next;
713                mCount -= 1;
714            }
715
716            if (mRingTime[mStart] < horizon) {
717                mRingTime[mStart] = horizon;
718            }
719        }
720
721        public int size() {
722            return mCount;
723        }
724
725        public void clear() {
726            mStart = 0;
727            mEnd = 0;
728            mCount = 0;
729        }
730
731        @Override
732        public String toString() {
733            StringBuffer buf = new StringBuffer();
734            buf.append('[');
735            for (int i = 0; i < mCount; i++) {
736                final long next = i + 1 < mCount ? getTime(i + 1) : SystemClock.uptimeMillis();
737                if (i != 0) {
738                    buf.append(", ");
739                }
740                buf.append(getLux(i));
741                buf.append(" / ");
742                buf.append(next - getTime(i));
743                buf.append("ms");
744            }
745            buf.append(']');
746            return buf.toString();
747        }
748
749        private int offsetOf(int index) {
750            if (index >= mCount || index < 0) {
751                throw new ArrayIndexOutOfBoundsException(index);
752            }
753            index += mStart;
754            if (index >= mCapacity) {
755                index -= mCapacity;
756            }
757            return index;
758        }
759    }
760}
761