• 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 android.annotation.Nullable;
20 import android.content.pm.ApplicationInfo;
21 import android.content.res.Resources;
22 import android.content.res.TypedArray;
23 import android.hardware.display.BrightnessConfiguration;
24 import android.hardware.display.BrightnessCorrection;
25 import android.os.PowerManager;
26 import android.util.MathUtils;
27 import android.util.Pair;
28 import android.util.Slog;
29 import android.util.Spline;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.internal.util.Preconditions;
33 import com.android.server.display.utils.Plog;
34 
35 import java.io.PrintWriter;
36 import java.util.Arrays;
37 
38 /**
39  * A utility to map from an ambient brightness to a display's "backlight" brightness based on the
40  * available display information and brightness configuration.
41  *
42  * Note that without a mapping from the nits to a display backlight level, any
43  * {@link BrightnessConfiguration}s that are set are just ignored.
44  */
45 public abstract class BrightnessMappingStrategy {
46     private static final String TAG = "BrightnessMappingStrategy";
47 
48     private static final float LUX_GRAD_SMOOTHING = 0.25f;
49     private static final float MAX_GRAD = 1.0f;
50 
51     protected boolean mLoggingEnabled;
52 
53     private static final Plog PLOG = Plog.createSystemPlog(TAG);
54 
55     @Nullable
create(Resources resources)56     public static BrightnessMappingStrategy create(Resources resources) {
57         float[] luxLevels = getLuxLevels(resources.getIntArray(
58                 com.android.internal.R.array.config_autoBrightnessLevels));
59         int[] brightnessLevelsBacklight = resources.getIntArray(
60                 com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
61         float[] brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
62                 com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
63         float autoBrightnessAdjustmentMaxGamma = resources.getFraction(
64                 com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
65                 1, 1);
66 
67         float[] nitsRange = getFloatArray(resources.obtainTypedArray(
68                 com.android.internal.R.array.config_screenBrightnessNits));
69         int[] backlightRange = resources.getIntArray(
70                 com.android.internal.R.array.config_screenBrightnessBacklight);
71 
72         if (isValidMapping(nitsRange, backlightRange)
73                 && isValidMapping(luxLevels, brightnessLevelsNits)) {
74             int minimumBacklight = resources.getInteger(
75                     com.android.internal.R.integer.config_screenBrightnessSettingMinimum);
76             int maximumBacklight = resources.getInteger(
77                     com.android.internal.R.integer.config_screenBrightnessSettingMaximum);
78             if (backlightRange[0] > minimumBacklight
79                     || backlightRange[backlightRange.length - 1] < maximumBacklight) {
80                 Slog.w(TAG, "Screen brightness mapping does not cover whole range of available " +
81                         "backlight values, autobrightness functionality may be impaired.");
82             }
83             BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
84                     luxLevels, brightnessLevelsNits);
85             return new PhysicalMappingStrategy(builder.build(), nitsRange, backlightRange,
86                     autoBrightnessAdjustmentMaxGamma);
87         } else if (isValidMapping(luxLevels, brightnessLevelsBacklight)) {
88             return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight,
89                     autoBrightnessAdjustmentMaxGamma);
90         } else {
91             return null;
92         }
93     }
94 
getLuxLevels(int[] lux)95     private static float[] getLuxLevels(int[] lux) {
96         // The first control point is implicit and always at 0 lux.
97         float[] levels = new float[lux.length + 1];
98         for (int i = 0; i < lux.length; i++) {
99             levels[i + 1] = (float) lux[i];
100         }
101         return levels;
102     }
103 
getFloatArray(TypedArray array)104     private static float[] getFloatArray(TypedArray array) {
105         final int N = array.length();
106         float[] vals = new float[N];
107         for (int i = 0; i < N; i++) {
108             vals[i] = array.getFloat(i, -1.0f);
109         }
110         array.recycle();
111         return vals;
112     }
113 
isValidMapping(float[] x, float[] y)114     private static boolean isValidMapping(float[] x, float[] y) {
115         if (x == null || y == null || x.length == 0 || y.length == 0) {
116             return false;
117         }
118         if (x.length != y.length) {
119             return false;
120         }
121         final int N = x.length;
122         float prevX = x[0];
123         float prevY = y[0];
124         if (prevX < 0 || prevY < 0 || Float.isNaN(prevX) || Float.isNaN(prevY)) {
125             return false;
126         }
127         for (int i = 1; i < N; i++) {
128             if (prevX >= x[i] || prevY > y[i]) {
129                 return false;
130             }
131             if (Float.isNaN(x[i]) || Float.isNaN(y[i])) {
132                 return false;
133             }
134             prevX = x[i];
135             prevY = y[i];
136         }
137         return true;
138     }
139 
isValidMapping(float[] x, int[] y)140     private static boolean isValidMapping(float[] x, int[] y) {
141         if (x == null || y == null || x.length == 0 || y.length == 0) {
142             return false;
143         }
144         if (x.length != y.length) {
145             return false;
146         }
147         final int N = x.length;
148         float prevX = x[0];
149         int prevY = y[0];
150         if (prevX < 0 || prevY < 0 || Float.isNaN(prevX)) {
151             return false;
152         }
153         for (int i = 1; i < N; i++) {
154             if (prevX >= x[i] || prevY > y[i]) {
155                 return false;
156             }
157             if (Float.isNaN(x[i])) {
158                 return false;
159             }
160             prevX = x[i];
161             prevY = y[i];
162         }
163         return true;
164     }
165 
166     /**
167      * Enable/disable logging.
168      *
169      * @param loggingEnabled
170      *      Whether logging should be on/off.
171      *
172      * @return Whether the method succeeded or not.
173      */
setLoggingEnabled(boolean loggingEnabled)174     public boolean setLoggingEnabled(boolean loggingEnabled) {
175         if (mLoggingEnabled == loggingEnabled) {
176             return false;
177         }
178         mLoggingEnabled = loggingEnabled;
179         return true;
180     }
181 
182     /**
183      * Sets the {@link BrightnessConfiguration}.
184      *
185      * @param config The new configuration. If {@code null} is passed, the default configuration is
186      *               used.
187      * @return Whether the brightness configuration has changed.
188      */
setBrightnessConfiguration(@ullable BrightnessConfiguration config)189     public abstract boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config);
190 
191     /**
192      * Returns the desired brightness of the display based on the current ambient lux, including
193      * any context-related corrections.
194      *
195      * The returned brightness will be in the range [0, 1.0], where 1.0 is the display at max
196      * brightness and 0 is the display at minimum brightness.
197      *
198      * @param lux The current ambient brightness in lux.
199      * @param packageName the foreground app package name.
200      * @param category the foreground app package category.
201      * @return The desired brightness of the display normalized to the range [0, 1.0].
202      */
getBrightness(float lux, String packageName, @ApplicationInfo.Category int category)203     public abstract float getBrightness(float lux, String packageName,
204             @ApplicationInfo.Category int category);
205 
206     /**
207      * Returns the desired brightness of the display based on the current ambient lux.
208      *
209      * The returned brightness wil be in the range [0, 1.0], where 1.0 is the display at max
210      * brightness and 0 is the display at minimum brightness.
211      *
212      * @param lux The current ambient brightness in lux.
213      *
214      * @return The desired brightness of the display normalized to the range [0, 1.0].
215      */
getBrightness(float lux)216     public float getBrightness(float lux) {
217         return getBrightness(lux, null /* packageName */, ApplicationInfo.CATEGORY_UNDEFINED);
218     }
219 
220     /**
221      * Returns the current auto-brightness adjustment.
222      *
223      * The returned adjustment is a value in the range [-1.0, 1.0] such that
224      * {@code config_autoBrightnessAdjustmentMaxGamma<sup>-adjustment</sup>} is used to gamma
225      * correct the brightness curve.
226      */
getAutoBrightnessAdjustment()227     public abstract float getAutoBrightnessAdjustment();
228 
229     /**
230      * Sets the auto-brightness adjustment.
231      *
232      * @param adjustment The desired auto-brightness adjustment.
233      * @return Whether the auto-brightness adjustment has changed.
234      *
235      * @Deprecated The auto-brightness adjustment should not be set directly, but rather inferred
236      * from user data points.
237      */
setAutoBrightnessAdjustment(float adjustment)238     public abstract boolean setAutoBrightnessAdjustment(float adjustment);
239 
240     /**
241      * Converts the provided backlight value to nits if possible.
242      *
243      * Returns -1.0f if there's no available mapping for the backlight to nits.
244      */
convertToNits(int backlight)245     public abstract float convertToNits(int backlight);
246 
247     /**
248      * Adds a user interaction data point to the brightness mapping.
249      *
250      * This data point <b>must</b> exist on the brightness curve as a result of this call. This is
251      * so that the next time we come to query what the screen brightness should be, we get what the
252      * user requested rather than immediately changing to some other value.
253      *
254      * Currently, we only keep track of one of these at a time to constrain what can happen to the
255      * curve.
256      */
addUserDataPoint(float lux, float brightness)257     public abstract void addUserDataPoint(float lux, float brightness);
258 
259     /**
260      * Removes any short term adjustments made to the curve from user interactions.
261      *
262      * Note that this does *not* reset the mapping to its initial state, any brightness
263      * configurations that have been applied will continue to be in effect. This solely removes the
264      * effects of user interactions on the model.
265      */
clearUserDataPoints()266     public abstract void clearUserDataPoints();
267 
268     /** @return True if there are any short term adjustments applied to the curve. */
hasUserDataPoints()269     public abstract boolean hasUserDataPoints();
270 
271     /** @return True if the current brightness configuration is the default one. */
isDefaultConfig()272     public abstract boolean isDefaultConfig();
273 
274     /** @return The default brightness configuration. */
getDefaultConfig()275     public abstract BrightnessConfiguration getDefaultConfig();
276 
dump(PrintWriter pw)277     public abstract void dump(PrintWriter pw);
278 
normalizeAbsoluteBrightness(int brightness)279     protected float normalizeAbsoluteBrightness(int brightness) {
280         brightness = MathUtils.constrain(brightness,
281                 PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
282         return (float) brightness / PowerManager.BRIGHTNESS_ON;
283     }
284 
insertControlPoint( float[] luxLevels, float[] brightnessLevels, float lux, float brightness)285     private Pair<float[], float[]> insertControlPoint(
286             float[] luxLevels, float[] brightnessLevels, float lux, float brightness) {
287         final int idx = findInsertionPoint(luxLevels, lux);
288         final float[] newLuxLevels;
289         final float[] newBrightnessLevels;
290         if (idx == luxLevels.length) {
291             newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
292             newBrightnessLevels  = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
293             newLuxLevels[idx] = lux;
294             newBrightnessLevels[idx] = brightness;
295         } else if (luxLevels[idx] == lux) {
296             newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length);
297             newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length);
298             newBrightnessLevels[idx] = brightness;
299         } else {
300             newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
301             System.arraycopy(newLuxLevels, idx, newLuxLevels, idx+1, luxLevels.length - idx);
302             newLuxLevels[idx] = lux;
303             newBrightnessLevels  = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
304             System.arraycopy(newBrightnessLevels, idx, newBrightnessLevels, idx+1,
305                     brightnessLevels.length - idx);
306             newBrightnessLevels[idx] = brightness;
307         }
308         smoothCurve(newLuxLevels, newBrightnessLevels, idx);
309         return Pair.create(newLuxLevels, newBrightnessLevels);
310     }
311 
312     /**
313      * Returns the index of the first value that's less than or equal to {@code val}.
314      *
315      * This assumes that {@code arr} is sorted. If all values in {@code arr} are greater
316      * than val, then it will return the length of arr as the insertion point.
317      */
findInsertionPoint(float[] arr, float val)318     private int findInsertionPoint(float[] arr, float val) {
319         for (int i = 0; i < arr.length; i++) {
320             if (val <= arr[i]) {
321                 return i;
322             }
323         }
324         return arr.length;
325     }
326 
smoothCurve(float[] lux, float[] brightness, int idx)327     private void smoothCurve(float[] lux, float[] brightness, int idx) {
328         if (mLoggingEnabled) {
329             PLOG.logCurve("unsmoothed curve", lux, brightness);
330         }
331         float prevLux = lux[idx];
332         float prevBrightness = brightness[idx];
333         // Smooth curve for data points above the newly introduced point
334         for (int i = idx+1; i < lux.length; i++) {
335             float currLux = lux[i];
336             float currBrightness = brightness[i];
337             float maxBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
338             float newBrightness = MathUtils.constrain(
339                     currBrightness, prevBrightness, maxBrightness);
340             if (newBrightness == currBrightness) {
341                 break;
342             }
343             prevLux = currLux;
344             prevBrightness = newBrightness;
345             brightness[i] = newBrightness;
346         }
347         // Smooth curve for data points below the newly introduced point
348         prevLux = lux[idx];
349         prevBrightness = brightness[idx];
350         for (int i = idx-1; i >= 0; i--) {
351             float currLux = lux[i];
352             float currBrightness = brightness[i];
353             float minBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
354             float newBrightness = MathUtils.constrain(
355                     currBrightness, minBrightness, prevBrightness);
356             if (newBrightness == currBrightness) {
357                 break;
358             }
359             prevLux = currLux;
360             prevBrightness = newBrightness;
361             brightness[i] = newBrightness;
362         }
363         if (mLoggingEnabled) {
364             PLOG.logCurve("smoothed curve", lux, brightness);
365         }
366     }
367 
permissibleRatio(float currLux, float prevLux)368     private float permissibleRatio(float currLux, float prevLux) {
369         return MathUtils.exp(MAX_GRAD
370                 * (MathUtils.log(currLux + LUX_GRAD_SMOOTHING)
371                     - MathUtils.log(prevLux + LUX_GRAD_SMOOTHING)));
372     }
373 
inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness, float currentBrightness)374     protected float inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness,
375             float currentBrightness) {
376         float adjustment = 0;
377         float gamma = Float.NaN;
378         // Extreme edge cases: use a simpler heuristic, as proper gamma correction around the edges
379         // affects the curve rather drastically.
380         if (currentBrightness <= 0.1f || currentBrightness >= 0.9f) {
381             adjustment = (desiredBrightness - currentBrightness);
382         // Edge case: darkest adjustment possible.
383         } else if (desiredBrightness == 0) {
384             adjustment = -1;
385         // Edge case: brightest adjustment possible.
386         } else if (desiredBrightness == 1) {
387             adjustment = +1;
388         } else {
389             // current^gamma = desired => gamma = log[current](desired)
390             gamma = MathUtils.log(desiredBrightness) / MathUtils.log(currentBrightness);
391             // max^-adjustment = gamma => adjustment = -log[max](gamma)
392             adjustment = -MathUtils.log(gamma) / MathUtils.log(maxGamma);
393         }
394         adjustment = MathUtils.constrain(adjustment, -1, +1);
395         if (mLoggingEnabled) {
396             Slog.d(TAG, "inferAutoBrightnessAdjustment: " + maxGamma + "^" + -adjustment + "=" +
397                     MathUtils.pow(maxGamma, -adjustment) + " == " + gamma);
398             Slog.d(TAG, "inferAutoBrightnessAdjustment: " + currentBrightness + "^" + gamma + "=" +
399                     MathUtils.pow(currentBrightness, gamma) + " == " + desiredBrightness);
400         }
401         return adjustment;
402     }
403 
getAdjustedCurve(float[] lux, float[] brightness, float userLux, float userBrightness, float adjustment, float maxGamma)404     protected Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness,
405             float userLux, float userBrightness, float adjustment, float maxGamma) {
406         float[] newLux = lux;
407         float[] newBrightness = Arrays.copyOf(brightness, brightness.length);
408         if (mLoggingEnabled) {
409             PLOG.logCurve("unadjusted curve", newLux, newBrightness);
410         }
411         adjustment = MathUtils.constrain(adjustment, -1, 1);
412         float gamma = MathUtils.pow(maxGamma, -adjustment);
413         if (mLoggingEnabled) {
414             Slog.d(TAG, "getAdjustedCurve: " + maxGamma + "^" + -adjustment + "=" +
415                     MathUtils.pow(maxGamma, -adjustment) + " == " + gamma);
416         }
417         if (gamma != 1) {
418             for (int i = 0; i < newBrightness.length; i++) {
419                 newBrightness[i] = MathUtils.pow(newBrightness[i], gamma);
420             }
421         }
422         if (mLoggingEnabled) {
423             PLOG.logCurve("gamma adjusted curve", newLux, newBrightness);
424         }
425         if (userLux != -1) {
426             Pair<float[], float[]> curve = insertControlPoint(newLux, newBrightness, userLux,
427                     userBrightness);
428             newLux = curve.first;
429             newBrightness = curve.second;
430             if (mLoggingEnabled) {
431                 PLOG.logCurve("gamma and user adjusted curve", newLux, newBrightness);
432                 // This is done for comparison.
433                 curve = insertControlPoint(lux, brightness, userLux, userBrightness);
434                 PLOG.logCurve("user adjusted curve", curve.first ,curve.second);
435             }
436         }
437         return Pair.create(newLux, newBrightness);
438     }
439 
440     /**
441      * A {@link BrightnessMappingStrategy} that maps from ambient room brightness directly to the
442      * backlight of the display.
443      *
444      * Since we don't have information about the display's physical brightness, any brightness
445      * configurations that are set are just ignored.
446      */
447     private static class SimpleMappingStrategy extends BrightnessMappingStrategy {
448         // Lux control points
449         private final float[] mLux;
450         // Brightness control points normalized to [0, 1]
451         private final float[] mBrightness;
452 
453         private Spline mSpline;
454         private float mMaxGamma;
455         private float mAutoBrightnessAdjustment;
456         private float mUserLux;
457         private float mUserBrightness;
458 
SimpleMappingStrategy(float[] lux, int[] brightness, float maxGamma)459         public SimpleMappingStrategy(float[] lux, int[] brightness, float maxGamma) {
460             Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
461                     "Lux and brightness arrays must not be empty!");
462             Preconditions.checkArgument(lux.length == brightness.length,
463                     "Lux and brightness arrays must be the same length!");
464             Preconditions.checkArrayElementsInRange(lux, 0, Float.MAX_VALUE, "lux");
465             Preconditions.checkArrayElementsInRange(brightness,
466                     0, Integer.MAX_VALUE, "brightness");
467 
468             final int N = brightness.length;
469             mLux = new float[N];
470             mBrightness = new float[N];
471             for (int i = 0; i < N; i++) {
472                 mLux[i] = lux[i];
473                 mBrightness[i] = normalizeAbsoluteBrightness(brightness[i]);
474             }
475 
476             mMaxGamma = maxGamma;
477             mAutoBrightnessAdjustment = 0;
478             mUserLux = -1;
479             mUserBrightness = -1;
480             if (mLoggingEnabled) {
481                 PLOG.start("simple mapping strategy");
482             }
483             computeSpline();
484         }
485 
486         @Override
setBrightnessConfiguration(@ullable BrightnessConfiguration config)487         public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
488             return false;
489         }
490 
491         @Override
getBrightness(float lux, String packageName, @ApplicationInfo.Category int category)492         public float getBrightness(float lux, String packageName,
493                 @ApplicationInfo.Category int category) {
494             return mSpline.interpolate(lux);
495         }
496 
497         @Override
getAutoBrightnessAdjustment()498         public float getAutoBrightnessAdjustment() {
499             return mAutoBrightnessAdjustment;
500         }
501 
502         @Override
setAutoBrightnessAdjustment(float adjustment)503         public boolean setAutoBrightnessAdjustment(float adjustment) {
504             adjustment = MathUtils.constrain(adjustment, -1, 1);
505             if (adjustment == mAutoBrightnessAdjustment) {
506                 return false;
507             }
508             if (mLoggingEnabled) {
509                 Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " +
510                         adjustment);
511                 PLOG.start("auto-brightness adjustment");
512             }
513             mAutoBrightnessAdjustment = adjustment;
514             computeSpline();
515             return true;
516         }
517 
518         @Override
convertToNits(int backlight)519         public float convertToNits(int backlight) {
520             return -1.0f;
521         }
522 
523         @Override
addUserDataPoint(float lux, float brightness)524         public void addUserDataPoint(float lux, float brightness) {
525             float unadjustedBrightness = getUnadjustedBrightness(lux);
526             if (mLoggingEnabled) {
527                 Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")");
528                 PLOG.start("add user data point")
529                         .logPoint("user data point", lux, brightness)
530                         .logPoint("current brightness", lux, unadjustedBrightness);
531             }
532             float adjustment = inferAutoBrightnessAdjustment(mMaxGamma,
533                     brightness /* desiredBrightness */,
534                     unadjustedBrightness /* currentBrightness */);
535             if (mLoggingEnabled) {
536                 Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " +
537                         adjustment);
538             }
539             mAutoBrightnessAdjustment = adjustment;
540             mUserLux = lux;
541             mUserBrightness = brightness;
542             computeSpline();
543         }
544 
545         @Override
clearUserDataPoints()546         public void clearUserDataPoints() {
547             if (mUserLux != -1) {
548                 if (mLoggingEnabled) {
549                     Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0");
550                     PLOG.start("clear user data points")
551                             .logPoint("user data point", mUserLux, mUserBrightness);
552                 }
553                 mAutoBrightnessAdjustment = 0;
554                 mUserLux = -1;
555                 mUserBrightness = -1;
556                 computeSpline();
557             }
558         }
559 
560         @Override
hasUserDataPoints()561         public boolean hasUserDataPoints() {
562             return mUserLux != -1;
563         }
564 
565         @Override
isDefaultConfig()566         public boolean isDefaultConfig() {
567             return true;
568         }
569 
570         @Override
getDefaultConfig()571         public BrightnessConfiguration getDefaultConfig() {
572             return null;
573         }
574 
575         @Override
dump(PrintWriter pw)576         public void dump(PrintWriter pw) {
577             pw.println("SimpleMappingStrategy");
578             pw.println("  mSpline=" + mSpline);
579             pw.println("  mMaxGamma=" + mMaxGamma);
580             pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
581             pw.println("  mUserLux=" + mUserLux);
582             pw.println("  mUserBrightness=" + mUserBrightness);
583         }
584 
computeSpline()585         private void computeSpline() {
586             Pair<float[], float[]> curve = getAdjustedCurve(mLux, mBrightness, mUserLux,
587                     mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
588             mSpline = Spline.createSpline(curve.first, curve.second);
589         }
590 
getUnadjustedBrightness(float lux)591         private float getUnadjustedBrightness(float lux) {
592             Spline spline = Spline.createSpline(mLux, mBrightness);
593             return spline.interpolate(lux);
594         }
595     }
596 
597     /** A {@link BrightnessMappingStrategy} that maps from ambient room brightness to the physical
598      * range of the display, rather than to the range of the backlight control (typically 0-255).
599      *
600      * By mapping through the physical brightness, the curve becomes portable across devices and
601      * gives us more resolution in the resulting mapping.
602      */
603     @VisibleForTesting
604     static class PhysicalMappingStrategy extends BrightnessMappingStrategy {
605         // The current brightness configuration.
606         private BrightnessConfiguration mConfig;
607 
608         // A spline mapping from the current ambient light in lux to the desired display brightness
609         // in nits.
610         private Spline mBrightnessSpline;
611 
612         // A spline mapping from nits to the corresponding backlight value, normalized to the range
613         // [0, 1.0].
614         private final Spline mNitsToBacklightSpline;
615 
616         // The default brightness configuration.
617         private final BrightnessConfiguration mDefaultConfig;
618 
619         // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to
620         // a brightness in nits.
621         private Spline mBacklightToNitsSpline;
622 
623         private float mMaxGamma;
624         private float mAutoBrightnessAdjustment;
625         private float mUserLux;
626         private float mUserBrightness;
627 
PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits, int[] backlight, float maxGamma)628         public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
629                                        int[] backlight, float maxGamma) {
630             Preconditions.checkArgument(nits.length != 0 && backlight.length != 0,
631                     "Nits and backlight arrays must not be empty!");
632             Preconditions.checkArgument(nits.length == backlight.length,
633                     "Nits and backlight arrays must be the same length!");
634             Preconditions.checkNotNull(config);
635             Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits");
636             Preconditions.checkArrayElementsInRange(backlight,
637                     PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON, "backlight");
638 
639             mMaxGamma = maxGamma;
640             mAutoBrightnessAdjustment = 0;
641             mUserLux = -1;
642             mUserBrightness = -1;
643 
644             // Setup the backlight spline
645             final int N = nits.length;
646             float[] normalizedBacklight = new float[N];
647             for (int i = 0; i < N; i++) {
648                 normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]);
649             }
650 
651             mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight);
652             mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
653 
654             mDefaultConfig = config;
655             if (mLoggingEnabled) {
656                 PLOG.start("physical mapping strategy");
657             }
658             mConfig = config;
659             computeSpline();
660         }
661 
662         @Override
setBrightnessConfiguration(@ullable BrightnessConfiguration config)663         public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
664             if (config == null) {
665                 config = mDefaultConfig;
666             }
667             if (config.equals(mConfig)) {
668                 return false;
669             }
670             if (mLoggingEnabled) {
671                 PLOG.start("brightness configuration");
672             }
673             mConfig = config;
674             computeSpline();
675             return true;
676         }
677 
678         @Override
getBrightness(float lux, String packageName, @ApplicationInfo.Category int category)679         public float getBrightness(float lux, String packageName,
680                 @ApplicationInfo.Category int category) {
681             float nits = mBrightnessSpline.interpolate(lux);
682             float backlight = mNitsToBacklightSpline.interpolate(nits);
683             // Correct the brightness according to the current application and its category, but
684             // only if no user data point is set (as this will oevrride the user setting).
685             if (mUserLux == -1) {
686                 backlight = correctBrightness(backlight, packageName, category);
687             } else if (mLoggingEnabled) {
688                 Slog.d(TAG, "user point set, correction not applied");
689             }
690             return backlight;
691         }
692 
693         @Override
getAutoBrightnessAdjustment()694         public float getAutoBrightnessAdjustment() {
695             return mAutoBrightnessAdjustment;
696         }
697 
698         @Override
setAutoBrightnessAdjustment(float adjustment)699         public boolean setAutoBrightnessAdjustment(float adjustment) {
700             adjustment = MathUtils.constrain(adjustment, -1, 1);
701             if (adjustment == mAutoBrightnessAdjustment) {
702                 return false;
703             }
704             if (mLoggingEnabled) {
705                 Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " +
706                         adjustment);
707                 PLOG.start("auto-brightness adjustment");
708             }
709             mAutoBrightnessAdjustment = adjustment;
710             computeSpline();
711             return true;
712         }
713 
714         @Override
convertToNits(int backlight)715         public float convertToNits(int backlight) {
716             return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight));
717         }
718 
719         @Override
addUserDataPoint(float lux, float brightness)720         public void addUserDataPoint(float lux, float brightness) {
721             float unadjustedBrightness = getUnadjustedBrightness(lux);
722             if (mLoggingEnabled) {
723                 Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")");
724                 PLOG.start("add user data point")
725                         .logPoint("user data point", lux, brightness)
726                         .logPoint("current brightness", lux, unadjustedBrightness);
727             }
728             float adjustment = inferAutoBrightnessAdjustment(mMaxGamma,
729                     brightness /* desiredBrightness */,
730                     unadjustedBrightness /* currentBrightness */);
731             if (mLoggingEnabled) {
732                 Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " +
733                         adjustment);
734             }
735             mAutoBrightnessAdjustment = adjustment;
736             mUserLux = lux;
737             mUserBrightness = brightness;
738             computeSpline();
739         }
740 
741         @Override
clearUserDataPoints()742         public void clearUserDataPoints() {
743             if (mUserLux != -1) {
744                 if (mLoggingEnabled) {
745                     Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0");
746                     PLOG.start("clear user data points")
747                             .logPoint("user data point", mUserLux, mUserBrightness);
748                 }
749                 mAutoBrightnessAdjustment = 0;
750                 mUserLux = -1;
751                 mUserBrightness = -1;
752                 computeSpline();
753             }
754         }
755 
756         @Override
hasUserDataPoints()757         public boolean hasUserDataPoints() {
758             return mUserLux != -1;
759         }
760 
761         @Override
isDefaultConfig()762         public boolean isDefaultConfig() {
763             return mDefaultConfig.equals(mConfig);
764         }
765 
766         @Override
getDefaultConfig()767         public BrightnessConfiguration getDefaultConfig() {
768             return mDefaultConfig;
769         }
770 
771         @Override
dump(PrintWriter pw)772         public void dump(PrintWriter pw) {
773             pw.println("PhysicalMappingStrategy");
774             pw.println("  mConfig=" + mConfig);
775             pw.println("  mBrightnessSpline=" + mBrightnessSpline);
776             pw.println("  mNitsToBacklightSpline=" + mNitsToBacklightSpline);
777             pw.println("  mMaxGamma=" + mMaxGamma);
778             pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
779             pw.println("  mUserLux=" + mUserLux);
780             pw.println("  mUserBrightness=" + mUserBrightness);
781             pw.println("  mDefaultConfig=" + mDefaultConfig);
782         }
783 
computeSpline()784         private void computeSpline() {
785             Pair<float[], float[]> defaultCurve = mConfig.getCurve();
786             float[] defaultLux = defaultCurve.first;
787             float[] defaultNits = defaultCurve.second;
788             float[] defaultBacklight = new float[defaultNits.length];
789             for (int i = 0; i < defaultBacklight.length; i++) {
790                 defaultBacklight[i] = mNitsToBacklightSpline.interpolate(defaultNits[i]);
791             }
792             Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBacklight, mUserLux,
793                     mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
794             float[] lux = curve.first;
795             float[] backlight = curve.second;
796             float[] nits = new float[backlight.length];
797             for (int i = 0; i < nits.length; i++) {
798                 nits[i] = mBacklightToNitsSpline.interpolate(backlight[i]);
799             }
800             mBrightnessSpline = Spline.createSpline(lux, nits);
801         }
802 
getUnadjustedBrightness(float lux)803         private float getUnadjustedBrightness(float lux) {
804             Pair<float[], float[]> curve = mConfig.getCurve();
805             Spline spline = Spline.createSpline(curve.first, curve.second);
806             return mNitsToBacklightSpline.interpolate(spline.interpolate(lux));
807         }
808 
correctBrightness(float brightness, String packageName, int category)809         private float correctBrightness(float brightness, String packageName, int category) {
810             if (packageName != null) {
811                 BrightnessCorrection correction = mConfig.getCorrectionByPackageName(packageName);
812                 if (correction != null) {
813                     return correction.apply(brightness);
814                 }
815             }
816             if (category != ApplicationInfo.CATEGORY_UNDEFINED) {
817                 BrightnessCorrection correction = mConfig.getCorrectionByCategory(category);
818                 if (correction != null) {
819                     return correction.apply(brightness);
820                 }
821             }
822             return brightness;
823         }
824     }
825 }
826