AutomaticBrightnessController.java revision c5d3291487d4b6a09cbeac222831bb1b2203d957
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.EventLogTags; 20import com.android.server.LocalServices; 21 22import android.annotation.Nullable; 23import android.hardware.Sensor; 24import android.hardware.SensorEvent; 25import android.hardware.SensorEventListener; 26import android.hardware.SensorManager; 27import android.hardware.display.BrightnessConfiguration; 28import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; 29import android.os.Handler; 30import android.os.Looper; 31import android.os.Message; 32import android.os.PowerManager; 33import android.os.SystemClock; 34import android.os.Trace; 35import android.text.format.DateUtils; 36import android.util.EventLog; 37import android.util.MathUtils; 38import android.util.Slog; 39import android.util.TimeUtils; 40 41import java.io.PrintWriter; 42 43class AutomaticBrightnessController { 44 private static final String TAG = "AutomaticBrightnessController"; 45 46 private static final boolean DEBUG = false; 47 private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; 48 49 // If true, enables the use of the screen auto-brightness adjustment setting. 50 private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = true; 51 52 // How long the current sensor reading is assumed to be valid beyond the current time. 53 // This provides a bit of prediction, as well as ensures that the weight for the last sample is 54 // non-zero, which in turn ensures that the total weight is non-zero. 55 private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100; 56 57 // Debounce for sampling user-initiated changes in display brightness to ensure 58 // the user is satisfied with the result before storing the sample. 59 private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000; 60 61 // Timeout after which we remove the effects any user interactions might've had on the 62 // brightness mapping. This timeout doesn't start until we transition to a non-interactive 63 // display policy so that we don't reset while users are using their devices, but also so that 64 // we don't erroneously keep the short-term model if the device is dozing but the display is 65 // fully on. 66 private static final int SHORT_TERM_MODEL_TIMEOUT_MILLIS = 30000; 67 68 private static final int MSG_UPDATE_AMBIENT_LUX = 1; 69 private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2; 70 private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3; 71 72 // Length of the ambient light horizon used to calculate the long term estimate of ambient 73 // light. 74 private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000; 75 76 // Length of the ambient light horizon used to calculate short-term estimate of ambient light. 77 private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; 78 79 // Callbacks for requesting updates to the display's power state 80 private final Callbacks mCallbacks; 81 82 // The sensor manager. 83 private final SensorManager mSensorManager; 84 85 // The light sensor, or null if not available or needed. 86 private final Sensor mLightSensor; 87 88 // The mapper to translate ambient lux to screen brightness in the range [0, 1.0]. 89 private final BrightnessMappingStrategy mBrightnessMapper; 90 91 // The minimum and maximum screen brightnesses. 92 private final int mScreenBrightnessRangeMinimum; 93 private final int mScreenBrightnessRangeMaximum; 94 private final float mDozeScaleFactor; 95 96 // Initial light sensor event rate in milliseconds. 97 private final int mInitialLightSensorRate; 98 99 // Steady-state light sensor event rate in milliseconds. 100 private final int mNormalLightSensorRate; 101 102 // The current light sensor event rate in milliseconds. 103 private int mCurrentLightSensorRate; 104 105 // Stability requirements in milliseconds for accepting a new brightness level. This is used 106 // for debouncing the light sensor. Different constants are used to debounce the light sensor 107 // when adapting to brighter or darker environments. This parameter controls how quickly 108 // brightness changes occur in response to an observed change in light level that exceeds the 109 // hysteresis threshold. 110 private final long mBrighteningLightDebounceConfig; 111 private final long mDarkeningLightDebounceConfig; 112 113 // If true immediately after the screen is turned on the controller will try to adjust the 114 // brightness based on the current sensor reads. If false, the controller will collect more data 115 // and only then decide whether to change brightness. 116 private final boolean mResetAmbientLuxAfterWarmUpConfig; 117 118 // Period of time in which to consider light samples in milliseconds. 119 private final int mAmbientLightHorizon; 120 121 // The intercept used for the weighting calculation. This is used in order to keep all possible 122 // weighting values positive. 123 private final int mWeightingIntercept; 124 125 // accessor object for determining thresholds to change brightness dynamically 126 private final HysteresisLevels mDynamicHysteresis; 127 128 // Amount of time to delay auto-brightness after screen on while waiting for 129 // the light sensor to warm-up in milliseconds. 130 // May be 0 if no warm-up is required. 131 private int mLightSensorWarmUpTimeConfig; 132 133 // Set to true if the light sensor is enabled. 134 private boolean mLightSensorEnabled; 135 136 // The time when the light sensor was enabled. 137 private long mLightSensorEnableTime; 138 139 // The currently accepted nominal ambient light level. 140 private float mAmbientLux; 141 142 // True if mAmbientLux holds a valid value. 143 private boolean mAmbientLuxValid; 144 145 // The ambient light level threshold at which to brighten or darken the screen. 146 private float mBrighteningLuxThreshold; 147 private float mDarkeningLuxThreshold; 148 149 // The most recent light sample. 150 private float mLastObservedLux; 151 152 // The time of the most light recent sample. 153 private long mLastObservedLuxTime; 154 155 // The number of light samples collected since the light sensor was enabled. 156 private int mRecentLightSamples; 157 158 // A ring buffer containing all of the recent ambient light sensor readings. 159 private AmbientLightRingBuffer mAmbientLightRingBuffer; 160 161 // The handler 162 private AutomaticBrightnessHandler mHandler; 163 164 // The screen brightness level that has been chosen by the auto-brightness 165 // algorithm. The actual brightness should ramp towards this value. 166 // We preserve this value even when we stop using the light sensor so 167 // that we can quickly revert to the previous auto-brightness level 168 // while the light sensor warms up. 169 // Use -1 if there is no current auto-brightness value available. 170 private int mScreenAutoBrightness = -1; 171 172 // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter) 173 private float mScreenAutoBrightnessAdjustment = 0.0f; 174 175 // The maximum range of gamma adjustment possible using the screen 176 // auto-brightness adjustment setting. 177 private float mScreenAutoBrightnessAdjustmentMaxGamma; 178 179 // The last screen auto-brightness gamma. (For printing in dump() only.) 180 private float mLastScreenAutoBrightnessGamma = 1.0f; 181 182 // The current display policy. This is useful, for example, for knowing when we're dozing, 183 // where the light sensor may not be available. 184 private int mDisplayPolicy = DisplayPowerRequest.POLICY_OFF; 185 186 // True if we are collecting a brightness adjustment sample, along with some data 187 // for the initial state of the sample. 188 private boolean mBrightnessAdjustmentSamplePending; 189 private float mBrightnessAdjustmentSampleOldAdjustment; 190 private float mBrightnessAdjustmentSampleOldLux; 191 private int mBrightnessAdjustmentSampleOldBrightness; 192 private float mBrightnessAdjustmentSampleOldGamma; 193 194 // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the 195 // user's adjustment) immediately, but wait for a drastic enough change in the ambient light. 196 // The anchor determines what were the light levels when the user has set her preference, and 197 // we use a relative threshold to determine when to revert to the OEM curve. 198 private boolean mShortTermModelValid; 199 private float mShortTermModelAnchor; 200 private float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f; 201 202 public AutomaticBrightnessController(Callbacks callbacks, Looper looper, 203 SensorManager sensorManager, BrightnessMappingStrategy mapper, int lightSensorWarmUpTime, 204 int brightnessMin, int brightnessMax, float dozeScaleFactor, 205 int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, 206 long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, 207 int ambientLightHorizon, float autoBrightnessAdjustmentMaxGamma, 208 HysteresisLevels dynamicHysteresis) { 209 mCallbacks = callbacks; 210 mSensorManager = sensorManager; 211 mBrightnessMapper = mapper; 212 mScreenBrightnessRangeMinimum = brightnessMin; 213 mScreenBrightnessRangeMaximum = brightnessMax; 214 mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime; 215 mDozeScaleFactor = dozeScaleFactor; 216 mNormalLightSensorRate = lightSensorRate; 217 mInitialLightSensorRate = initialLightSensorRate; 218 mCurrentLightSensorRate = -1; 219 mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; 220 mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; 221 mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; 222 mAmbientLightHorizon = ambientLightHorizon; 223 mWeightingIntercept = ambientLightHorizon; 224 mScreenAutoBrightnessAdjustmentMaxGamma = autoBrightnessAdjustmentMaxGamma; 225 mDynamicHysteresis = dynamicHysteresis; 226 mShortTermModelValid = true; 227 mShortTermModelAnchor = -1; 228 229 mHandler = new AutomaticBrightnessHandler(looper); 230 mAmbientLightRingBuffer = 231 new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon); 232 233 if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { 234 mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); 235 } 236 } 237 238 public int getAutomaticScreenBrightness() { 239 if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) { 240 return (int) (mScreenAutoBrightness * mDozeScaleFactor); 241 } 242 return mScreenAutoBrightness; 243 } 244 245 public float getAutomaticScreenBrightnessAdjustment() { 246 return mScreenAutoBrightnessAdjustment; 247 } 248 249 public void configure(boolean enable, @Nullable BrightnessConfiguration configuration, 250 float brightness, boolean userChangedBrightness, float adjustment, 251 boolean userChangedAutoBrightnessAdjustment, int displayPolicy) { 252 // While dozing, the application processor may be suspended which will prevent us from 253 // receiving new information from the light sensor. On some devices, we may be able to 254 // switch to a wake-up light sensor instead but for now we will simply disable the sensor 255 // and hold onto the last computed screen auto brightness. We save the dozing flag for 256 // debugging purposes. 257 boolean dozing = (displayPolicy == DisplayPowerRequest.POLICY_DOZE); 258 boolean changed = setBrightnessConfiguration(configuration); 259 changed |= setDisplayPolicy(displayPolicy); 260 changed |= setScreenAutoBrightnessAdjustment(adjustment); 261 if (userChangedBrightness && enable) { 262 // Update the brightness curve with the new user control point. It's critical this 263 // happens after we update the autobrightness adjustment since it may reset it. 264 changed |= setScreenBrightnessByUser(brightness); 265 } 266 final boolean userInitiatedChange = 267 userChangedBrightness || userChangedAutoBrightnessAdjustment; 268 if (userInitiatedChange && enable && !dozing) { 269 prepareBrightnessAdjustmentSample(); 270 } 271 changed |= setLightSensorEnabled(enable && !dozing); 272 if (changed) { 273 updateAutoBrightness(false /*sendUpdate*/); 274 } 275 } 276 277 public boolean hasUserDataPoints() { 278 return mBrightnessMapper.hasUserDataPoints(); 279 } 280 281 public boolean isDefaultConfig() { 282 return mBrightnessMapper.isDefaultConfig(); 283 } 284 285 private boolean setDisplayPolicy(int policy) { 286 if (mDisplayPolicy == policy) { 287 return false; 288 } 289 final int oldPolicy = mDisplayPolicy; 290 mDisplayPolicy = policy; 291 if (DEBUG) { 292 Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy); 293 } 294 if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) { 295 mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_SHORT_TERM_MODEL, 296 SHORT_TERM_MODEL_TIMEOUT_MILLIS); 297 } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) { 298 mHandler.removeMessages(MSG_INVALIDATE_SHORT_TERM_MODEL); 299 } 300 return true; 301 } 302 303 private static boolean isInteractivePolicy(int policy) { 304 return policy == DisplayPowerRequest.POLICY_BRIGHT 305 || policy == DisplayPowerRequest.POLICY_DIM 306 || policy == DisplayPowerRequest.POLICY_VR; 307 } 308 309 private boolean setScreenBrightnessByUser(float brightness) { 310 if (!mAmbientLuxValid) { 311 // If we don't have a valid ambient lux then we don't have a valid brightness anyways, 312 // and we can't use this data to add a new control point to the short-term model. 313 return false; 314 } 315 mBrightnessMapper.addUserDataPoint(mAmbientLux, brightness); 316 mShortTermModelValid = true; 317 mShortTermModelAnchor = mAmbientLux; 318 if (DEBUG) { 319 Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor); 320 } 321 // Reset the brightness adjustment so that the next time we're queried for brightness we 322 // return the value the user set. 323 mScreenAutoBrightnessAdjustment = 0.0f; 324 return true; 325 } 326 327 private void resetShortTermModel() { 328 mBrightnessMapper.clearUserDataPoints(); 329 mShortTermModelValid = true; 330 mShortTermModelAnchor = -1; 331 } 332 333 private void invalidateShortTermModel() { 334 if (DEBUG) { 335 Slog.d(TAG, "ShortTermModel: invalidate user data"); 336 } 337 mShortTermModelValid = false; 338 } 339 340 public boolean setBrightnessConfiguration(BrightnessConfiguration configuration) { 341 return mBrightnessMapper.setBrightnessConfiguration(configuration); 342 } 343 344 public void dump(PrintWriter pw) { 345 pw.println(); 346 pw.println("Automatic Brightness Controller Configuration:"); 347 pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); 348 pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); 349 pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); 350 pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); 351 pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); 352 pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); 353 354 pw.println(); 355 pw.println("Automatic Brightness Controller State:"); 356 pw.println(" mLightSensor=" + mLightSensor); 357 pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); 358 pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime)); 359 pw.println(" mAmbientLux=" + mAmbientLux); 360 pw.println(" mAmbientLightHorizon=" + mAmbientLightHorizon); 361 pw.println(" mBrighteningLuxThreshold=" + mBrighteningLuxThreshold); 362 pw.println(" mDarkeningLuxThreshold=" + mDarkeningLuxThreshold); 363 pw.println(" mLastObservedLux=" + mLastObservedLux); 364 pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime)); 365 pw.println(" mRecentLightSamples=" + mRecentLightSamples); 366 pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer); 367 pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness); 368 pw.println(" mScreenAutoBrightnessAdjustment=" + mScreenAutoBrightnessAdjustment); 369 pw.println(" mScreenAutoBrightnessAdjustmentMaxGamma=" 370 + mScreenAutoBrightnessAdjustmentMaxGamma); 371 pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma); 372 pw.println(" mDisplayPolicy=" + mDisplayPolicy); 373 pw.println(" mShortTermModelAnchor=" + mShortTermModelAnchor); 374 375 pw.println(); 376 mBrightnessMapper.dump(pw); 377 } 378 379 private boolean setLightSensorEnabled(boolean enable) { 380 if (enable) { 381 if (!mLightSensorEnabled) { 382 mLightSensorEnabled = true; 383 mLightSensorEnableTime = SystemClock.uptimeMillis(); 384 mCurrentLightSensorRate = mInitialLightSensorRate; 385 mSensorManager.registerListener(mLightSensorListener, mLightSensor, 386 mCurrentLightSensorRate * 1000, mHandler); 387 return true; 388 } 389 } else if (mLightSensorEnabled) { 390 mLightSensorEnabled = false; 391 mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig; 392 mRecentLightSamples = 0; 393 mAmbientLightRingBuffer.clear(); 394 mCurrentLightSensorRate = -1; 395 mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); 396 mSensorManager.unregisterListener(mLightSensorListener); 397 } 398 return false; 399 } 400 401 private void handleLightSensorEvent(long time, float lux) { 402 Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux); 403 mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); 404 405 if (mAmbientLightRingBuffer.size() == 0) { 406 // switch to using the steady-state sample rate after grabbing the initial light sample 407 adjustLightSensorRate(mNormalLightSensorRate); 408 } 409 applyLightSensorMeasurement(time, lux); 410 updateAmbientLux(time); 411 } 412 413 private void applyLightSensorMeasurement(long time, float lux) { 414 mRecentLightSamples++; 415 mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon); 416 mAmbientLightRingBuffer.push(time, lux); 417 418 // Remember this sample value. 419 mLastObservedLux = lux; 420 mLastObservedLuxTime = time; 421 } 422 423 private void adjustLightSensorRate(int lightSensorRate) { 424 // if the light sensor rate changed, update the sensor listener 425 if (lightSensorRate != mCurrentLightSensorRate) { 426 if (DEBUG) { 427 Slog.d(TAG, "adjustLightSensorRate: " + 428 "previousRate=" + mCurrentLightSensorRate + ", " + 429 "currentRate=" + lightSensorRate); 430 } 431 mCurrentLightSensorRate = lightSensorRate; 432 mSensorManager.unregisterListener(mLightSensorListener); 433 mSensorManager.registerListener(mLightSensorListener, mLightSensor, 434 lightSensorRate * 1000, mHandler); 435 } 436 } 437 438 private boolean setScreenAutoBrightnessAdjustment(float adjustment) { 439 if (adjustment != mScreenAutoBrightnessAdjustment) { 440 mScreenAutoBrightnessAdjustment = adjustment; 441 return true; 442 } 443 return false; 444 } 445 446 private void setAmbientLux(float lux) { 447 if (DEBUG) { 448 Slog.d(TAG, "setAmbientLux(" + lux + ")"); 449 } 450 if (lux < 0) { 451 Slog.w(TAG, "Ambient lux was negative, ignoring and setting to 0"); 452 lux = 0; 453 } 454 mAmbientLux = lux; 455 mBrighteningLuxThreshold = mDynamicHysteresis.getBrighteningThreshold(lux); 456 mDarkeningLuxThreshold = mDynamicHysteresis.getDarkeningThreshold(lux); 457 458 // If the short term model was invalidated and the change is drastic enough, reset it. 459 if (!mShortTermModelValid && mShortTermModelAnchor != -1) { 460 final float minAmbientLux = 461 mShortTermModelAnchor - mShortTermModelAnchor * SHORT_TERM_MODEL_THRESHOLD_RATIO; 462 final float maxAmbientLux = 463 mShortTermModelAnchor + mShortTermModelAnchor * SHORT_TERM_MODEL_THRESHOLD_RATIO; 464 if (minAmbientLux < mAmbientLux && mAmbientLux < maxAmbientLux) { 465 Slog.d(TAG, "ShortTermModel: re-validate user data, ambient lux is " + 466 minAmbientLux + " < " + mAmbientLux + " < " + maxAmbientLux); 467 mShortTermModelValid = true; 468 } else { 469 Slog.d(TAG, "ShortTermModel: reset data, ambient lux is " + mAmbientLux + 470 "(" + minAmbientLux + ", " + maxAmbientLux + ")"); 471 resetShortTermModel(); 472 } 473 } 474 } 475 476 private float calculateAmbientLux(long now, long horizon) { 477 if (DEBUG) { 478 Slog.d(TAG, "calculateAmbientLux(" + now + ", " + horizon + ")"); 479 } 480 final int N = mAmbientLightRingBuffer.size(); 481 if (N == 0) { 482 Slog.e(TAG, "calculateAmbientLux: No ambient light readings available"); 483 return -1; 484 } 485 486 // Find the first measurement that is just outside of the horizon. 487 int endIndex = 0; 488 final long horizonStartTime = now - horizon; 489 for (int i = 0; i < N-1; i++) { 490 if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) { 491 endIndex++; 492 } else { 493 break; 494 } 495 } 496 if (DEBUG) { 497 Slog.d(TAG, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" 498 + mAmbientLightRingBuffer.getTime(endIndex) + ", " 499 + mAmbientLightRingBuffer.getLux(endIndex) + ")"); 500 } 501 float sum = 0; 502 float totalWeight = 0; 503 long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS; 504 for (int i = N - 1; i >= endIndex; i--) { 505 long eventTime = mAmbientLightRingBuffer.getTime(i); 506 if (i == endIndex && eventTime < horizonStartTime) { 507 // If we're at the final value, make sure we only consider the part of the sample 508 // within our desired horizon. 509 eventTime = horizonStartTime; 510 } 511 final long startTime = eventTime - now; 512 float weight = calculateWeight(startTime, endTime); 513 float lux = mAmbientLightRingBuffer.getLux(i); 514 if (DEBUG) { 515 Slog.d(TAG, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " + 516 "lux=" + lux + ", " + 517 "weight=" + weight); 518 } 519 totalWeight += weight; 520 sum += lux * weight; 521 endTime = startTime; 522 } 523 if (DEBUG) { 524 Slog.d(TAG, "calculateAmbientLux: " + 525 "totalWeight=" + totalWeight + ", " + 526 "newAmbientLux=" + (sum / totalWeight)); 527 } 528 return sum / totalWeight; 529 } 530 531 private float calculateWeight(long startDelta, long endDelta) { 532 return weightIntegral(endDelta) - weightIntegral(startDelta); 533 } 534 535 // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the 536 // horizon we're looking at and provides a non-linear weighting for light samples. 537 private float weightIntegral(long x) { 538 return x * (x * 0.5f + mWeightingIntercept); 539 } 540 541 private long nextAmbientLightBrighteningTransition(long time) { 542 final int N = mAmbientLightRingBuffer.size(); 543 long earliestValidTime = time; 544 for (int i = N - 1; i >= 0; i--) { 545 if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) { 546 break; 547 } 548 earliestValidTime = mAmbientLightRingBuffer.getTime(i); 549 } 550 return earliestValidTime + mBrighteningLightDebounceConfig; 551 } 552 553 private long nextAmbientLightDarkeningTransition(long time) { 554 final int N = mAmbientLightRingBuffer.size(); 555 long earliestValidTime = time; 556 for (int i = N - 1; i >= 0; i--) { 557 if (mAmbientLightRingBuffer.getLux(i) >= mDarkeningLuxThreshold) { 558 break; 559 } 560 earliestValidTime = mAmbientLightRingBuffer.getTime(i); 561 } 562 return earliestValidTime + mDarkeningLightDebounceConfig; 563 } 564 565 private void updateAmbientLux() { 566 long time = SystemClock.uptimeMillis(); 567 mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon); 568 updateAmbientLux(time); 569 } 570 571 private void updateAmbientLux(long time) { 572 // If the light sensor was just turned on then immediately update our initial 573 // estimate of the current ambient light level. 574 if (!mAmbientLuxValid) { 575 final long timeWhenSensorWarmedUp = 576 mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; 577 if (time < timeWhenSensorWarmedUp) { 578 if (DEBUG) { 579 Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: " + 580 "time=" + time + ", " + 581 "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); 582 } 583 mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, 584 timeWhenSensorWarmedUp); 585 return; 586 } 587 setAmbientLux(calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS)); 588 mAmbientLuxValid = true; 589 if (DEBUG) { 590 Slog.d(TAG, "updateAmbientLux: Initializing: " + 591 "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + 592 "mAmbientLux=" + mAmbientLux); 593 } 594 updateAutoBrightness(true); 595 } 596 597 long nextBrightenTransition = nextAmbientLightBrighteningTransition(time); 598 long nextDarkenTransition = nextAmbientLightDarkeningTransition(time); 599 // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term 600 // change in lighting conditions, and a fast ambient lux to determine what the new 601 // brightness situation is since the slow lux can be quite slow to converge. 602 // 603 // Note that both values need to be checked for sufficient change before updating the 604 // proposed ambient light value since the slow value might be sufficiently far enough away 605 // from the fast value to cause a recalculation while its actually just converging on 606 // the fast value still. 607 float slowAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_LONG_HORIZON_MILLIS); 608 float fastAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS); 609 610 if ((slowAmbientLux >= mBrighteningLuxThreshold && 611 fastAmbientLux >= mBrighteningLuxThreshold && 612 nextBrightenTransition <= time) 613 || 614 (slowAmbientLux <= mDarkeningLuxThreshold && 615 fastAmbientLux <= mDarkeningLuxThreshold && 616 nextDarkenTransition <= time)) { 617 setAmbientLux(fastAmbientLux); 618 if (DEBUG) { 619 Slog.d(TAG, "updateAmbientLux: " + 620 ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " + 621 "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + ", " + 622 "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + 623 "mAmbientLux=" + mAmbientLux); 624 } 625 updateAutoBrightness(true); 626 nextBrightenTransition = nextAmbientLightBrighteningTransition(time); 627 nextDarkenTransition = nextAmbientLightDarkeningTransition(time); 628 } 629 long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition); 630 // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't 631 // exceed the necessary threshold, then it's possible we'll get a transition time prior to 632 // now. Rather than continually checking to see whether the weighted lux exceeds the 633 // threshold, schedule an update for when we'd normally expect another light sample, which 634 // should be enough time to decide whether we should actually transition to the new 635 // weighted ambient lux or not. 636 nextTransitionTime = 637 nextTransitionTime > time ? nextTransitionTime : time + mNormalLightSensorRate; 638 if (DEBUG) { 639 Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " + 640 nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime)); 641 } 642 mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime); 643 } 644 645 private void updateAutoBrightness(boolean sendUpdate) { 646 if (!mAmbientLuxValid) { 647 return; 648 } 649 650 float value = mBrightnessMapper.getBrightness(mAmbientLux); 651 float gamma = 1.0f; 652 653 if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT 654 && mScreenAutoBrightnessAdjustment != 0.0f) { 655 final float adjGamma = MathUtils.pow(mScreenAutoBrightnessAdjustmentMaxGamma, 656 Math.min(1.0f, Math.max(-1.0f, -mScreenAutoBrightnessAdjustment))); 657 gamma *= adjGamma; 658 if (DEBUG) { 659 Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma); 660 } 661 } 662 663 if (gamma != 1.0f) { 664 final float in = value; 665 value = MathUtils.pow(value, gamma); 666 if (DEBUG) { 667 Slog.d(TAG, "updateAutoBrightness: " + 668 "gamma=" + gamma + ", " + 669 "in=" + in + ", " + 670 "out=" + value); 671 } 672 } 673 674 int newScreenAutoBrightness = 675 clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON)); 676 if (mScreenAutoBrightness != newScreenAutoBrightness) { 677 if (DEBUG) { 678 Slog.d(TAG, "updateAutoBrightness: " + 679 "mScreenAutoBrightness=" + mScreenAutoBrightness + ", " + 680 "newScreenAutoBrightness=" + newScreenAutoBrightness); 681 } 682 683 mScreenAutoBrightness = newScreenAutoBrightness; 684 mLastScreenAutoBrightnessGamma = gamma; 685 if (sendUpdate) { 686 mCallbacks.updateBrightness(); 687 } 688 } 689 } 690 691 private int clampScreenBrightness(int value) { 692 return MathUtils.constrain(value, 693 mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum); 694 } 695 696 private void prepareBrightnessAdjustmentSample() { 697 if (!mBrightnessAdjustmentSamplePending) { 698 mBrightnessAdjustmentSamplePending = true; 699 mBrightnessAdjustmentSampleOldAdjustment = mScreenAutoBrightnessAdjustment; 700 mBrightnessAdjustmentSampleOldLux = mAmbientLuxValid ? mAmbientLux : -1; 701 mBrightnessAdjustmentSampleOldBrightness = mScreenAutoBrightness; 702 mBrightnessAdjustmentSampleOldGamma = mLastScreenAutoBrightnessGamma; 703 } else { 704 mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE); 705 } 706 707 mHandler.sendEmptyMessageDelayed(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE, 708 BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS); 709 } 710 711 private void cancelBrightnessAdjustmentSample() { 712 if (mBrightnessAdjustmentSamplePending) { 713 mBrightnessAdjustmentSamplePending = false; 714 mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE); 715 } 716 } 717 718 private void collectBrightnessAdjustmentSample() { 719 if (mBrightnessAdjustmentSamplePending) { 720 mBrightnessAdjustmentSamplePending = false; 721 if (mAmbientLuxValid && mScreenAutoBrightness >= 0) { 722 if (DEBUG) { 723 Slog.d(TAG, "Auto-brightness adjustment changed by user: " + 724 "adj=" + mScreenAutoBrightnessAdjustment + ", " + 725 "lux=" + mAmbientLux + ", " + 726 "brightness=" + mScreenAutoBrightness + ", " + 727 "gamma=" + mLastScreenAutoBrightnessGamma + ", " + 728 "ring=" + mAmbientLightRingBuffer); 729 } 730 731 EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ, 732 mBrightnessAdjustmentSampleOldAdjustment, 733 mBrightnessAdjustmentSampleOldLux, 734 mBrightnessAdjustmentSampleOldBrightness, 735 mBrightnessAdjustmentSampleOldGamma, 736 mScreenAutoBrightnessAdjustment, 737 mAmbientLux, 738 mScreenAutoBrightness, 739 mLastScreenAutoBrightnessGamma); 740 } 741 } 742 } 743 744 private final class AutomaticBrightnessHandler extends Handler { 745 public AutomaticBrightnessHandler(Looper looper) { 746 super(looper, null, true /*async*/); 747 } 748 749 @Override 750 public void handleMessage(Message msg) { 751 switch (msg.what) { 752 case MSG_UPDATE_AMBIENT_LUX: 753 updateAmbientLux(); 754 break; 755 756 case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE: 757 collectBrightnessAdjustmentSample(); 758 break; 759 760 case MSG_INVALIDATE_SHORT_TERM_MODEL: 761 invalidateShortTermModel(); 762 break; 763 } 764 } 765 } 766 767 private final SensorEventListener mLightSensorListener = new SensorEventListener() { 768 @Override 769 public void onSensorChanged(SensorEvent event) { 770 if (mLightSensorEnabled) { 771 final long time = SystemClock.uptimeMillis(); 772 final float lux = event.values[0]; 773 handleLightSensorEvent(time, lux); 774 } 775 } 776 777 @Override 778 public void onAccuracyChanged(Sensor sensor, int accuracy) { 779 // Not used. 780 } 781 }; 782 783 /** Callbacks to request updates to the display's power state. */ 784 interface Callbacks { 785 void updateBrightness(); 786 } 787 788 /** 789 * A ring buffer of ambient light measurements sorted by time. 790 * 791 * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted 792 * from oldest to newest. 793 */ 794 private static final class AmbientLightRingBuffer { 795 // Proportional extra capacity of the buffer beyond the expected number of light samples 796 // in the horizon 797 private static final float BUFFER_SLACK = 1.5f; 798 private float[] mRingLux; 799 private long[] mRingTime; 800 private int mCapacity; 801 802 // The first valid element and the next open slot. 803 // Note that if mCount is zero then there are no valid elements. 804 private int mStart; 805 private int mEnd; 806 private int mCount; 807 808 public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) { 809 mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate); 810 mRingLux = new float[mCapacity]; 811 mRingTime = new long[mCapacity]; 812 } 813 814 public float getLux(int index) { 815 return mRingLux[offsetOf(index)]; 816 } 817 818 public long getTime(int index) { 819 return mRingTime[offsetOf(index)]; 820 } 821 822 public void push(long time, float lux) { 823 int next = mEnd; 824 if (mCount == mCapacity) { 825 int newSize = mCapacity * 2; 826 827 float[] newRingLux = new float[newSize]; 828 long[] newRingTime = new long[newSize]; 829 int length = mCapacity - mStart; 830 System.arraycopy(mRingLux, mStart, newRingLux, 0, length); 831 System.arraycopy(mRingTime, mStart, newRingTime, 0, length); 832 if (mStart != 0) { 833 System.arraycopy(mRingLux, 0, newRingLux, length, mStart); 834 System.arraycopy(mRingTime, 0, newRingTime, length, mStart); 835 } 836 mRingLux = newRingLux; 837 mRingTime = newRingTime; 838 839 next = mCapacity; 840 mCapacity = newSize; 841 mStart = 0; 842 } 843 mRingTime[next] = time; 844 mRingLux[next] = lux; 845 mEnd = next + 1; 846 if (mEnd == mCapacity) { 847 mEnd = 0; 848 } 849 mCount++; 850 } 851 852 public void prune(long horizon) { 853 if (mCount == 0) { 854 return; 855 } 856 857 while (mCount > 1) { 858 int next = mStart + 1; 859 if (next >= mCapacity) { 860 next -= mCapacity; 861 } 862 if (mRingTime[next] > horizon) { 863 // Some light sensors only produce data upon a change in the ambient light 864 // levels, so we need to consider the previous measurement as the ambient light 865 // level for all points in time up until we receive a new measurement. Thus, we 866 // always want to keep the youngest element that would be removed from the 867 // buffer and just set its measurement time to the horizon time since at that 868 // point it is the ambient light level, and to remove it would be to drop a 869 // valid data point within our horizon. 870 break; 871 } 872 mStart = next; 873 mCount -= 1; 874 } 875 876 if (mRingTime[mStart] < horizon) { 877 mRingTime[mStart] = horizon; 878 } 879 } 880 881 public int size() { 882 return mCount; 883 } 884 885 public void clear() { 886 mStart = 0; 887 mEnd = 0; 888 mCount = 0; 889 } 890 891 @Override 892 public String toString() { 893 StringBuffer buf = new StringBuffer(); 894 buf.append('['); 895 for (int i = 0; i < mCount; i++) { 896 final long next = i + 1 < mCount ? getTime(i + 1) : SystemClock.uptimeMillis(); 897 if (i != 0) { 898 buf.append(", "); 899 } 900 buf.append(getLux(i)); 901 buf.append(" / "); 902 buf.append(next - getTime(i)); 903 buf.append("ms"); 904 } 905 buf.append(']'); 906 return buf.toString(); 907 } 908 909 private int offsetOf(int index) { 910 if (index >= mCount || index < 0) { 911 throw new ArrayIndexOutOfBoundsException(index); 912 } 913 index += mStart; 914 if (index >= mCapacity) { 915 index -= mCapacity; 916 } 917 return index; 918 } 919 } 920} 921