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