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