AutomaticBrightnessController.java revision c135225466eb1b209e12c8c2df135641a21c5d66
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.hardware.display.BrightnessConfiguration;
28import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
29import android.os.Handler;
30import android.os.Looper;
31import android.os.Message;
32import android.os.PowerManager;
33import android.os.SystemClock;
34import android.os.Trace;
35import android.text.format.DateUtils;
36import android.util.EventLog;
37import android.util.MathUtils;
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    // How long the current sensor reading is assumed to be valid beyond the current time.
53    // This provides a bit of prediction, as well as ensures that the weight for the last sample is
54    // non-zero, which in turn ensures that the total weight is non-zero.
55    private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100;
56
57    // Debounce for sampling user-initiated changes in display brightness to ensure
58    // the user is satisfied with the result before storing the sample.
59    private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000;
60
61    // Timeout after which we remove the effects any user interactions might've had on the
62    // brightness mapping. This timeout doesn't start until we transition to a non-interactive
63    // display policy so that we don't reset while users are using their devices, but also so that
64    // we don't erroneously keep the short-term model if the device is dozing but the display is
65    // fully on.
66    private static final int SHORT_TERM_MODEL_TIMEOUT_MILLIS = 30000;
67
68    private static final int MSG_UPDATE_AMBIENT_LUX = 1;
69    private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
70    private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3;
71
72    // Length of the ambient light horizon used to calculate the long term estimate of ambient
73    // light.
74    private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000;
75
76    // Length of the ambient light horizon used to calculate short-term estimate of ambient light.
77    private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000;
78
79    // Callbacks for requesting updates to the display's power state
80    private final Callbacks mCallbacks;
81
82    // The sensor manager.
83    private final SensorManager mSensorManager;
84
85    // The light sensor, or null if not available or needed.
86    private final Sensor mLightSensor;
87
88    // The mapper to translate ambient lux to screen brightness in the range [0, 1.0].
89    private final BrightnessMappingStrategy mBrightnessMapper;
90
91    // The minimum and maximum screen brightnesses.
92    private final int mScreenBrightnessRangeMinimum;
93    private final int mScreenBrightnessRangeMaximum;
94    private final float mDozeScaleFactor;
95
96    // Initial light sensor event rate in milliseconds.
97    private final int mInitialLightSensorRate;
98
99    // Steady-state light sensor event rate in milliseconds.
100    private final int mNormalLightSensorRate;
101
102    // The current light sensor event rate in milliseconds.
103    private int mCurrentLightSensorRate;
104
105    // Stability requirements in milliseconds for accepting a new brightness level.  This is used
106    // for debouncing the light sensor.  Different constants are used to debounce the light sensor
107    // when adapting to brighter or darker environments.  This parameter controls how quickly
108    // brightness changes occur in response to an observed change in light level that exceeds the
109    // hysteresis threshold.
110    private final long mBrighteningLightDebounceConfig;
111    private final long mDarkeningLightDebounceConfig;
112
113    // If true immediately after the screen is turned on the controller will try to adjust the
114    // brightness based on the current sensor reads. If false, the controller will collect more data
115    // and only then decide whether to change brightness.
116    private final boolean mResetAmbientLuxAfterWarmUpConfig;
117
118    // Period of time in which to consider light samples in milliseconds.
119    private final int mAmbientLightHorizon;
120
121    // The intercept used for the weighting calculation. This is used in order to keep all possible
122    // weighting values positive.
123    private final int mWeightingIntercept;
124
125    // accessor object for determining thresholds to change brightness dynamically
126    private final HysteresisLevels mDynamicHysteresis;
127
128    // Amount of time to delay auto-brightness after screen on while waiting for
129    // the light sensor to warm-up in milliseconds.
130    // May be 0 if no warm-up is required.
131    private int mLightSensorWarmUpTimeConfig;
132
133    // Set to true if the light sensor is enabled.
134    private boolean mLightSensorEnabled;
135
136    // The time when the light sensor was enabled.
137    private long mLightSensorEnableTime;
138
139    // The currently accepted nominal ambient light level.
140    private float mAmbientLux;
141
142    // True if mAmbientLux holds a valid value.
143    private boolean mAmbientLuxValid;
144
145    // The ambient light level threshold at which to brighten or darken the screen.
146    private float mBrighteningLuxThreshold;
147    private float mDarkeningLuxThreshold;
148
149    // The most recent light sample.
150    private float mLastObservedLux;
151
152    // The time of the most light recent sample.
153    private long mLastObservedLuxTime;
154
155    // The number of light samples collected since the light sensor was enabled.
156    private int mRecentLightSamples;
157
158    // A ring buffer containing all of the recent ambient light sensor readings.
159    private AmbientLightRingBuffer mAmbientLightRingBuffer;
160
161    // The handler
162    private AutomaticBrightnessHandler mHandler;
163
164    // The screen brightness level that has been chosen by the auto-brightness
165    // algorithm.  The actual brightness should ramp towards this value.
166    // We preserve this value even when we stop using the light sensor so
167    // that we can quickly revert to the previous auto-brightness level
168    // while the light sensor warms up.
169    // Use -1 if there is no current auto-brightness value available.
170    private int mScreenAutoBrightness = -1;
171
172    // The current display policy. This is useful, for example,  for knowing when we're dozing,
173    // where the light sensor may not be available.
174    private int mDisplayPolicy = DisplayPowerRequest.POLICY_OFF;
175
176    // True if we are collecting a brightness adjustment sample, along with some data
177    // for the initial state of the sample.
178    private boolean mBrightnessAdjustmentSamplePending;
179    private float mBrightnessAdjustmentSampleOldLux;
180    private int mBrightnessAdjustmentSampleOldBrightness;
181
182    // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
183    // user's adjustment) immediately, but wait for a drastic enough change in the ambient light.
184    // The anchor determines what were the light levels when the user has set her preference, and
185    // we use a relative threshold to determine when to revert to the OEM curve.
186    private boolean mShortTermModelValid;
187    private float mShortTermModelAnchor;
188    private float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f;
189
190    public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
191            SensorManager sensorManager, BrightnessMappingStrategy mapper,
192            int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
193            int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
194            long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
195            int ambientLightHorizon, HysteresisLevels dynamicHysteresis) {
196        mCallbacks = callbacks;
197        mSensorManager = sensorManager;
198        mBrightnessMapper = mapper;
199        mScreenBrightnessRangeMinimum = brightnessMin;
200        mScreenBrightnessRangeMaximum = brightnessMax;
201        mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
202        mDozeScaleFactor = dozeScaleFactor;
203        mNormalLightSensorRate = lightSensorRate;
204        mInitialLightSensorRate = initialLightSensorRate;
205        mCurrentLightSensorRate = -1;
206        mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
207        mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
208        mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
209        mAmbientLightHorizon = ambientLightHorizon;
210        mWeightingIntercept = ambientLightHorizon;
211        mDynamicHysteresis = dynamicHysteresis;
212        mShortTermModelValid = true;
213        mShortTermModelAnchor = -1;
214
215        mHandler = new AutomaticBrightnessHandler(looper);
216        mAmbientLightRingBuffer =
217            new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon);
218
219        if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
220            mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
221        }
222    }
223
224    public int getAutomaticScreenBrightness() {
225        if (!mAmbientLuxValid) {
226            return -1;
227        }
228        if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
229            return (int) (mScreenAutoBrightness * mDozeScaleFactor);
230        }
231        return mScreenAutoBrightness;
232    }
233
234    public float getAutomaticScreenBrightnessAdjustment() {
235        return mBrightnessMapper.getAutoBrightnessAdjustment();
236    }
237
238    public void configure(boolean enable, @Nullable BrightnessConfiguration configuration,
239            float brightness, boolean userChangedBrightness, float adjustment,
240            boolean userChangedAutoBrightnessAdjustment, int displayPolicy) {
241        // While dozing, the application processor may be suspended which will prevent us from
242        // receiving new information from the light sensor. On some devices, we may be able to
243        // switch to a wake-up light sensor instead but for now we will simply disable the sensor
244        // and hold onto the last computed screen auto brightness.  We save the dozing flag for
245        // debugging purposes.
246        boolean dozing = (displayPolicy == DisplayPowerRequest.POLICY_DOZE);
247        boolean changed = setBrightnessConfiguration(configuration);
248        changed |= setDisplayPolicy(displayPolicy);
249        if (userChangedAutoBrightnessAdjustment) {
250            changed |= setAutoBrightnessAdjustment(adjustment);
251        }
252        if (userChangedBrightness && enable) {
253            // Update the brightness curve with the new user control point. It's critical this
254            // happens after we update the autobrightness adjustment since it may reset it.
255            changed |= setScreenBrightnessByUser(brightness);
256        }
257        final boolean userInitiatedChange =
258                userChangedBrightness || userChangedAutoBrightnessAdjustment;
259        if (userInitiatedChange && enable && !dozing) {
260            prepareBrightnessAdjustmentSample();
261        }
262        changed |= setLightSensorEnabled(enable && !dozing);
263        if (changed) {
264            updateAutoBrightness(false /*sendUpdate*/);
265        }
266    }
267
268    public boolean hasUserDataPoints() {
269        return mBrightnessMapper.hasUserDataPoints();
270    }
271
272    public boolean isDefaultConfig() {
273        return mBrightnessMapper.isDefaultConfig();
274    }
275
276    public BrightnessConfiguration getDefaultConfig() {
277        return mBrightnessMapper.getDefaultConfig();
278    }
279
280    private boolean setDisplayPolicy(int policy) {
281        if (mDisplayPolicy == policy) {
282            return false;
283        }
284        final int oldPolicy = mDisplayPolicy;
285        mDisplayPolicy = policy;
286        if (DEBUG) {
287            Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy);
288        }
289        if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) {
290            mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_SHORT_TERM_MODEL,
291                    SHORT_TERM_MODEL_TIMEOUT_MILLIS);
292        } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) {
293            mHandler.removeMessages(MSG_INVALIDATE_SHORT_TERM_MODEL);
294        }
295        return true;
296    }
297
298    private static boolean isInteractivePolicy(int policy) {
299        return policy == DisplayPowerRequest.POLICY_BRIGHT
300                || policy == DisplayPowerRequest.POLICY_DIM
301                || policy == DisplayPowerRequest.POLICY_VR;
302    }
303
304    private boolean setScreenBrightnessByUser(float brightness) {
305        if (!mAmbientLuxValid) {
306            // If we don't have a valid ambient lux then we don't have a valid brightness anyways,
307            // and we can't use this data to add a new control point to the short-term model.
308            return false;
309        }
310        mBrightnessMapper.addUserDataPoint(mAmbientLux, brightness);
311        mShortTermModelValid = true;
312        mShortTermModelAnchor = mAmbientLux;
313        if (DEBUG) {
314            Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor);
315        }
316        return true;
317    }
318
319    public void resetShortTermModel() {
320        mBrightnessMapper.clearUserDataPoints();
321        mShortTermModelValid = true;
322        mShortTermModelAnchor = -1;
323    }
324
325    private void invalidateShortTermModel() {
326        if (DEBUG) {
327            Slog.d(TAG, "ShortTermModel: invalidate user data");
328        }
329        mShortTermModelValid = false;
330    }
331
332    public boolean setBrightnessConfiguration(BrightnessConfiguration configuration) {
333        return mBrightnessMapper.setBrightnessConfiguration(configuration);
334    }
335
336    public void dump(PrintWriter pw) {
337        pw.println();
338        pw.println("Automatic Brightness Controller Configuration:");
339        pw.println("  mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
340        pw.println("  mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
341        pw.println("  mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
342        pw.println("  mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
343        pw.println("  mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
344        pw.println("  mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
345
346        pw.println();
347        pw.println("Automatic Brightness Controller State:");
348        pw.println("  mLightSensor=" + mLightSensor);
349        pw.println("  mLightSensorEnabled=" + mLightSensorEnabled);
350        pw.println("  mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
351        pw.println("  mAmbientLux=" + mAmbientLux);
352        pw.println("  mAmbientLuxValid=" + mAmbientLuxValid);
353        pw.println("  mAmbientLightHorizon=" + mAmbientLightHorizon);
354        pw.println("  mBrighteningLuxThreshold=" + mBrighteningLuxThreshold);
355        pw.println("  mDarkeningLuxThreshold=" + mDarkeningLuxThreshold);
356        pw.println("  mLastObservedLux=" + mLastObservedLux);
357        pw.println("  mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
358        pw.println("  mRecentLightSamples=" + mRecentLightSamples);
359        pw.println("  mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
360        pw.println("  mScreenAutoBrightness=" + mScreenAutoBrightness);
361        pw.println("  mDisplayPolicy=" + mDisplayPolicy);
362        pw.println("  mShortTermModelAnchor=" + mShortTermModelAnchor);
363
364        pw.println();
365        mBrightnessMapper.dump(pw);
366    }
367
368    private boolean setLightSensorEnabled(boolean enable) {
369        if (enable) {
370            if (!mLightSensorEnabled) {
371                mLightSensorEnabled = true;
372                mLightSensorEnableTime = SystemClock.uptimeMillis();
373                mCurrentLightSensorRate = mInitialLightSensorRate;
374                mSensorManager.registerListener(mLightSensorListener, mLightSensor,
375                        mCurrentLightSensorRate * 1000, mHandler);
376                return true;
377            }
378        } else if (mLightSensorEnabled) {
379            mLightSensorEnabled = false;
380            mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig;
381            mRecentLightSamples = 0;
382            mAmbientLightRingBuffer.clear();
383            mCurrentLightSensorRate = -1;
384            mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
385            mSensorManager.unregisterListener(mLightSensorListener);
386        }
387        return false;
388    }
389
390    private void handleLightSensorEvent(long time, float lux) {
391        Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux);
392        mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
393
394        if (mAmbientLightRingBuffer.size() == 0) {
395            // switch to using the steady-state sample rate after grabbing the initial light sample
396            adjustLightSensorRate(mNormalLightSensorRate);
397        }
398        applyLightSensorMeasurement(time, lux);
399        updateAmbientLux(time);
400    }
401
402    private void applyLightSensorMeasurement(long time, float lux) {
403        mRecentLightSamples++;
404        mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon);
405        mAmbientLightRingBuffer.push(time, lux);
406
407        // Remember this sample value.
408        mLastObservedLux = lux;
409        mLastObservedLuxTime = time;
410    }
411
412    private void adjustLightSensorRate(int lightSensorRate) {
413        // if the light sensor rate changed, update the sensor listener
414        if (lightSensorRate != mCurrentLightSensorRate) {
415            if (DEBUG) {
416                Slog.d(TAG, "adjustLightSensorRate: " +
417                        "previousRate=" + mCurrentLightSensorRate + ", " +
418                        "currentRate=" + lightSensorRate);
419            }
420            mCurrentLightSensorRate = lightSensorRate;
421            mSensorManager.unregisterListener(mLightSensorListener);
422            mSensorManager.registerListener(mLightSensorListener, mLightSensor,
423                    lightSensorRate * 1000, mHandler);
424        }
425    }
426
427    private boolean setAutoBrightnessAdjustment(float adjustment) {
428        return mBrightnessMapper.setAutoBrightnessAdjustment(adjustment);
429    }
430
431    private void setAmbientLux(float lux) {
432        if (DEBUG) {
433            Slog.d(TAG, "setAmbientLux(" + lux + ")");
434        }
435        if (lux < 0) {
436            Slog.w(TAG, "Ambient lux was negative, ignoring and setting to 0");
437            lux = 0;
438        }
439        mAmbientLux = lux;
440        mBrighteningLuxThreshold = mDynamicHysteresis.getBrighteningThreshold(lux);
441        mDarkeningLuxThreshold = mDynamicHysteresis.getDarkeningThreshold(lux);
442
443        // If the short term model was invalidated and the change is drastic enough, reset it.
444        if (!mShortTermModelValid && mShortTermModelAnchor != -1) {
445            final float minAmbientLux =
446                mShortTermModelAnchor - mShortTermModelAnchor * SHORT_TERM_MODEL_THRESHOLD_RATIO;
447            final float maxAmbientLux =
448                mShortTermModelAnchor + mShortTermModelAnchor * SHORT_TERM_MODEL_THRESHOLD_RATIO;
449            if (minAmbientLux < mAmbientLux && mAmbientLux < maxAmbientLux) {
450                if (DEBUG) {
451                    Slog.d(TAG, "ShortTermModel: re-validate user data, ambient lux is " +
452                            minAmbientLux + " < " + mAmbientLux + " < " + maxAmbientLux);
453                }
454                mShortTermModelValid = true;
455            } else {
456                Slog.d(TAG, "ShortTermModel: reset data, ambient lux is " + mAmbientLux +
457                        "(" + minAmbientLux + ", " + maxAmbientLux + ")");
458                resetShortTermModel();
459            }
460        }
461    }
462
463    private float calculateAmbientLux(long now, long horizon) {
464        if (DEBUG) {
465            Slog.d(TAG, "calculateAmbientLux(" + now + ", " + horizon + ")");
466        }
467        final int N = mAmbientLightRingBuffer.size();
468        if (N == 0) {
469            Slog.e(TAG, "calculateAmbientLux: No ambient light readings available");
470            return -1;
471        }
472
473        // Find the first measurement that is just outside of the horizon.
474        int endIndex = 0;
475        final long horizonStartTime = now - horizon;
476        for (int i = 0; i < N-1; i++) {
477            if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) {
478                endIndex++;
479            } else {
480                break;
481            }
482        }
483        if (DEBUG) {
484            Slog.d(TAG, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" +
485                    mAmbientLightRingBuffer.getTime(endIndex) + ", " +
486                    mAmbientLightRingBuffer.getLux(endIndex) + ")");
487        }
488        float sum = 0;
489        float totalWeight = 0;
490        long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
491        for (int i = N - 1; i >= endIndex; i--) {
492            long eventTime = mAmbientLightRingBuffer.getTime(i);
493            if (i == endIndex && eventTime < horizonStartTime) {
494                // If we're at the final value, make sure we only consider the part of the sample
495                // within our desired horizon.
496                eventTime = horizonStartTime;
497            }
498            final long startTime = eventTime - now;
499            float weight = calculateWeight(startTime, endTime);
500            float lux = mAmbientLightRingBuffer.getLux(i);
501            if (DEBUG) {
502                Slog.d(TAG, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " +
503                        "lux=" + lux + ", " +
504                        "weight=" + weight);
505            }
506            totalWeight += weight;
507            sum += lux * weight;
508            endTime = startTime;
509        }
510        if (DEBUG) {
511            Slog.d(TAG, "calculateAmbientLux: " +
512                    "totalWeight=" + totalWeight + ", " +
513                    "newAmbientLux=" + (sum / totalWeight));
514        }
515        return sum / totalWeight;
516    }
517
518    private float calculateWeight(long startDelta, long endDelta) {
519        return weightIntegral(endDelta) - weightIntegral(startDelta);
520    }
521
522    // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the
523    // horizon we're looking at and provides a non-linear weighting for light samples.
524    private float weightIntegral(long x) {
525        return x * (x * 0.5f + mWeightingIntercept);
526    }
527
528    private long nextAmbientLightBrighteningTransition(long time) {
529        final int N = mAmbientLightRingBuffer.size();
530        long earliestValidTime = time;
531        for (int i = N - 1; i >= 0; i--) {
532            if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) {
533                break;
534            }
535            earliestValidTime = mAmbientLightRingBuffer.getTime(i);
536        }
537        return earliestValidTime + mBrighteningLightDebounceConfig;
538    }
539
540    private long nextAmbientLightDarkeningTransition(long time) {
541        final int N = mAmbientLightRingBuffer.size();
542        long earliestValidTime = time;
543        for (int i = N - 1; i >= 0; i--) {
544            if (mAmbientLightRingBuffer.getLux(i) >= mDarkeningLuxThreshold) {
545                break;
546            }
547            earliestValidTime = mAmbientLightRingBuffer.getTime(i);
548        }
549        return earliestValidTime + mDarkeningLightDebounceConfig;
550    }
551
552    private void updateAmbientLux() {
553        long time = SystemClock.uptimeMillis();
554        mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon);
555        updateAmbientLux(time);
556    }
557
558    private void updateAmbientLux(long time) {
559        // If the light sensor was just turned on then immediately update our initial
560        // estimate of the current ambient light level.
561        if (!mAmbientLuxValid) {
562            final long timeWhenSensorWarmedUp =
563                mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
564            if (time < timeWhenSensorWarmedUp) {
565                if (DEBUG) {
566                    Slog.d(TAG, "updateAmbientLux: Sensor not  ready yet: " +
567                            "time=" + time + ", " +
568                            "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp);
569                }
570                mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX,
571                        timeWhenSensorWarmedUp);
572                return;
573            }
574            setAmbientLux(calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS));
575            mAmbientLuxValid = true;
576            if (DEBUG) {
577                Slog.d(TAG, "updateAmbientLux: Initializing: " +
578                        "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " +
579                        "mAmbientLux=" + mAmbientLux);
580            }
581            updateAutoBrightness(true);
582        }
583
584        long nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
585        long nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
586        // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term
587        // change in lighting conditions, and a fast ambient lux to determine what the new
588        // brightness situation is since the slow lux can be quite slow to converge.
589        //
590        // Note that both values need to be checked for sufficient change before updating the
591        // proposed ambient light value since the slow value might be sufficiently far enough away
592        // from the fast value to cause a recalculation while its actually just converging on
593        // the fast value still.
594        float slowAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_LONG_HORIZON_MILLIS);
595        float fastAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS);
596
597        if ((slowAmbientLux >= mBrighteningLuxThreshold &&
598             fastAmbientLux >= mBrighteningLuxThreshold &&
599             nextBrightenTransition <= time)
600             ||
601            (slowAmbientLux <= mDarkeningLuxThreshold &&
602             fastAmbientLux <= mDarkeningLuxThreshold &&
603             nextDarkenTransition <= time)) {
604            setAmbientLux(fastAmbientLux);
605            if (DEBUG) {
606                Slog.d(TAG, "updateAmbientLux: " +
607                        ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " +
608                        "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + ", " +
609                        "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " +
610                        "mAmbientLux=" + mAmbientLux);
611            }
612            updateAutoBrightness(true);
613            nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
614            nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
615        }
616        long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition);
617        // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't
618        // exceed the necessary threshold, then it's possible we'll get a transition time prior to
619        // now. Rather than continually checking to see whether the weighted lux exceeds the
620        // threshold, schedule an update for when we'd normally expect another light sample, which
621        // should be enough time to decide whether we should actually transition to the new
622        // weighted ambient lux or not.
623        nextTransitionTime =
624                nextTransitionTime > time ? nextTransitionTime : time + mNormalLightSensorRate;
625        if (DEBUG) {
626            Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " +
627                    nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
628        }
629        mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime);
630    }
631
632    private void updateAutoBrightness(boolean sendUpdate) {
633        if (!mAmbientLuxValid) {
634            return;
635        }
636
637        float value = mBrightnessMapper.getBrightness(mAmbientLux);
638
639        int newScreenAutoBrightness =
640                clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
641        if (mScreenAutoBrightness != newScreenAutoBrightness) {
642            if (DEBUG) {
643                Slog.d(TAG, "updateAutoBrightness: " +
644                        "mScreenAutoBrightness=" + mScreenAutoBrightness + ", " +
645                        "newScreenAutoBrightness=" + newScreenAutoBrightness);
646            }
647
648            mScreenAutoBrightness = newScreenAutoBrightness;
649            if (sendUpdate) {
650                mCallbacks.updateBrightness();
651            }
652        }
653    }
654
655    private int clampScreenBrightness(int value) {
656        return MathUtils.constrain(value,
657                mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
658    }
659
660    private void prepareBrightnessAdjustmentSample() {
661        if (!mBrightnessAdjustmentSamplePending) {
662            mBrightnessAdjustmentSamplePending = true;
663            mBrightnessAdjustmentSampleOldLux = mAmbientLuxValid ? mAmbientLux : -1;
664            mBrightnessAdjustmentSampleOldBrightness = mScreenAutoBrightness;
665        } else {
666            mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE);
667        }
668
669        mHandler.sendEmptyMessageDelayed(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE,
670                BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS);
671    }
672
673    private void cancelBrightnessAdjustmentSample() {
674        if (mBrightnessAdjustmentSamplePending) {
675            mBrightnessAdjustmentSamplePending = false;
676            mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE);
677        }
678    }
679
680    private void collectBrightnessAdjustmentSample() {
681        if (mBrightnessAdjustmentSamplePending) {
682            mBrightnessAdjustmentSamplePending = false;
683            if (mAmbientLuxValid && mScreenAutoBrightness >= 0) {
684                if (DEBUG) {
685                    Slog.d(TAG, "Auto-brightness adjustment changed by user: " +
686                            "lux=" + mAmbientLux + ", " +
687                            "brightness=" + mScreenAutoBrightness + ", " +
688                            "ring=" + mAmbientLightRingBuffer);
689                }
690
691                EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ,
692                        mBrightnessAdjustmentSampleOldLux,
693                        mBrightnessAdjustmentSampleOldBrightness,
694                        mAmbientLux,
695                        mScreenAutoBrightness);
696            }
697        }
698    }
699
700    private final class AutomaticBrightnessHandler extends Handler {
701        public AutomaticBrightnessHandler(Looper looper) {
702            super(looper, null, true /*async*/);
703        }
704
705        @Override
706        public void handleMessage(Message msg) {
707            switch (msg.what) {
708                case MSG_UPDATE_AMBIENT_LUX:
709                    updateAmbientLux();
710                    break;
711
712                case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE:
713                    collectBrightnessAdjustmentSample();
714                    break;
715
716                case MSG_INVALIDATE_SHORT_TERM_MODEL:
717                    invalidateShortTermModel();
718                    break;
719            }
720        }
721    }
722
723    private final SensorEventListener mLightSensorListener = new SensorEventListener() {
724        @Override
725        public void onSensorChanged(SensorEvent event) {
726            if (mLightSensorEnabled) {
727                final long time = SystemClock.uptimeMillis();
728                final float lux = event.values[0];
729                handleLightSensorEvent(time, lux);
730            }
731        }
732
733        @Override
734        public void onAccuracyChanged(Sensor sensor, int accuracy) {
735            // Not used.
736        }
737    };
738
739    /** Callbacks to request updates to the display's power state. */
740    interface Callbacks {
741        void updateBrightness();
742    }
743
744    /**
745     * A ring buffer of ambient light measurements sorted by time.
746     *
747     * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted
748     * from oldest to newest.
749     */
750    private static final class AmbientLightRingBuffer {
751        // Proportional extra capacity of the buffer beyond the expected number of light samples
752        // in the horizon
753        private static final float BUFFER_SLACK = 1.5f;
754        private float[] mRingLux;
755        private long[] mRingTime;
756        private int mCapacity;
757
758        // The first valid element and the next open slot.
759        // Note that if mCount is zero then there are no valid elements.
760        private int mStart;
761        private int mEnd;
762        private int mCount;
763
764        public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
765            mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
766            mRingLux = new float[mCapacity];
767            mRingTime = new long[mCapacity];
768        }
769
770        public float getLux(int index) {
771            return mRingLux[offsetOf(index)];
772        }
773
774        public long getTime(int index) {
775            return mRingTime[offsetOf(index)];
776        }
777
778        public void push(long time, float lux) {
779            int next = mEnd;
780            if (mCount == mCapacity) {
781                int newSize = mCapacity * 2;
782
783                float[] newRingLux = new float[newSize];
784                long[] newRingTime = new long[newSize];
785                int length = mCapacity - mStart;
786                System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
787                System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
788                if (mStart != 0) {
789                    System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
790                    System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
791                }
792                mRingLux = newRingLux;
793                mRingTime = newRingTime;
794
795                next = mCapacity;
796                mCapacity = newSize;
797                mStart = 0;
798            }
799            mRingTime[next] = time;
800            mRingLux[next] = lux;
801            mEnd = next + 1;
802            if (mEnd == mCapacity) {
803                mEnd = 0;
804            }
805            mCount++;
806        }
807
808        public void prune(long horizon) {
809            if (mCount == 0) {
810                return;
811            }
812
813            while (mCount > 1) {
814                int next = mStart + 1;
815                if (next >= mCapacity) {
816                    next -= mCapacity;
817                }
818                if (mRingTime[next] > horizon) {
819                    // Some light sensors only produce data upon a change in the ambient light
820                    // levels, so we need to consider the previous measurement as the ambient light
821                    // level for all points in time up until we receive a new measurement. Thus, we
822                    // always want to keep the youngest element that would be removed from the
823                    // buffer and just set its measurement time to the horizon time since at that
824                    // point it is the ambient light level, and to remove it would be to drop a
825                    // valid data point within our horizon.
826                    break;
827                }
828                mStart = next;
829                mCount -= 1;
830            }
831
832            if (mRingTime[mStart] < horizon) {
833                mRingTime[mStart] = horizon;
834            }
835        }
836
837        public int size() {
838            return mCount;
839        }
840
841        public void clear() {
842            mStart = 0;
843            mEnd = 0;
844            mCount = 0;
845        }
846
847        @Override
848        public String toString() {
849            StringBuffer buf = new StringBuffer();
850            buf.append('[');
851            for (int i = 0; i < mCount; i++) {
852                final long next = i + 1 < mCount ? getTime(i + 1) : SystemClock.uptimeMillis();
853                if (i != 0) {
854                    buf.append(", ");
855                }
856                buf.append(getLux(i));
857                buf.append(" / ");
858                buf.append(next - getTime(i));
859                buf.append("ms");
860            }
861            buf.append(']');
862            return buf.toString();
863        }
864
865        private int offsetOf(int index) {
866            if (index >= mCount || index < 0) {
867                throw new ArrayIndexOutOfBoundsException(index);
868            }
869            index += mStart;
870            if (index >= mCapacity) {
871                index -= mCapacity;
872            }
873            return index;
874        }
875    }
876}
877