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