AutomaticBrightnessController.java revision 6dee605c834fe4926cc1ae5c584563bdb2b608a1
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 = true; 52 53 // The maximum range of gamma adjustment possible using the screen 54 // auto-brightness adjustment setting. 55 private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f; 56 57 // Light sensor event rate in milliseconds. 58 private static final int LIGHT_SENSOR_RATE_MILLIS = 1000; 59 60 // Period of time in which to consider light samples in milliseconds. 61 private static final int AMBIENT_LIGHT_HORIZON = 10000; 62 63 // Stability requirements in milliseconds for accepting a new brightness level. This is used 64 // for debouncing the light sensor. Different constants are used to debounce the light sensor 65 // when adapting to brighter or darker environments. This parameter controls how quickly 66 // brightness changes occur in response to an observed change in light level that exceeds the 67 // hysteresis threshold. 68 private static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000; 69 private static final long DARKENING_LIGHT_DEBOUNCE = 8000; 70 71 // Hysteresis constraints for brightening or darkening. 72 // The recent lux must have changed by at least this fraction relative to the 73 // current ambient lux before a change will be considered. 74 private static final float BRIGHTENING_LIGHT_HYSTERESIS = 0.10f; 75 private static final float DARKENING_LIGHT_HYSTERESIS = 0.20f; 76 77 // The intercept used for the weighting calculation. This is used in order to keep all possible 78 // weighting values positive. 79 private static final int WEIGHTING_INTERCEPT = AMBIENT_LIGHT_HORIZON; 80 81 // How long the current sensor reading is assumed to be valid beyond the current time. 82 // This provides a bit of prediction, as well as ensures that the weight for the last sample is 83 // non-zero, which in turn ensures that the total weight is non-zero. 84 private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100; 85 86 // If true, enables the use of the current time as an auto-brightness adjustment. 87 // The basic idea here is to expand the dynamic range of auto-brightness 88 // when it is especially dark outside. The light sensor tends to perform 89 // poorly at low light levels so we compensate for it by making an 90 // assumption about the environment. 91 private static final boolean USE_TWILIGHT_ADJUSTMENT = 92 PowerManager.useTwilightAdjustmentFeature(); 93 94 // Specifies the maximum magnitude of the time of day adjustment. 95 private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1.5f; 96 97 // The amount of time after or before sunrise over which to start adjusting 98 // the gamma. We want the change to happen gradually so that it is below the 99 // threshold of perceptibility and so that the adjustment has maximum effect 100 // well after dusk. 101 private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2; 102 103 private static final int MSG_UPDATE_AMBIENT_LUX = 1; 104 105 // Callbacks for requesting updates to the the display's power state 106 private final Callbacks mCallbacks; 107 108 // The sensor manager. 109 private final SensorManager mSensorManager; 110 111 // The light sensor, or null if not available or needed. 112 private final Sensor mLightSensor; 113 114 // The twilight service. 115 private final TwilightManager mTwilight; 116 117 // The auto-brightness spline adjustment. 118 // The brightness values have been scaled to a range of 0..1. 119 private final Spline mScreenAutoBrightnessSpline; 120 121 // The minimum and maximum screen brightnesses. 122 private final int mScreenBrightnessRangeMinimum; 123 private final int mScreenBrightnessRangeMaximum; 124 125 // Amount of time to delay auto-brightness after screen on while waiting for 126 // the light sensor to warm-up in milliseconds. 127 // May be 0 if no warm-up is required. 128 private int mLightSensorWarmUpTimeConfig; 129 130 // Set to true if the light sensor is enabled. 131 private boolean mLightSensorEnabled; 132 133 // The time when the light sensor was enabled. 134 private long mLightSensorEnableTime; 135 136 // The currently accepted nominal ambient light level. 137 private float mAmbientLux; 138 139 // True if mAmbientLux holds a valid value. 140 private boolean mAmbientLuxValid; 141 142 // The ambient light level threshold at which to brighten or darken the screen. 143 private float mBrighteningLuxThreshold; 144 private float mDarkeningLuxThreshold; 145 146 // The most recent light sample. 147 private float mLastObservedLux; 148 149 // The time of the most light recent sample. 150 private long mLastObservedLuxTime; 151 152 // The number of light samples collected since the light sensor was enabled. 153 private int mRecentLightSamples; 154 155 // A ring buffer containing all of the recent ambient light sensor readings. 156 private AmbientLightRingBuffer mAmbientLightRingBuffer; 157 158 // The handler 159 private AutomaticBrightnessHandler mHandler; 160 161 // The screen brightness level that has been chosen by the auto-brightness 162 // algorithm. The actual brightness should ramp towards this value. 163 // We preserve this value even when we stop using the light sensor so 164 // that we can quickly revert to the previous auto-brightness level 165 // while the light sensor warms up. 166 // Use -1 if there is no current auto-brightness value available. 167 private int mScreenAutoBrightness = -1; 168 169 // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter) 170 private float mScreenAutoBrightnessAdjustment = 0.0f; 171 172 // The last screen auto-brightness gamma. (For printing in dump() only.) 173 private float mLastScreenAutoBrightnessGamma = 1.0f; 174 175 public AutomaticBrightnessController(Callbacks callbacks, Looper looper, 176 SensorManager sensorManager, Spline autoBrightnessSpline, 177 int lightSensorWarmUpTime, int brightnessMin, int brightnessMax) { 178 mCallbacks = callbacks; 179 mTwilight = LocalServices.getService(TwilightManager.class); 180 mSensorManager = sensorManager; 181 mScreenAutoBrightnessSpline = autoBrightnessSpline; 182 mScreenBrightnessRangeMinimum = brightnessMin; 183 mScreenBrightnessRangeMaximum = brightnessMax; 184 mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime; 185 186 mHandler = new AutomaticBrightnessHandler(looper); 187 mAmbientLightRingBuffer = new AmbientLightRingBuffer(); 188 189 if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { 190 mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); 191 } 192 193 if (USE_TWILIGHT_ADJUSTMENT) { 194 mTwilight.registerListener(mTwilightListener, mHandler); 195 } 196 } 197 198 public int getAutomaticScreenBrightness() { 199 return mScreenAutoBrightness; 200 } 201 202 public void updatePowerState(DisplayManagerInternal.DisplayPowerRequest request) { 203 if (setScreenAutoBrightnessAdjustment(request.screenAutoBrightnessAdjustment) 204 | setLightSensorEnabled(request.wantLightSensorEnabled())) { 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