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