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