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