• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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;
18 
19 import static android.text.TextUtils.formatSimple;
20 
21 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
22 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
23 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
24 
25 import android.annotation.Nullable;
26 import android.content.Context;
27 import android.content.pm.ApplicationInfo;
28 import android.content.res.TypedArray;
29 import android.hardware.display.BrightnessConfiguration;
30 import android.hardware.display.BrightnessCorrection;
31 import android.os.PowerManager;
32 import android.os.UserHandle;
33 import android.provider.Settings;
34 import android.util.LongArray;
35 import android.util.MathUtils;
36 import android.util.Pair;
37 import android.util.Slog;
38 import android.util.Spline;
39 
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.internal.display.BrightnessUtils;
42 import com.android.internal.util.Preconditions;
43 import com.android.server.display.utils.Plog;
44 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
45 
46 import java.io.PrintWriter;
47 import java.text.SimpleDateFormat;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Date;
51 import java.util.List;
52 import java.util.Locale;
53 import java.util.Objects;
54 
55 /**
56  * A utility to map from an ambient brightness to a display's "backlight" brightness based on the
57  * available display information and brightness configuration.
58  *
59  * Note that without a mapping from the nits to a display backlight level, any
60  * {@link BrightnessConfiguration}s that are set are just ignored.
61  */
62 public abstract class BrightnessMappingStrategy {
63     private static final String TAG = "BrightnessMappingStrategy";
64 
65     public static final float INVALID_LUX = -1;
66     public static final float INVALID_NITS = -1;
67 
68     private static final float LUX_GRAD_SMOOTHING = 0.25f;
69     private static final float MAX_GRAD = 1.0f;
70     private static final float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f;
71 
72     // Constant that ensures that each step of the curve can increase by up to at least
73     // MIN_PERMISSABLE_INCREASE. Otherwise when the brightness is set to 0, the curve will never
74     // increase and will always be 0.
75     private static final float MIN_PERMISSABLE_INCREASE =  0.004f;
76 
77     protected boolean mLoggingEnabled;
78 
79     private static final Plog PLOG = Plog.createSystemPlog(TAG);
80 
81     /**
82      * Creates a BrightnessMapping strategy. We do not create a simple mapping strategy for idle
83      * mode.
84      *
85      * @param context
86      * @param displayDeviceConfig
87      * @param mode The auto-brightness mode. Different modes use different brightness curves
88      * @param displayWhiteBalanceController
89      * @return the BrightnessMappingStrategy
90      */
91     @Nullable
create(Context context, DisplayDeviceConfig displayDeviceConfig, @AutomaticBrightnessController.AutomaticBrightnessMode int mode, @Nullable DisplayWhiteBalanceController displayWhiteBalanceController)92     static BrightnessMappingStrategy create(Context context,
93             DisplayDeviceConfig displayDeviceConfig,
94             @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
95             @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
96 
97         // Display independent, mode dependent values
98         float[] brightnessLevelsNits = null;
99         float[] brightnessLevels = null;
100         float[] luxLevels = null;
101         int preset = Settings.System.getIntForUser(context.getContentResolver(),
102                 Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
103                 Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
104         switch (mode) {
105             case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
106                 brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
107                 luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
108                 brightnessLevels =
109                         displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
110             }
111             case AUTO_BRIGHTNESS_MODE_IDLE -> {
112                 brightnessLevelsNits = getFloatArray(context.getResources().obtainTypedArray(
113                         com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle));
114                 luxLevels = getLuxLevels(context.getResources().getIntArray(
115                         com.android.internal.R.array.config_autoBrightnessLevelsIdle));
116             }
117             case AUTO_BRIGHTNESS_MODE_DOZE -> {
118                 luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
119                 brightnessLevels =
120                         displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
121             }
122         }
123 
124         // Display independent, mode independent values
125         float autoBrightnessAdjustmentMaxGamma = context.getResources().getFraction(
126                 com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
127                 1, 1);
128         long shortTermModelTimeout = context.getResources().getInteger(
129                 com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout);
130 
131         // Display dependent values - used for physical mapping strategy nits -> brightness
132         final float[] nitsRange = displayDeviceConfig.getNits();
133         final float[] brightnessRange = displayDeviceConfig.getBrightness();
134 
135         if (isValidMapping(nitsRange, brightnessRange)
136                 && isValidMapping(luxLevels, brightnessLevelsNits)) {
137             BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
138                     luxLevels, brightnessLevelsNits);
139             builder.setShortTermModelTimeoutMillis(shortTermModelTimeout);
140             builder.setShortTermModelLowerLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
141             builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
142             return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange,
143                     autoBrightnessAdjustmentMaxGamma, mode, displayWhiteBalanceController);
144         } else if (isValidMapping(luxLevels, brightnessLevels)) {
145             return new SimpleMappingStrategy(luxLevels, brightnessLevels,
146                     autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout, mode);
147         } else {
148             return null;
149         }
150     }
151 
getLuxLevels(int[] lux)152     private static float[] getLuxLevels(int[] lux) {
153         // The first control point is implicit and always at 0 lux.
154         float[] levels = new float[lux.length + 1];
155         for (int i = 0; i < lux.length; i++) {
156             levels[i + 1] = (float) lux[i];
157         }
158         return levels;
159     }
160 
161     /**
162      * Extracts a float array from the specified {@link TypedArray}.
163      *
164      * @param array The array to convert.
165      * @return the given array as a float array.
166      */
getFloatArray(TypedArray array)167     public static float[] getFloatArray(TypedArray array) {
168         final int N = array.length();
169         float[] vals = new float[N];
170         for (int i = 0; i < N; i++) {
171             vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
172         }
173         array.recycle();
174         return vals;
175     }
176 
isValidMapping(float[] x, float[] y)177     private static boolean isValidMapping(float[] x, float[] y) {
178         if (x == null || y == null || x.length == 0 || y.length == 0) {
179             return false;
180         }
181         if (x.length != y.length) {
182             return false;
183         }
184         final int N = x.length;
185         float prevX = x[0];
186         float prevY = y[0];
187         if (prevX < 0 || prevY < 0 || Float.isNaN(prevX) || Float.isNaN(prevY)) {
188             return false;
189         }
190         for (int i = 1; i < N; i++) {
191             if (prevX >= x[i] || prevY > y[i]) {
192                 return false;
193             }
194             if (Float.isNaN(x[i]) || Float.isNaN(y[i])) {
195                 return false;
196             }
197             prevX = x[i];
198             prevY = y[i];
199         }
200         return true;
201     }
202 
isValidMapping(float[] x, int[] y)203     private static boolean isValidMapping(float[] x, int[] y) {
204         if (x == null || y == null || x.length == 0 || y.length == 0) {
205             return false;
206         }
207         if (x.length != y.length) {
208             return false;
209         }
210         final int N = x.length;
211         float prevX = x[0];
212         int prevY = y[0];
213         if (prevX < 0 || prevY < 0 || Float.isNaN(prevX)) {
214             return false;
215         }
216         for (int i = 1; i < N; i++) {
217             if (prevX >= x[i] || prevY > y[i]) {
218                 return false;
219             }
220             if (Float.isNaN(x[i])) {
221                 return false;
222             }
223             prevX = x[i];
224             prevY = y[i];
225         }
226         return true;
227     }
228 
229     /**
230      * Enable/disable logging.
231      *
232      * @param loggingEnabled
233      *      Whether logging should be on/off.
234      *
235      * @return Whether the method succeeded or not.
236      */
setLoggingEnabled(boolean loggingEnabled)237     public boolean setLoggingEnabled(boolean loggingEnabled) {
238         if (mLoggingEnabled == loggingEnabled) {
239             return false;
240         }
241         mLoggingEnabled = loggingEnabled;
242         return true;
243     }
244 
245     /**
246      * Sets the {@link BrightnessConfiguration}.
247      *
248      * @param config The new configuration. If {@code null} is passed, the default configuration is
249      *               used.
250      * @return Whether the brightness configuration has changed.
251      */
setBrightnessConfiguration(@ullable BrightnessConfiguration config)252     public abstract boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config);
253 
254     /**
255      * Gets the current {@link BrightnessConfiguration}.
256      */
257     @Nullable
getBrightnessConfiguration()258     public abstract BrightnessConfiguration getBrightnessConfiguration();
259 
260     /**
261      * Returns the desired brightness of the display based on the current ambient lux, including
262      * any context-related corrections.
263      *
264      * The returned brightness will be in the range [0, 1.0], where 1.0 is the display at max
265      * brightness and 0 is the display at minimum brightness.
266      *
267      * @param lux The current ambient brightness in lux.
268      * @param packageName the foreground app package name.
269      * @param category the foreground app package category.
270      * @return The desired brightness of the display normalized to the range [0, 1.0].
271      */
getBrightness(float lux, String packageName, @ApplicationInfo.Category int category)272     public abstract float getBrightness(float lux, String packageName,
273             @ApplicationInfo.Category int category);
274 
275     /**
276      * Returns the desired brightness of the display based on the current ambient lux.
277      *
278      * The returned brightness wil be in the range [0, 1.0], where 1.0 is the display at max
279      * brightness and 0 is the display at minimum brightness.
280      *
281      * @param lux The current ambient brightness in lux.
282      *
283      * @return The desired brightness of the display normalized to the range [0, 1.0].
284      */
getBrightness(float lux)285     public float getBrightness(float lux) {
286         return getBrightness(lux, null /* packageName */, ApplicationInfo.CATEGORY_UNDEFINED);
287     }
288 
289     /**
290      * Returns the current auto-brightness adjustment.
291      *
292      * The returned adjustment is a value in the range [-1.0, 1.0] such that
293      * {@code config_autoBrightnessAdjustmentMaxGamma<sup>-adjustment</sup>} is used to gamma
294      * correct the brightness curve.
295      */
getAutoBrightnessAdjustment()296     public abstract float getAutoBrightnessAdjustment();
297 
298     /**
299      * Sets the auto-brightness adjustment.
300      *
301      * @param adjustment The desired auto-brightness adjustment.
302      * @return Whether the auto-brightness adjustment has changed.
303      *
304      * @Deprecated The auto-brightness adjustment should not be set directly, but rather inferred
305      * from user data points.
306      */
setAutoBrightnessAdjustment(float adjustment)307     public abstract boolean setAutoBrightnessAdjustment(float adjustment);
308 
309     /**
310      * Converts the provided brightness value to nits if possible.
311      *
312      * Returns {@link INVALID_NITS} if there's no available mapping for the brightness to nits.
313      */
convertToNits(float brightness)314     public abstract float convertToNits(float brightness);
315 
316     /**
317      * Converts the provided brightness value to nits if possible. Adjustments, such as RBC are
318      * applied.
319      *
320      * Returns {@link INVALID_NITS} if there's no available mapping for the brightness to nits.
321      */
convertToAdjustedNits(float brightness)322     public abstract float convertToAdjustedNits(float brightness);
323 
324     /**
325      * Converts the provided nit value to a float scale value if possible.
326      *
327      * Returns {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if there's no available mapping for
328      * the nits to float scale.
329      */
getBrightnessFromNits(float nits)330     public abstract float getBrightnessFromNits(float nits);
331 
332     /**
333      * Adds a user interaction data point to the brightness mapping.
334      *
335      * This data point <b>must</b> exist on the brightness curve as a result of this call. This is
336      * so that the next time we come to query what the screen brightness should be, we get what the
337      * user requested rather than immediately changing to some other value.
338      *
339      * Currently, we only keep track of one of these at a time to constrain what can happen to the
340      * curve.
341      */
addUserDataPoint(float lux, float brightness)342     public abstract void addUserDataPoint(float lux, float brightness);
343 
344     /**
345      * Removes any short term adjustments made to the curve from user interactions.
346      *
347      * Note that this does *not* reset the mapping to its initial state, any brightness
348      * configurations that have been applied will continue to be in effect. This solely removes the
349      * effects of user interactions on the model.
350      */
clearUserDataPoints()351     public abstract void clearUserDataPoints();
352 
353     /** @return True if there are any short term adjustments applied to the curve. */
hasUserDataPoints()354     public abstract boolean hasUserDataPoints();
355 
356     /** @return True if the current brightness configuration is the default one. */
isDefaultConfig()357     public abstract boolean isDefaultConfig();
358 
359     /** @return The default brightness configuration. */
getDefaultConfig()360     public abstract BrightnessConfiguration getDefaultConfig();
361 
362     /** Recalculates the backlight-to-nits and nits-to-backlight splines. */
recalculateSplines(boolean applyAdjustment, float[] adjustment)363     public abstract void recalculateSplines(boolean applyAdjustment, float[] adjustment);
364 
365     /**
366      * Returns the timeout, in milliseconds for the short term model
367      *
368      * Timeout after which we remove the effects any user interactions might've had on the
369      * brightness mapping. This timeout doesn't start until we transition to a non-interactive
370      * display policy so that we don't reset while users are using their devices, but also so that
371      * we don't erroneously keep the short-term model if the device is dozing but the
372      * display is fully on.
373      *
374      * This timeout is also used when the device switches from interactive screen brightness mode
375      * to idle screen brightness mode, to preserve the user's preference when they resume usage of
376      * the device, within the specified timeframe.
377      */
getShortTermModelTimeout()378     public abstract long getShortTermModelTimeout();
379 
380     /**
381      * Prints dump output for display dumpsys.
382      */
dump(PrintWriter pw, float hbmTransition)383     public abstract void dump(PrintWriter pw, float hbmTransition);
384 
getUserLux()385     abstract float getUserLux();
386 
getUserBrightness()387     abstract float getUserBrightness();
388 
389     /**
390      * @return The auto-brightness mode of this mapping strategy. Different modes use different
391      * brightness curves.
392      */
393     @AutomaticBrightnessController.AutomaticBrightnessMode
getMode()394     abstract int getMode();
395 
396     /**
397      * Check if the short term model should be reset given the anchor lux the last
398      * brightness change was made at and the current ambient lux.
399      */
shouldResetShortTermModel(float ambientLux, float shortTermModelAnchor)400     public boolean shouldResetShortTermModel(float ambientLux, float shortTermModelAnchor) {
401         BrightnessConfiguration config = getBrightnessConfiguration();
402         float minThresholdRatio = SHORT_TERM_MODEL_THRESHOLD_RATIO;
403         float maxThresholdRatio = SHORT_TERM_MODEL_THRESHOLD_RATIO;
404         if (config != null) {
405             if (!Float.isNaN(config.getShortTermModelLowerLuxMultiplier())) {
406                 minThresholdRatio = config.getShortTermModelLowerLuxMultiplier();
407             }
408             if (!Float.isNaN(config.getShortTermModelUpperLuxMultiplier())) {
409                 maxThresholdRatio = config.getShortTermModelUpperLuxMultiplier();
410             }
411         }
412         final float minAmbientLux =
413                 shortTermModelAnchor - shortTermModelAnchor * minThresholdRatio;
414         final float maxAmbientLux =
415                 shortTermModelAnchor + shortTermModelAnchor * maxThresholdRatio;
416         if (minAmbientLux < ambientLux && ambientLux <= maxAmbientLux) {
417             if (mLoggingEnabled) {
418                 Slog.d(TAG, "ShortTermModel: re-validate user data, ambient lux is "
419                         + minAmbientLux + " < " + ambientLux + " < " + maxAmbientLux);
420             }
421             return false;
422         } else {
423             Slog.d(TAG, "ShortTermModel: reset data, ambient lux is " + ambientLux
424                     + "(" + minAmbientLux + ", " + maxAmbientLux + ")");
425             return true;
426         }
427     }
428 
insertControlPoint( float[] luxLevels, float[] brightnessLevels, float lux, float brightness)429     private Pair<float[], float[]> insertControlPoint(
430             float[] luxLevels, float[] brightnessLevels, float lux, float brightness) {
431         final int idx = findInsertionPoint(luxLevels, lux);
432         final float[] newLuxLevels;
433         final float[] newBrightnessLevels;
434         if (idx == luxLevels.length) {
435             newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
436             newBrightnessLevels  = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
437             newLuxLevels[idx] = lux;
438             newBrightnessLevels[idx] = brightness;
439         } else if (luxLevels[idx] == lux) {
440             newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length);
441             newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length);
442             newBrightnessLevels[idx] = brightness;
443         } else {
444             newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
445             System.arraycopy(newLuxLevels, idx, newLuxLevels, idx+1, luxLevels.length - idx);
446             newLuxLevels[idx] = lux;
447             newBrightnessLevels  = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
448             System.arraycopy(newBrightnessLevels, idx, newBrightnessLevels, idx+1,
449                     brightnessLevels.length - idx);
450             newBrightnessLevels[idx] = brightness;
451         }
452         smoothCurve(newLuxLevels, newBrightnessLevels, idx);
453         return Pair.create(newLuxLevels, newBrightnessLevels);
454     }
455 
456     /**
457      * Returns the index of the first value that's less than or equal to {@code val}.
458      *
459      * This assumes that {@code arr} is sorted. If all values in {@code arr} are greater
460      * than val, then it will return the length of arr as the insertion point.
461      */
findInsertionPoint(float[] arr, float val)462     private int findInsertionPoint(float[] arr, float val) {
463         for (int i = 0; i < arr.length; i++) {
464             if (val <= arr[i]) {
465                 return i;
466             }
467         }
468         return arr.length;
469     }
470 
smoothCurve(float[] lux, float[] brightness, int idx)471     private void smoothCurve(float[] lux, float[] brightness, int idx) {
472         if (mLoggingEnabled) {
473             PLOG.logCurve("unsmoothed curve", lux, brightness);
474         }
475         float prevLux = lux[idx];
476         float prevBrightness = brightness[idx];
477         // Smooth curve for data points above the newly introduced point
478         for (int i = idx+1; i < lux.length; i++) {
479             float currLux = lux[i];
480             float currBrightness = brightness[i];
481             float maxBrightness = MathUtils.max(
482                     prevBrightness * permissibleRatio(currLux, prevLux),
483                     prevBrightness + MIN_PERMISSABLE_INCREASE);
484             float newBrightness = MathUtils.constrain(
485                     currBrightness, prevBrightness, maxBrightness);
486             if (newBrightness == currBrightness) {
487                 break;
488             }
489             prevLux = currLux;
490             prevBrightness = newBrightness;
491             brightness[i] = newBrightness;
492         }
493         // Smooth curve for data points below the newly introduced point
494         prevLux = lux[idx];
495         prevBrightness = brightness[idx];
496         for (int i = idx-1; i >= 0; i--) {
497             float currLux = lux[i];
498             float currBrightness = brightness[i];
499             float minBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
500             float newBrightness = MathUtils.constrain(
501                     currBrightness, minBrightness, prevBrightness);
502             if (newBrightness == currBrightness) {
503                 break;
504             }
505             prevLux = currLux;
506             prevBrightness = newBrightness;
507             brightness[i] = newBrightness;
508         }
509         if (mLoggingEnabled) {
510             PLOG.logCurve("smoothed curve", lux, brightness);
511         }
512     }
513 
permissibleRatio(float currLux, float prevLux)514     private float permissibleRatio(float currLux, float prevLux) {
515         return MathUtils.pow((currLux + LUX_GRAD_SMOOTHING)
516                 / (prevLux + LUX_GRAD_SMOOTHING), MAX_GRAD);
517     }
518 
inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness, float currentBrightness)519     protected float inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness,
520             float currentBrightness) {
521         float adjustment = 0;
522         float gamma = Float.NaN;
523         // Extreme edge cases: use a simpler heuristic, as proper gamma correction around the edges
524         // affects the curve rather drastically.
525         if (currentBrightness <= 0.1f || currentBrightness >= 0.9f) {
526             adjustment = (desiredBrightness - currentBrightness);
527         // Edge case: darkest adjustment possible.
528         } else if (desiredBrightness == 0) {
529             adjustment = -1;
530         // Edge case: brightest adjustment possible.
531         } else if (desiredBrightness == 1) {
532             adjustment = +1;
533         } else {
534             // current^gamma = desired => gamma = log[current](desired)
535             gamma = MathUtils.log(desiredBrightness) / MathUtils.log(currentBrightness);
536             // max^-adjustment = gamma => adjustment = -log[max](gamma)
537             adjustment = -MathUtils.log(gamma) / MathUtils.log(maxGamma);
538         }
539         adjustment = MathUtils.constrain(adjustment, -1, +1);
540         if (mLoggingEnabled) {
541             Slog.d(TAG, "inferAutoBrightnessAdjustment: " + maxGamma + "^" + -adjustment + "=" +
542                     MathUtils.pow(maxGamma, -adjustment) + " == " + gamma);
543             Slog.d(TAG, "inferAutoBrightnessAdjustment: " + currentBrightness + "^" + gamma + "=" +
544                     MathUtils.pow(currentBrightness, gamma) + " == " + desiredBrightness);
545         }
546         return adjustment;
547     }
548 
getAdjustedCurve(float[] lux, float[] brightness, float userLux, float userBrightness, float adjustment, float maxGamma)549     protected Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness,
550             float userLux, float userBrightness, float adjustment, float maxGamma) {
551         float[] newLux = lux;
552         float[] newBrightness = Arrays.copyOf(brightness, brightness.length);
553         if (mLoggingEnabled) {
554             PLOG.logCurve("unadjusted curve", newLux, newBrightness);
555         }
556         adjustment = MathUtils.constrain(adjustment, -1, 1);
557         float gamma = MathUtils.pow(maxGamma, -adjustment);
558         if (mLoggingEnabled) {
559             Slog.d(TAG, "getAdjustedCurve: " + maxGamma + "^" + -adjustment + "=" +
560                     MathUtils.pow(maxGamma, -adjustment) + " == " + gamma);
561         }
562         if (gamma != 1) {
563             for (int i = 0; i < newBrightness.length; i++) {
564                 newBrightness[i] = MathUtils.pow(newBrightness[i], gamma);
565             }
566         }
567         if (mLoggingEnabled) {
568             PLOG.logCurve("gamma adjusted curve", newLux, newBrightness);
569         }
570         if (userLux != INVALID_LUX) {
571             Pair<float[], float[]> curve = insertControlPoint(newLux, newBrightness, userLux,
572                     userBrightness);
573             newLux = curve.first;
574             newBrightness = curve.second;
575             if (mLoggingEnabled) {
576                 PLOG.logCurve("gamma and user adjusted curve", newLux, newBrightness);
577                 // This is done for comparison.
578                 curve = insertControlPoint(lux, brightness, userLux, userBrightness);
579                 PLOG.logCurve("user adjusted curve", curve.first ,curve.second);
580             }
581         }
582         return Pair.create(newLux, newBrightness);
583     }
584 
585     /**
586      * A {@link BrightnessMappingStrategy} that maps from ambient room brightness directly to the
587      * backlight of the display.
588      *
589      * Since we don't have information about the display's physical brightness, any brightness
590      * configurations that are set are just ignored.
591      */
592     private static class SimpleMappingStrategy extends BrightnessMappingStrategy {
593         // Lux control points
594         private final float[] mLux;
595         // Brightness control points normalized to [0, 1]
596         private final float[] mBrightness;
597 
598         @AutomaticBrightnessController.AutomaticBrightnessMode
599         private final int mMode;
600 
601         private Spline mSpline;
602         private float mMaxGamma;
603         private float mAutoBrightnessAdjustment;
604         private float mUserLux;
605         private float mUserBrightness;
606         private long mShortTermModelTimeout;
607 
SimpleMappingStrategy(float[] lux, float[] brightness, float maxGamma, long timeout, @AutomaticBrightnessController.AutomaticBrightnessMode int mode)608         private SimpleMappingStrategy(float[] lux, float[] brightness, float maxGamma,
609                 long timeout, @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
610             Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
611                     "Lux and brightness arrays must not be empty!");
612             Preconditions.checkArgument(lux.length == brightness.length,
613                     "Lux and brightness arrays must be the same length!");
614             Preconditions.checkArrayElementsInRange(lux, 0, Float.MAX_VALUE, "lux");
615             Preconditions.checkArrayElementsInRange(brightness,
616                     0, Integer.MAX_VALUE, "brightness");
617 
618             final int N = brightness.length;
619             mLux = new float[N];
620             mBrightness = new float[N];
621             for (int i = 0; i < N; i++) {
622                 mLux[i] = lux[i];
623                 mBrightness[i] = brightness[i];
624             }
625 
626             mMaxGamma = maxGamma;
627             mAutoBrightnessAdjustment = 0;
628             mUserLux = INVALID_LUX;
629             mUserBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
630             if (mLoggingEnabled) {
631                 PLOG.start("simple mapping strategy");
632             }
633             computeSpline();
634             mShortTermModelTimeout = timeout;
635             mMode = mode;
636         }
637 
638         @Override
getShortTermModelTimeout()639         public long getShortTermModelTimeout() {
640             return mShortTermModelTimeout;
641         }
642 
643         @Override
setBrightnessConfiguration(@ullable BrightnessConfiguration config)644         public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
645             return false;
646         }
647 
648         @Override
getBrightnessConfiguration()649         public BrightnessConfiguration getBrightnessConfiguration() {
650             return null;
651         }
652 
653         @Override
getBrightness(float lux, String packageName, @ApplicationInfo.Category int category)654         public float getBrightness(float lux, String packageName,
655                 @ApplicationInfo.Category int category) {
656             return mSpline.interpolate(lux);
657         }
658 
659         @Override
getAutoBrightnessAdjustment()660         public float getAutoBrightnessAdjustment() {
661             return mAutoBrightnessAdjustment;
662         }
663 
664         @Override
setAutoBrightnessAdjustment(float adjustment)665         public boolean setAutoBrightnessAdjustment(float adjustment) {
666             adjustment = MathUtils.constrain(adjustment, -1, 1);
667             if (adjustment == mAutoBrightnessAdjustment) {
668                 return false;
669             }
670             if (mLoggingEnabled) {
671                 Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " +
672                         adjustment);
673                 PLOG.start("auto-brightness adjustment");
674             }
675             mAutoBrightnessAdjustment = adjustment;
676             computeSpline();
677             return true;
678         }
679 
680         @Override
convertToNits(float brightness)681         public float convertToNits(float brightness) {
682             return INVALID_NITS;
683         }
684 
685         @Override
convertToAdjustedNits(float brightness)686         public float convertToAdjustedNits(float brightness) {
687             return INVALID_NITS;
688         }
689 
690         @Override
getBrightnessFromNits(float nits)691         public float getBrightnessFromNits(float nits) {
692             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
693         }
694 
695         @Override
addUserDataPoint(float lux, float brightness)696         public void addUserDataPoint(float lux, float brightness) {
697             float unadjustedBrightness = getUnadjustedBrightness(lux);
698             if (mLoggingEnabled) {
699                 Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")");
700                 PLOG.start("add user data point")
701                         .logPoint("user data point", lux, brightness)
702                         .logPoint("current brightness", lux, unadjustedBrightness);
703             }
704             float adjustment = inferAutoBrightnessAdjustment(mMaxGamma,
705                     brightness /* desiredBrightness */,
706                     unadjustedBrightness /* currentBrightness */);
707             if (mLoggingEnabled) {
708                 Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " +
709                         adjustment);
710             }
711             mAutoBrightnessAdjustment = adjustment;
712             mUserLux = lux;
713             mUserBrightness = brightness;
714             computeSpline();
715         }
716 
717         @Override
clearUserDataPoints()718         public void clearUserDataPoints() {
719             if (mUserLux != INVALID_LUX) {
720                 if (mLoggingEnabled) {
721                     Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0");
722                     PLOG.start("clear user data points")
723                             .logPoint("user data point", mUserLux, mUserBrightness);
724                 }
725                 mAutoBrightnessAdjustment = 0;
726                 mUserLux = INVALID_LUX;
727                 mUserBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
728                 computeSpline();
729             }
730         }
731 
732         @Override
hasUserDataPoints()733         public boolean hasUserDataPoints() {
734             return mUserLux != INVALID_LUX;
735         }
736 
737         @Override
isDefaultConfig()738         public boolean isDefaultConfig() {
739             return true;
740         }
741 
742         @Override
getDefaultConfig()743         public BrightnessConfiguration getDefaultConfig() {
744             return null;
745         }
746 
747         @Override
recalculateSplines(boolean applyAdjustment, float[] adjustment)748         public void recalculateSplines(boolean applyAdjustment, float[] adjustment) {
749             // Do nothing.
750         }
751 
752         @Override
dump(PrintWriter pw, float hbmTransition)753         public void dump(PrintWriter pw, float hbmTransition) {
754             pw.println("SimpleMappingStrategy");
755             pw.println("  mSpline=" + mSpline);
756             pw.println("  mMaxGamma=" + mMaxGamma);
757             pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
758             pw.println("  mUserLux=" + mUserLux);
759             pw.println("  mUserBrightness=" + mUserBrightness);
760             pw.println("  mShortTermModelTimeout=" + mShortTermModelTimeout);
761         }
762 
763         @Override
getMode()764         int getMode() {
765             return mMode;
766         }
767 
768         @Override
getUserLux()769         float getUserLux() {
770             return mUserLux;
771         }
772 
773         @Override
getUserBrightness()774         float getUserBrightness() {
775             return mUserBrightness;
776         }
777 
computeSpline()778         private void computeSpline() {
779             Pair<float[], float[]> curve = getAdjustedCurve(mLux, mBrightness, mUserLux,
780                     mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
781             mSpline = Spline.createSpline(curve.first, curve.second);
782         }
783 
getUnadjustedBrightness(float lux)784         private float getUnadjustedBrightness(float lux) {
785             Spline spline = Spline.createSpline(mLux, mBrightness);
786             return spline.interpolate(lux);
787         }
788     }
789 
790     /** A {@link BrightnessMappingStrategy} that maps from ambient room brightness to the physical
791      * range of the display, rather than to the range of the backlight control (typically 0-255).
792      *
793      * By mapping through the physical brightness, the curve becomes portable across devices and
794      * gives us more resolution in the resulting mapping.
795      */
796     @VisibleForTesting
797     static class PhysicalMappingStrategy extends BrightnessMappingStrategy {
798         // The current brightness configuration.
799         private BrightnessConfiguration mConfig;
800 
801         // A spline mapping from the current ambient light in lux to the desired display brightness
802         // in nits.
803         private Spline mBrightnessSpline;
804 
805         // A spline mapping from nits to the corresponding brightness value, normalized to the range
806         // [0, 1.0].
807         private Spline mNitsToBrightnessSpline;
808 
809         // A spline mapping from the system brightness value, normalized to the range [0, 1.0], to
810         // a brightness in nits.
811         private Spline mBrightnessToNitsSpline;
812 
813         // A spline mapping from nits with adjustments applied to the corresponding brightness
814         // value, normalized to the range [0, 1.0].
815         private Spline mAdjustedNitsToBrightnessSpline;
816 
817         // A spline mapping from the system brightness value, normalized to the range [0, 1.0], to
818         // a brightness in nits with adjustments applied.
819         private Spline mBrightnessToAdjustedNitsSpline;
820 
821         // The default brightness configuration.
822         private final BrightnessConfiguration mDefaultConfig;
823 
824         private final float[] mNits;
825         private final float[] mBrightness;
826 
827         private boolean mBrightnessRangeAdjustmentApplied;
828 
829         private final float mMaxGamma;
830         private float mAutoBrightnessAdjustment;
831         private float mUserLux;
832         private float mUserBrightness;
833 
834         @Nullable
835         private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
836 
837         @AutomaticBrightnessController.AutomaticBrightnessMode
838         private final int mMode;
839 
840         // Previous short-term models and the times that they were computed stored for debugging
841         // purposes
842         private List<Spline> mPreviousBrightnessSplines = new ArrayList<>();
843         private LongArray mBrightnessSplineChangeTimes = new LongArray();
844         private static final int NO_OF_PREVIOUS_CONFIGS_TO_LOG = 5;
845         private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
846 
PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits, float[] brightness, float maxGamma, @AutomaticBrightnessController.AutomaticBrightnessMode int mode, @Nullable DisplayWhiteBalanceController displayWhiteBalanceController)847         public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
848                 float[] brightness, float maxGamma,
849                 @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
850                 @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
851 
852             Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
853                     "Nits and brightness arrays must not be empty!");
854 
855             Preconditions.checkArgument(nits.length == brightness.length,
856                     "Nits and brightness arrays must be the same length!");
857             Objects.requireNonNull(config);
858             Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits");
859             Preconditions.checkArrayElementsInRange(brightness,
860                     PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, "brightness");
861 
862             mMode = mode;
863             mMaxGamma = maxGamma;
864             mAutoBrightnessAdjustment = 0;
865             mUserLux = INVALID_LUX;
866             mUserBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
867             mDisplayWhiteBalanceController = displayWhiteBalanceController;
868 
869             mNits = nits;
870             mBrightness = brightness;
871             computeNitsBrightnessSplines(mNits);
872             mAdjustedNitsToBrightnessSpline = mNitsToBrightnessSpline;
873             mBrightnessToAdjustedNitsSpline = mBrightnessToNitsSpline;
874 
875             mDefaultConfig = config;
876             if (mLoggingEnabled) {
877                 PLOG.start("physical mapping strategy");
878             }
879             mConfig = config;
880             computeSpline();
881         }
882 
883         @Override
getShortTermModelTimeout()884         public long getShortTermModelTimeout() {
885             if (mConfig.getShortTermModelTimeoutMillis() >= 0) {
886                 return mConfig.getShortTermModelTimeoutMillis();
887             } else {
888                 return mDefaultConfig.getShortTermModelTimeoutMillis();
889             }
890         }
891 
892         @Override
setBrightnessConfiguration(@ullable BrightnessConfiguration config)893         public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
894             if (config == null) {
895                 config = mDefaultConfig;
896             }
897             if (config.equals(mConfig)) {
898                 return false;
899             }
900             if (mLoggingEnabled) {
901                 PLOG.start("brightness configuration");
902             }
903             mConfig = config;
904             computeSpline();
905             return true;
906         }
907 
908         @Override
getBrightnessConfiguration()909         public BrightnessConfiguration getBrightnessConfiguration() {
910             return mConfig;
911         }
912 
913         @Override
getBrightness(float lux, String packageName, @ApplicationInfo.Category int category)914         public float getBrightness(float lux, String packageName,
915                 @ApplicationInfo.Category int category) {
916             float nits = mBrightnessSpline.interpolate(lux);
917 
918             // Adjust nits to compensate for display white balance colour strength.
919             if (mDisplayWhiteBalanceController != null) {
920                 nits = mDisplayWhiteBalanceController.calculateAdjustedBrightnessNits(nits);
921             }
922 
923             float brightness = mAdjustedNitsToBrightnessSpline.interpolate(nits);
924             // Correct the brightness according to the current application and its category, but
925             // only if no user data point is set (as this will override the user setting).
926             if (mUserLux == -1) {
927                 brightness = correctBrightness(brightness, packageName, category);
928             } else if (mLoggingEnabled) {
929                 Slog.d(TAG, "user point set, correction not applied");
930             }
931             return brightness;
932         }
933 
934         @Override
getAutoBrightnessAdjustment()935         public float getAutoBrightnessAdjustment() {
936             return mAutoBrightnessAdjustment;
937         }
938 
939         @Override
setAutoBrightnessAdjustment(float adjustment)940         public boolean setAutoBrightnessAdjustment(float adjustment) {
941             adjustment = MathUtils.constrain(adjustment, -1, 1);
942             if (adjustment == mAutoBrightnessAdjustment) {
943                 return false;
944             }
945             if (mLoggingEnabled) {
946                 Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " +
947                         adjustment);
948                 PLOG.start("auto-brightness adjustment");
949             }
950             mAutoBrightnessAdjustment = adjustment;
951             computeSpline();
952             return true;
953         }
954 
955         @Override
convertToNits(float brightness)956         public float convertToNits(float brightness) {
957             return mBrightnessToNitsSpline.interpolate(brightness);
958         }
959 
960         @Override
convertToAdjustedNits(float brightness)961         public float convertToAdjustedNits(float brightness) {
962             return mBrightnessToAdjustedNitsSpline.interpolate(brightness);
963         }
964 
965         @Override
getBrightnessFromNits(float nits)966         public float getBrightnessFromNits(float nits) {
967             return mNitsToBrightnessSpline.interpolate(nits);
968         }
969 
970         @Override
addUserDataPoint(float lux, float brightness)971         public void addUserDataPoint(float lux, float brightness) {
972             float unadjustedBrightness = getUnadjustedBrightness(lux);
973             if (mLoggingEnabled) {
974                 Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")");
975                 PLOG.start("add user data point")
976                         .logPoint("user data point", lux, brightness)
977                         .logPoint("current brightness", lux, unadjustedBrightness);
978             }
979             float adjustment = inferAutoBrightnessAdjustment(mMaxGamma,
980                     brightness /* desiredBrightness */,
981                     unadjustedBrightness /* currentBrightness */);
982             if (mLoggingEnabled) {
983                 Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " +
984                         adjustment);
985             }
986             mAutoBrightnessAdjustment = adjustment;
987             mUserLux = lux;
988             mUserBrightness = brightness;
989             computeSpline();
990 
991             if (mPreviousBrightnessSplines.size() == NO_OF_PREVIOUS_CONFIGS_TO_LOG) {
992                 mPreviousBrightnessSplines.remove(0);
993                 mBrightnessSplineChangeTimes.remove(0);
994             }
995             mPreviousBrightnessSplines.add(mBrightnessSpline);
996             mBrightnessSplineChangeTimes.add(System.currentTimeMillis());
997         }
998 
999         @Override
clearUserDataPoints()1000         public void clearUserDataPoints() {
1001             if (mUserLux != -1) {
1002                 if (mLoggingEnabled) {
1003                     Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0");
1004                     PLOG.start("clear user data points")
1005                             .logPoint("user data point", mUserLux, mUserBrightness);
1006                 }
1007                 mAutoBrightnessAdjustment = 0;
1008                 mUserLux = INVALID_LUX;
1009                 mUserBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
1010                 computeSpline();
1011             }
1012         }
1013 
1014         @Override
hasUserDataPoints()1015         public boolean hasUserDataPoints() {
1016             return mUserLux != INVALID_LUX;
1017         }
1018 
1019         @Override
isDefaultConfig()1020         public boolean isDefaultConfig() {
1021             return mDefaultConfig.equals(mConfig);
1022         }
1023 
1024         @Override
getDefaultConfig()1025         public BrightnessConfiguration getDefaultConfig() {
1026             return mDefaultConfig;
1027         }
1028 
1029         @Override
recalculateSplines(boolean applyAdjustment, float[] adjustedNits)1030         public void recalculateSplines(boolean applyAdjustment, float[] adjustedNits) {
1031             mBrightnessRangeAdjustmentApplied = applyAdjustment;
1032             if (applyAdjustment) {
1033                 mAdjustedNitsToBrightnessSpline = Spline.createSpline(adjustedNits, mBrightness);
1034                 mBrightnessToAdjustedNitsSpline = Spline.createSpline(mBrightness, adjustedNits);
1035             } else {
1036                 mAdjustedNitsToBrightnessSpline = mNitsToBrightnessSpline;
1037                 mBrightnessToAdjustedNitsSpline = mBrightnessToNitsSpline;
1038             }
1039         }
1040 
1041         @Override
dump(PrintWriter pw, float hbmTransition)1042         public void dump(PrintWriter pw, float hbmTransition) {
1043             pw.println("PhysicalMappingStrategy");
1044             pw.println("  mConfig=" + mConfig);
1045             pw.println("  mBrightnessSpline=" + mBrightnessSpline);
1046             pw.println("  mNitsToBrightnessSpline=" + mNitsToBrightnessSpline);
1047             pw.println("  mBrightnessToNitsSpline=" + mBrightnessToNitsSpline);
1048             pw.println("  mAdjustedNitsToBrightnessSpline=" + mAdjustedNitsToBrightnessSpline);
1049             pw.println("  mAdjustedBrightnessToNitsSpline=" + mBrightnessToAdjustedNitsSpline);
1050             pw.println("  mMaxGamma=" + mMaxGamma);
1051             pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
1052             pw.println("  mUserLux=" + mUserLux);
1053             pw.println("  mUserBrightness=" + mUserBrightness);
1054             pw.println("  mDefaultConfig=" + mDefaultConfig);
1055             pw.println("  mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied);
1056             pw.println("  shortTermModelTimeout=" + getShortTermModelTimeout());
1057 
1058             pw.println("  Previous short-term models (oldest to newest): ");
1059             for (int i = 0; i < mPreviousBrightnessSplines.size(); i++) {
1060                 pw.println("  Computed at "
1061                         + FORMAT.format(new Date(mBrightnessSplineChangeTimes.get(i))) + ": ");
1062                 dumpConfigDiff(pw, hbmTransition, mPreviousBrightnessSplines.get(i),
1063                         /* shortTermModelOnly= */ true);
1064             }
1065 
1066             pw.println("  Difference between current config and default: ");
1067             dumpConfigDiff(pw, hbmTransition, mBrightnessSpline, /* shortTermModelOnly= */ false);
1068         }
1069 
1070         @Override
getMode()1071         int getMode() {
1072             return mMode;
1073         }
1074 
1075         @Override
getUserLux()1076         float getUserLux() {
1077             return mUserLux;
1078         }
1079 
1080         @Override
getUserBrightness()1081         float getUserBrightness() {
1082             return mUserBrightness;
1083         }
1084 
1085         /**
1086          * Prints out the default curve and how it differs from the long-term curve
1087          * and the current curve (in case the current curve includes short-term adjustments).
1088          *
1089          * @param pw The print-writer to write to.
1090          */
dumpConfigDiff(PrintWriter pw, float hbmTransition, Spline brightnessSpline, boolean shortTermModelOnly)1091         private void dumpConfigDiff(PrintWriter pw, float hbmTransition, Spline brightnessSpline,
1092                 boolean shortTermModelOnly) {
1093             Pair<float[], float[]> currentCurve = mConfig.getCurve();
1094             Spline currSpline = Spline.createSpline(currentCurve.first, currentCurve.second);
1095 
1096             Pair<float[], float[]> defaultCurve = mDefaultConfig.getCurve();
1097             Spline defaultSpline = Spline.createSpline(defaultCurve.first, defaultCurve.second);
1098 
1099             // Add the short-term curve lux point if present
1100             float[] luxes = currentCurve.first;
1101             if (mUserLux >= 0) {
1102                 luxes = Arrays.copyOf(currentCurve.first, currentCurve.first.length + 1);
1103                 luxes[luxes.length - 1] = mUserLux;
1104                 Arrays.sort(luxes);
1105             }
1106 
1107             StringBuilder sbLux = null;
1108             StringBuilder sbNits = null;
1109             StringBuilder sbLong = null;
1110             StringBuilder sbShort = null;
1111             StringBuilder sbBrightness = null;
1112             StringBuilder sbPercent = null;
1113             StringBuilder sbPercentHbm = null;
1114             boolean needsHeaders = true;
1115             String separator = "";
1116             for (int i = 0; i < luxes.length; i++) {
1117                 float lux = luxes[i];
1118                 if (needsHeaders) {
1119                     sbLux = new StringBuilder("            lux: ");
1120                     sbNits = new StringBuilder("        default: ");
1121                     sbLong = new StringBuilder("      long-term: ");
1122                     sbShort = new StringBuilder("        current: ");
1123                     sbBrightness = new StringBuilder("    current(bl): ");
1124                     sbPercent = new StringBuilder("     current(%): ");
1125                     sbPercentHbm = new StringBuilder("  current(hbm%): ");
1126                     needsHeaders = false;
1127                 }
1128 
1129                 float defaultNits = defaultSpline.interpolate(lux);
1130                 float longTermNits = currSpline.interpolate(lux);
1131                 float shortTermNits = brightnessSpline.interpolate(lux);
1132                 float brightness = mAdjustedNitsToBrightnessSpline.interpolate(shortTermNits);
1133 
1134                 String luxPrefix = (lux == mUserLux ? "^" : "");
1135                 String strLux = luxPrefix + toStrFloatForDump(lux);
1136                 String strNits = toStrFloatForDump(defaultNits);
1137                 String strLong = toStrFloatForDump(longTermNits);
1138                 String strShort = toStrFloatForDump(shortTermNits);
1139                 String strBrightness = toStrFloatForDump(brightness);
1140                 String strPercent = String.valueOf(
1141                         Math.round(100.0f * BrightnessUtils.convertLinearToGamma(
1142                             (brightness / hbmTransition))));
1143                 String strPercentHbm = String.valueOf(
1144                         Math.round(100.0f * BrightnessUtils.convertLinearToGamma(brightness)));
1145 
1146                 int maxLen = Math.max(strLux.length(),
1147                         Math.max(strNits.length(),
1148                         Math.max(strBrightness.length(),
1149                         Math.max(strPercent.length(),
1150                         Math.max(strPercentHbm.length(),
1151                         Math.max(strLong.length(), strShort.length()))))));
1152                 String format = separator + "%" + maxLen + "s";
1153                 separator = ", ";
1154 
1155                 sbLux.append(formatSimple(format, strLux));
1156                 sbNits.append(formatSimple(format, strNits));
1157                 sbLong.append(formatSimple(format, strLong));
1158                 sbShort.append(formatSimple(format, strShort));
1159                 sbBrightness.append(formatSimple(format, strBrightness));
1160                 sbPercent.append(formatSimple(format, strPercent));
1161                 sbPercentHbm.append(formatSimple(format, strPercentHbm));
1162 
1163                 // At 80 chars, start another row
1164                 if (sbLux.length() > 80 || (i == luxes.length - 1)) {
1165                     pw.println(sbLux);
1166                     if (!shortTermModelOnly) {
1167                         pw.println(sbNits);
1168                         pw.println(sbLong);
1169                     }
1170                     pw.println(sbShort);
1171                     pw.println(sbBrightness);
1172                     pw.println(sbPercent);
1173                     if (hbmTransition < PowerManager.BRIGHTNESS_MAX) {
1174                         pw.println(sbPercentHbm);
1175                     }
1176                     pw.println("");
1177                     needsHeaders = true;
1178                     separator = "";
1179                 }
1180             }
1181         }
1182 
toStrFloatForDump(float value)1183         private String toStrFloatForDump(float value) {
1184             if (value == 0.0f) {
1185                 return "0";
1186             } else if (value < 0.1f) {
1187                 return String.format(Locale.US, "%.3f", value);
1188             } else if (value < 1) {
1189                 return String.format(Locale.US, "%.2f", value);
1190             } else if (value < 10) {
1191                 return String.format(Locale.US, "%.1f", value);
1192             } else {
1193                 return formatSimple("%d", Math.round(value));
1194             }
1195         }
1196 
computeNitsBrightnessSplines(float[] nits)1197         private void computeNitsBrightnessSplines(float[] nits) {
1198             mNitsToBrightnessSpline = Spline.createSpline(nits, mBrightness);
1199             mBrightnessToNitsSpline = Spline.createSpline(mBrightness, nits);
1200         }
1201 
computeSpline()1202         private void computeSpline() {
1203             Pair<float[], float[]> defaultCurve = mConfig.getCurve();
1204             float[] defaultLux = defaultCurve.first;
1205             float[] defaultNits = defaultCurve.second;
1206             float[] defaultBrightness = new float[defaultNits.length];
1207             for (int i = 0; i < defaultBrightness.length; i++) {
1208                 defaultBrightness[i] = mAdjustedNitsToBrightnessSpline.interpolate(defaultNits[i]);
1209             }
1210             Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBrightness, mUserLux,
1211                     mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
1212             float[] lux = curve.first;
1213             float[] brightness = curve.second;
1214             float[] nits = new float[brightness.length];
1215             for (int i = 0; i < nits.length; i++) {
1216                 nits[i] = mBrightnessToAdjustedNitsSpline.interpolate(brightness[i]);
1217             }
1218             mBrightnessSpline = Spline.createSpline(lux, nits);
1219         }
1220 
getUnadjustedBrightness(float lux)1221         private float getUnadjustedBrightness(float lux) {
1222             Pair<float[], float[]> curve = mConfig.getCurve();
1223             Spline spline = Spline.createSpline(curve.first, curve.second);
1224             return mAdjustedNitsToBrightnessSpline.interpolate(spline.interpolate(lux));
1225         }
1226 
correctBrightness(float brightness, String packageName, int category)1227         private float correctBrightness(float brightness, String packageName, int category) {
1228             if (packageName != null) {
1229                 BrightnessCorrection correction = mConfig.getCorrectionByPackageName(packageName);
1230                 if (correction != null) {
1231                     return correction.apply(brightness);
1232                 }
1233             }
1234             if (category != ApplicationInfo.CATEGORY_UNDEFINED) {
1235                 BrightnessCorrection correction = mConfig.getCorrectionByCategory(category);
1236                 if (correction != null) {
1237                     return correction.apply(brightness);
1238                 }
1239             }
1240             return brightness;
1241         }
1242     }
1243 }
1244