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