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