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