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