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