1 /* 2 * Copyright (C) 2019 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 17 package com.android.server.display.whitebalance; 18 19 import android.annotation.NonNull; 20 import android.util.Slog; 21 import android.util.Spline; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 import com.android.internal.util.Preconditions; 25 import com.android.server.LocalServices; 26 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; 27 import com.android.server.display.utils.AmbientFilter; 28 import com.android.server.display.utils.History; 29 30 import java.io.PrintWriter; 31 import java.util.Objects; 32 33 /** 34 * The DisplayWhiteBalanceController drives display white-balance (automatically correcting the 35 * display color temperature depending on the ambient color temperature). 36 * 37 * The DisplayWhiteBalanceController: 38 * - Uses the AmbientColorTemperatureSensor to detect changes in the ambient color temperature; 39 * - Uses the AmbientColorTemperatureFilter to average these changes over time, filter out the 40 * noise, and arrive at an estimate of the actual ambient color temperature; 41 * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color tempearture should 42 * be updated, suppressing changes that are too frequent or too minor. 43 */ 44 public class DisplayWhiteBalanceController implements 45 AmbientSensor.AmbientBrightnessSensor.Callbacks, 46 AmbientSensor.AmbientColorTemperatureSensor.Callbacks { 47 48 protected static final String TAG = "DisplayWhiteBalanceController"; 49 protected boolean mLoggingEnabled; 50 51 private boolean mEnabled; 52 53 // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC 54 // implements Callbacks and passes itself to the DWBC so it can call back into it without 55 // knowing about it. 56 private Callbacks mCallbacks; 57 58 private AmbientSensor.AmbientBrightnessSensor mBrightnessSensor; 59 60 @VisibleForTesting 61 AmbientFilter mBrightnessFilter; 62 private AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor; 63 64 @VisibleForTesting 65 AmbientFilter mColorTemperatureFilter; 66 private DisplayWhiteBalanceThrottler mThrottler; 67 68 // In low brightness conditions the ALS readings are more noisy and produce 69 // high errors. This default is introduced to provide a fixed display color 70 // temperature when sensor readings become unreliable. 71 private final float mLowLightAmbientColorTemperature; 72 // In high brightness conditions certain color temperatures can cause peak display 73 // brightness to drop. This fixed color temperature can be used to compensate for 74 // this effect. 75 private final float mHighLightAmbientColorTemperature; 76 77 private float mAmbientColorTemperature; 78 79 @VisibleForTesting 80 float mPendingAmbientColorTemperature; 81 private float mLastAmbientColorTemperature; 82 83 private ColorDisplayServiceInternal mColorDisplayServiceInternal; 84 85 // The most recent ambient color temperature values are kept for debugging purposes. 86 private static final int HISTORY_SIZE = 50; 87 private History mAmbientColorTemperatureHistory; 88 89 // Override the ambient color temperature for debugging purposes. 90 private float mAmbientColorTemperatureOverride; 91 92 // A piecewise linear relationship between ambient and display color temperatures. 93 private Spline.LinearSpline mAmbientToDisplayColorTemperatureSpline; 94 95 // In very low or very high brightness conditions Display White Balance should 96 // be to set to a default instead of using mAmbientToDisplayColorTemperatureSpline. 97 // However, setting Display White Balance based on thresholds can cause the 98 // display to rapidly change color temperature. To solve this, 99 // mLowLightAmbientBrightnessToBiasSpline and 100 // mHighLightAmbientBrightnessToBiasSpline are used to smoothly interpolate from 101 // ambient color temperature to the defaults. A piecewise linear relationship 102 // between low light brightness and low light bias. 103 private Spline.LinearSpline mLowLightAmbientBrightnessToBiasSpline; 104 105 // A piecewise linear relationship between high light brightness and high light bias. 106 private Spline.LinearSpline mHighLightAmbientBrightnessToBiasSpline; 107 108 private float mLatestAmbientColorTemperature; 109 private float mLatestAmbientBrightness; 110 private float mLatestLowLightBias; 111 private float mLatestHighLightBias; 112 113 /** 114 * @param brightnessSensor 115 * The sensor used to detect changes in the ambient brightness. 116 * @param brightnessFilter 117 * The filter used to avergae ambient brightness changes over time, filter out the noise 118 * and arrive at an estimate of the actual ambient brightness. 119 * @param colorTemperatureSensor 120 * The sensor used to detect changes in the ambient color temperature. 121 * @param colorTemperatureFilter 122 * The filter used to average ambient color temperature changes over time, filter out the 123 * noise and arrive at an estimate of the actual ambient color temperature. 124 * @param throttler 125 * The throttler used to determine whether the new display color temperature should be 126 * updated or not. 127 * @param lowLightAmbientBrightnesses 128 * The ambient brightness used to map the ambient brightnesses to the biases used to 129 * interpolate to lowLightAmbientColorTemperature. 130 * @param lowLightAmbientBiases 131 * The biases used to map the ambient brightnesses to the biases used to interpolate to 132 * lowLightAmbientColorTemperature. 133 * @param lowLightAmbientColorTemperature 134 * The ambient color temperature to which we interpolate to based on the low light curve. 135 * @param highLightAmbientBrightnesses 136 * The ambient brightness used to map the ambient brightnesses to the biases used to 137 * interpolate to highLightAmbientColorTemperature. 138 * @param highLightAmbientBiases 139 * The biases used to map the ambient brightnesses to the biases used to interpolate to 140 * highLightAmbientColorTemperature. 141 * @param highLightAmbientColorTemperature 142 * The ambient color temperature to which we interpolate to based on the high light curve. 143 * @param ambientColorTemperatures 144 * The ambient color tempeartures used to map the ambient color temperature to the display 145 * color temperature (or null if no mapping is necessary). 146 * @param displayColorTemperatures 147 * The display color temperatures used to map the ambient color temperature to the display 148 * color temperature (or null if no mapping is necessary). 149 * 150 * @throws NullPointerException 151 * - brightnessSensor is null; 152 * - brightnessFilter is null; 153 * - colorTemperatureSensor is null; 154 * - colorTemperatureFilter is null; 155 * - throttler is null. 156 */ DisplayWhiteBalanceController( @onNull AmbientSensor.AmbientBrightnessSensor brightnessSensor, @NonNull AmbientFilter brightnessFilter, @NonNull AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor, @NonNull AmbientFilter colorTemperatureFilter, @NonNull DisplayWhiteBalanceThrottler throttler, float[] lowLightAmbientBrightnesses, float[] lowLightAmbientBiases, float lowLightAmbientColorTemperature, float[] highLightAmbientBrightnesses, float[] highLightAmbientBiases, float highLightAmbientColorTemperature, float[] ambientColorTemperatures, float[] displayColorTemperatures)157 public DisplayWhiteBalanceController( 158 @NonNull AmbientSensor.AmbientBrightnessSensor brightnessSensor, 159 @NonNull AmbientFilter brightnessFilter, 160 @NonNull AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor, 161 @NonNull AmbientFilter colorTemperatureFilter, 162 @NonNull DisplayWhiteBalanceThrottler throttler, 163 float[] lowLightAmbientBrightnesses, float[] lowLightAmbientBiases, 164 float lowLightAmbientColorTemperature, 165 float[] highLightAmbientBrightnesses, float[] highLightAmbientBiases, 166 float highLightAmbientColorTemperature, 167 float[] ambientColorTemperatures, float[] displayColorTemperatures) { 168 validateArguments(brightnessSensor, brightnessFilter, colorTemperatureSensor, 169 colorTemperatureFilter, throttler); 170 mLoggingEnabled = false; 171 mEnabled = false; 172 mCallbacks = null; 173 mBrightnessSensor = brightnessSensor; 174 mBrightnessFilter = brightnessFilter; 175 mColorTemperatureSensor = colorTemperatureSensor; 176 mColorTemperatureFilter = colorTemperatureFilter; 177 mThrottler = throttler; 178 mLowLightAmbientColorTemperature = lowLightAmbientColorTemperature; 179 mHighLightAmbientColorTemperature = highLightAmbientColorTemperature; 180 mAmbientColorTemperature = -1.0f; 181 mPendingAmbientColorTemperature = -1.0f; 182 mLastAmbientColorTemperature = -1.0f; 183 mAmbientColorTemperatureHistory = new History(HISTORY_SIZE); 184 mAmbientColorTemperatureOverride = -1.0f; 185 186 try { 187 mLowLightAmbientBrightnessToBiasSpline = new Spline.LinearSpline( 188 lowLightAmbientBrightnesses, lowLightAmbientBiases); 189 } catch (Exception e) { 190 Slog.e(TAG, "failed to create low light ambient brightness to bias spline.", e); 191 mLowLightAmbientBrightnessToBiasSpline = null; 192 } 193 if (mLowLightAmbientBrightnessToBiasSpline != null) { 194 if (mLowLightAmbientBrightnessToBiasSpline.interpolate(0.0f) != 0.0f || 195 mLowLightAmbientBrightnessToBiasSpline.interpolate(Float.POSITIVE_INFINITY) 196 != 1.0f) { 197 Slog.d(TAG, "invalid low light ambient brightness to bias spline, " 198 + "bias must begin at 0.0 and end at 1.0."); 199 mLowLightAmbientBrightnessToBiasSpline = null; 200 } 201 } 202 203 try { 204 mHighLightAmbientBrightnessToBiasSpline = new Spline.LinearSpline( 205 highLightAmbientBrightnesses, highLightAmbientBiases); 206 } catch (Exception e) { 207 Slog.e(TAG, "failed to create high light ambient brightness to bias spline.", e); 208 mHighLightAmbientBrightnessToBiasSpline = null; 209 } 210 if (mHighLightAmbientBrightnessToBiasSpline != null) { 211 if (mHighLightAmbientBrightnessToBiasSpline.interpolate(0.0f) != 0.0f || 212 mHighLightAmbientBrightnessToBiasSpline.interpolate(Float.POSITIVE_INFINITY) 213 != 1.0f) { 214 Slog.d(TAG, "invalid high light ambient brightness to bias spline, " 215 + "bias must begin at 0.0 and end at 1.0."); 216 mHighLightAmbientBrightnessToBiasSpline = null; 217 } 218 } 219 220 if (mLowLightAmbientBrightnessToBiasSpline != null && 221 mHighLightAmbientBrightnessToBiasSpline != null) { 222 if (lowLightAmbientBrightnesses[lowLightAmbientBrightnesses.length - 1] > 223 highLightAmbientBrightnesses[0]) { 224 Slog.d(TAG, "invalid low light and high light ambient brightness to bias spline " 225 + "combination, defined domains must not intersect."); 226 mLowLightAmbientBrightnessToBiasSpline = null; 227 mHighLightAmbientBrightnessToBiasSpline = null; 228 } 229 } 230 231 try { 232 mAmbientToDisplayColorTemperatureSpline = new Spline.LinearSpline( 233 ambientColorTemperatures, displayColorTemperatures); 234 } catch (Exception e) { 235 Slog.e(TAG, "failed to create ambient to display color temperature spline.", e); 236 mAmbientToDisplayColorTemperatureSpline = null; 237 } 238 239 mColorDisplayServiceInternal = LocalServices.getService(ColorDisplayServiceInternal.class); 240 } 241 242 /** 243 * Enable/disable the controller. 244 * 245 * @param enabled 246 * Whether the controller should be on/off. 247 * 248 * @return Whether the method succeeded or not. 249 */ setEnabled(boolean enabled)250 public boolean setEnabled(boolean enabled) { 251 if (enabled) { 252 return enable(); 253 } else { 254 return disable(); 255 } 256 } 257 258 /** 259 * Set an object to call back to when the display color temperature should be updated. 260 * 261 * @param callbacks 262 * The object to call back to. 263 * 264 * @return Whether the method succeeded or not. 265 */ setCallbacks(Callbacks callbacks)266 public boolean setCallbacks(Callbacks callbacks) { 267 if (mCallbacks == callbacks) { 268 return false; 269 } 270 mCallbacks = callbacks; 271 return true; 272 } 273 274 /** 275 * Enable/disable logging. 276 * 277 * @param loggingEnabled 278 * Whether logging should be on/off. 279 * 280 * @return Whether the method succeeded or not. 281 */ setLoggingEnabled(boolean loggingEnabled)282 public boolean setLoggingEnabled(boolean loggingEnabled) { 283 if (mLoggingEnabled == loggingEnabled) { 284 return false; 285 } 286 mLoggingEnabled = loggingEnabled; 287 mBrightnessSensor.setLoggingEnabled(loggingEnabled); 288 mBrightnessFilter.setLoggingEnabled(loggingEnabled); 289 mColorTemperatureSensor.setLoggingEnabled(loggingEnabled); 290 mColorTemperatureFilter.setLoggingEnabled(loggingEnabled); 291 mThrottler.setLoggingEnabled(loggingEnabled); 292 return true; 293 } 294 295 /** 296 * Set the ambient color temperature override. 297 * 298 * This is only applied when the ambient color temperature changes or is updated (in which case 299 * it overrides the ambient color temperature estimate); in other words, it doesn't necessarily 300 * change the display color temperature immediately. 301 * 302 * @param ambientColorTemperatureOverride 303 * The ambient color temperature override. 304 * 305 * @return Whether the method succeeded or not. 306 */ setAmbientColorTemperatureOverride(float ambientColorTemperatureOverride)307 public boolean setAmbientColorTemperatureOverride(float ambientColorTemperatureOverride) { 308 if (mAmbientColorTemperatureOverride == ambientColorTemperatureOverride) { 309 return false; 310 } 311 mAmbientColorTemperatureOverride = ambientColorTemperatureOverride; 312 return true; 313 } 314 315 /** 316 * Dump the state. 317 * 318 * @param writer 319 * The writer used to dump the state. 320 */ dump(PrintWriter writer)321 public void dump(PrintWriter writer) { 322 writer.println("DisplayWhiteBalanceController"); 323 writer.println(" mLoggingEnabled=" + mLoggingEnabled); 324 writer.println(" mEnabled=" + mEnabled); 325 writer.println(" mCallbacks=" + mCallbacks); 326 mBrightnessSensor.dump(writer); 327 mBrightnessFilter.dump(writer); 328 mColorTemperatureSensor.dump(writer); 329 mColorTemperatureFilter.dump(writer); 330 mThrottler.dump(writer); 331 writer.println(" mLowLightAmbientColorTemperature=" + mLowLightAmbientColorTemperature); 332 writer.println(" mHighLightAmbientColorTemperature=" + mHighLightAmbientColorTemperature); 333 writer.println(" mAmbientColorTemperature=" + mAmbientColorTemperature); 334 writer.println(" mPendingAmbientColorTemperature=" + mPendingAmbientColorTemperature); 335 writer.println(" mLastAmbientColorTemperature=" + mLastAmbientColorTemperature); 336 writer.println(" mAmbientColorTemperatureHistory=" + mAmbientColorTemperatureHistory); 337 writer.println(" mAmbientColorTemperatureOverride=" + mAmbientColorTemperatureOverride); 338 writer.println(" mAmbientToDisplayColorTemperatureSpline=" 339 + mAmbientToDisplayColorTemperatureSpline); 340 writer.println(" mLowLightAmbientBrightnessToBiasSpline=" 341 + mLowLightAmbientBrightnessToBiasSpline); 342 writer.println(" mHighLightAmbientBrightnessToBiasSpline=" 343 + mHighLightAmbientBrightnessToBiasSpline); 344 } 345 346 @Override // AmbientSensor.AmbientBrightnessSensor.Callbacks onAmbientBrightnessChanged(float value)347 public void onAmbientBrightnessChanged(float value) { 348 final long time = System.currentTimeMillis(); 349 mBrightnessFilter.addValue(time, value); 350 updateAmbientColorTemperature(); 351 } 352 353 @Override // AmbientSensor.AmbientColorTemperatureSensor.Callbacks onAmbientColorTemperatureChanged(float value)354 public void onAmbientColorTemperatureChanged(float value) { 355 final long time = System.currentTimeMillis(); 356 mColorTemperatureFilter.addValue(time, value); 357 updateAmbientColorTemperature(); 358 } 359 360 /** 361 * Updates the ambient color temperature. 362 */ updateAmbientColorTemperature()363 public void updateAmbientColorTemperature() { 364 final long time = System.currentTimeMillis(); 365 float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time); 366 mLatestAmbientColorTemperature = ambientColorTemperature; 367 368 if (mAmbientToDisplayColorTemperatureSpline != null && ambientColorTemperature != -1.0f) { 369 ambientColorTemperature = 370 mAmbientToDisplayColorTemperatureSpline.interpolate(ambientColorTemperature); 371 } 372 373 float ambientBrightness = mBrightnessFilter.getEstimate(time); 374 mLatestAmbientBrightness = ambientBrightness; 375 376 if (ambientColorTemperature != -1.0f && 377 mLowLightAmbientBrightnessToBiasSpline != null) { 378 float bias = mLowLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness); 379 ambientColorTemperature = 380 bias * ambientColorTemperature + (1.0f - bias) 381 * mLowLightAmbientColorTemperature; 382 mLatestLowLightBias = bias; 383 } 384 if (ambientColorTemperature != -1.0f && 385 mHighLightAmbientBrightnessToBiasSpline != null) { 386 float bias = mHighLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness); 387 ambientColorTemperature = 388 (1.0f - bias) * ambientColorTemperature + bias 389 * mHighLightAmbientColorTemperature; 390 mLatestHighLightBias = bias; 391 } 392 393 if (mAmbientColorTemperatureOverride != -1.0f) { 394 if (mLoggingEnabled) { 395 Slog.d(TAG, "override ambient color temperature: " + ambientColorTemperature 396 + " => " + mAmbientColorTemperatureOverride); 397 } 398 ambientColorTemperature = mAmbientColorTemperatureOverride; 399 } 400 401 // When the display color temperature needs to be updated, we call DisplayPowerController to 402 // call our updateColorTemperature. The reason we don't call it directly is that we want 403 // all changes to the system to happen in a predictable order in DPC's main loop 404 // (updatePowerState). 405 if (ambientColorTemperature == -1.0f || mThrottler.throttle(ambientColorTemperature)) { 406 return; 407 } 408 409 if (mLoggingEnabled) { 410 Slog.d(TAG, "pending ambient color temperature: " + ambientColorTemperature); 411 } 412 mPendingAmbientColorTemperature = ambientColorTemperature; 413 if (mCallbacks != null) { 414 mCallbacks.updateWhiteBalance(); 415 } 416 } 417 418 /** 419 * Updates the display color temperature. 420 */ updateDisplayColorTemperature()421 public void updateDisplayColorTemperature() { 422 float ambientColorTemperature = -1.0f; 423 424 // If both the pending and the current ambient color temperatures are -1, it means the DWBC 425 // was just enabled, and we use the last ambient color temperature until new sensor events 426 // give us a better estimate. 427 if (mAmbientColorTemperature == -1.0f && mPendingAmbientColorTemperature == -1.0f) { 428 ambientColorTemperature = mLastAmbientColorTemperature; 429 } 430 431 // Otherwise, we use the pending ambient color temperature, but only if it's non-trivial 432 // and different than the current one. 433 if (mPendingAmbientColorTemperature != -1.0f 434 && mPendingAmbientColorTemperature != mAmbientColorTemperature) { 435 ambientColorTemperature = mPendingAmbientColorTemperature; 436 } 437 438 if (ambientColorTemperature == -1.0f) { 439 return; 440 } 441 442 mAmbientColorTemperature = ambientColorTemperature; 443 if (mLoggingEnabled) { 444 Slog.d(TAG, "ambient color temperature: " + mAmbientColorTemperature); 445 } 446 mPendingAmbientColorTemperature = -1.0f; 447 mAmbientColorTemperatureHistory.add(mAmbientColorTemperature); 448 Slog.d(TAG, "Display cct: " + mAmbientColorTemperature 449 + " Latest ambient cct: " + mLatestAmbientColorTemperature 450 + " Latest ambient lux: " + mLatestAmbientBrightness 451 + " Latest low light bias: " + mLatestLowLightBias 452 + " Latest high light bias: " + mLatestHighLightBias); 453 mColorDisplayServiceInternal.setDisplayWhiteBalanceColorTemperature( 454 (int) mAmbientColorTemperature); 455 mLastAmbientColorTemperature = mAmbientColorTemperature; 456 } 457 458 /** 459 * The DisplayWhiteBalanceController decouples itself from its parent (DisplayPowerController) 460 * by providing this interface to implement (and a method to set its callbacks object), and 461 * calling these methods. 462 */ 463 public interface Callbacks { 464 465 /** 466 * Called whenever the display white-balance state has changed. 467 * 468 * Usually, this means the estimated ambient color temperature has changed enough, and the 469 * display color temperature should be updated; but it is also called if settings change. 470 */ updateWhiteBalance()471 void updateWhiteBalance(); 472 } 473 validateArguments(AmbientSensor.AmbientBrightnessSensor brightnessSensor, AmbientFilter brightnessFilter, AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor, AmbientFilter colorTemperatureFilter, DisplayWhiteBalanceThrottler throttler)474 private void validateArguments(AmbientSensor.AmbientBrightnessSensor brightnessSensor, 475 AmbientFilter brightnessFilter, 476 AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor, 477 AmbientFilter colorTemperatureFilter, 478 DisplayWhiteBalanceThrottler throttler) { 479 Objects.requireNonNull(brightnessSensor, "brightnessSensor must not be null"); 480 Objects.requireNonNull(brightnessFilter, "brightnessFilter must not be null"); 481 Objects.requireNonNull(colorTemperatureSensor, 482 "colorTemperatureSensor must not be null"); 483 Objects.requireNonNull(colorTemperatureFilter, 484 "colorTemperatureFilter must not be null"); 485 Objects.requireNonNull(throttler, "throttler cannot be null"); 486 } 487 enable()488 private boolean enable() { 489 if (mEnabled) { 490 return false; 491 } 492 if (mLoggingEnabled) { 493 Slog.d(TAG, "enabling"); 494 } 495 mEnabled = true; 496 mBrightnessSensor.setEnabled(true); 497 mColorTemperatureSensor.setEnabled(true); 498 return true; 499 } 500 disable()501 private boolean disable() { 502 if (!mEnabled) { 503 return false; 504 } 505 if (mLoggingEnabled) { 506 Slog.d(TAG, "disabling"); 507 } 508 mEnabled = false; 509 mBrightnessSensor.setEnabled(false); 510 mBrightnessFilter.clear(); 511 mColorTemperatureSensor.setEnabled(false); 512 mColorTemperatureFilter.clear(); 513 mThrottler.clear(); 514 mAmbientColorTemperature = -1.0f; 515 mPendingAmbientColorTemperature = -1.0f; 516 mColorDisplayServiceInternal.resetDisplayWhiteBalanceColorTemperature(); 517 return true; 518 } 519 520 } 521