/*
|
* Copyright (C) 2017 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.server.display;
|
|
import android.annotation.Nullable;
|
import android.content.pm.ApplicationInfo;
|
import android.content.res.Resources;
|
import android.content.res.TypedArray;
|
import android.hardware.display.BrightnessConfiguration;
|
import android.hardware.display.BrightnessCorrection;
|
import android.os.PowerManager;
|
import android.util.MathUtils;
|
import android.util.Pair;
|
import android.util.Slog;
|
import android.util.Spline;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.util.Preconditions;
|
import com.android.server.display.utils.Plog;
|
|
import java.io.PrintWriter;
|
import java.util.Arrays;
|
|
/**
|
* A utility to map from an ambient brightness to a display's "backlight" brightness based on the
|
* available display information and brightness configuration.
|
*
|
* Note that without a mapping from the nits to a display backlight level, any
|
* {@link BrightnessConfiguration}s that are set are just ignored.
|
*/
|
public abstract class BrightnessMappingStrategy {
|
private static final String TAG = "BrightnessMappingStrategy";
|
|
private static final float LUX_GRAD_SMOOTHING = 0.25f;
|
private static final float MAX_GRAD = 1.0f;
|
|
protected boolean mLoggingEnabled;
|
|
private static final Plog PLOG = Plog.createSystemPlog(TAG);
|
|
@Nullable
|
public static BrightnessMappingStrategy create(Resources resources) {
|
float[] luxLevels = getLuxLevels(resources.getIntArray(
|
com.android.internal.R.array.config_autoBrightnessLevels));
|
int[] brightnessLevelsBacklight = resources.getIntArray(
|
com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
|
float[] brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
|
com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
|
float autoBrightnessAdjustmentMaxGamma = resources.getFraction(
|
com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
|
1, 1);
|
|
float[] nitsRange = getFloatArray(resources.obtainTypedArray(
|
com.android.internal.R.array.config_screenBrightnessNits));
|
int[] backlightRange = resources.getIntArray(
|
com.android.internal.R.array.config_screenBrightnessBacklight);
|
|
if (isValidMapping(nitsRange, backlightRange)
|
&& isValidMapping(luxLevels, brightnessLevelsNits)) {
|
int minimumBacklight = resources.getInteger(
|
com.android.internal.R.integer.config_screenBrightnessSettingMinimum);
|
int maximumBacklight = resources.getInteger(
|
com.android.internal.R.integer.config_screenBrightnessSettingMaximum);
|
if (backlightRange[0] > minimumBacklight
|
|| backlightRange[backlightRange.length - 1] < maximumBacklight) {
|
Slog.w(TAG, "Screen brightness mapping does not cover whole range of available " +
|
"backlight values, autobrightness functionality may be impaired.");
|
}
|
BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
|
luxLevels, brightnessLevelsNits);
|
return new PhysicalMappingStrategy(builder.build(), nitsRange, backlightRange,
|
autoBrightnessAdjustmentMaxGamma);
|
} else if (isValidMapping(luxLevels, brightnessLevelsBacklight)) {
|
return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight,
|
autoBrightnessAdjustmentMaxGamma);
|
} else {
|
return null;
|
}
|
}
|
|
private static float[] getLuxLevels(int[] lux) {
|
// The first control point is implicit and always at 0 lux.
|
float[] levels = new float[lux.length + 1];
|
for (int i = 0; i < lux.length; i++) {
|
levels[i + 1] = (float) lux[i];
|
}
|
return levels;
|
}
|
|
private static float[] getFloatArray(TypedArray array) {
|
final int N = array.length();
|
float[] vals = new float[N];
|
for (int i = 0; i < N; i++) {
|
vals[i] = array.getFloat(i, -1.0f);
|
}
|
array.recycle();
|
return vals;
|
}
|
|
private static boolean isValidMapping(float[] x, float[] y) {
|
if (x == null || y == null || x.length == 0 || y.length == 0) {
|
return false;
|
}
|
if (x.length != y.length) {
|
return false;
|
}
|
final int N = x.length;
|
float prevX = x[0];
|
float prevY = y[0];
|
if (prevX < 0 || prevY < 0 || Float.isNaN(prevX) || Float.isNaN(prevY)) {
|
return false;
|
}
|
for (int i = 1; i < N; i++) {
|
if (prevX >= x[i] || prevY > y[i]) {
|
return false;
|
}
|
if (Float.isNaN(x[i]) || Float.isNaN(y[i])) {
|
return false;
|
}
|
prevX = x[i];
|
prevY = y[i];
|
}
|
return true;
|
}
|
|
private static boolean isValidMapping(float[] x, int[] y) {
|
if (x == null || y == null || x.length == 0 || y.length == 0) {
|
return false;
|
}
|
if (x.length != y.length) {
|
return false;
|
}
|
final int N = x.length;
|
float prevX = x[0];
|
int prevY = y[0];
|
if (prevX < 0 || prevY < 0 || Float.isNaN(prevX)) {
|
return false;
|
}
|
for (int i = 1; i < N; i++) {
|
if (prevX >= x[i] || prevY > y[i]) {
|
return false;
|
}
|
if (Float.isNaN(x[i])) {
|
return false;
|
}
|
prevX = x[i];
|
prevY = y[i];
|
}
|
return true;
|
}
|
|
/**
|
* Enable/disable logging.
|
*
|
* @param loggingEnabled
|
* Whether logging should be on/off.
|
*
|
* @return Whether the method succeeded or not.
|
*/
|
public boolean setLoggingEnabled(boolean loggingEnabled) {
|
if (mLoggingEnabled == loggingEnabled) {
|
return false;
|
}
|
mLoggingEnabled = loggingEnabled;
|
return true;
|
}
|
|
/**
|
* Sets the {@link BrightnessConfiguration}.
|
*
|
* @param config The new configuration. If {@code null} is passed, the default configuration is
|
* used.
|
* @return Whether the brightness configuration has changed.
|
*/
|
public abstract boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config);
|
|
/**
|
* Returns the desired brightness of the display based on the current ambient lux, including
|
* any context-related corrections.
|
*
|
* The returned brightness will be in the range [0, 1.0], where 1.0 is the display at max
|
* brightness and 0 is the display at minimum brightness.
|
*
|
* @param lux The current ambient brightness in lux.
|
* @param packageName the foreground app package name.
|
* @param category the foreground app package category.
|
* @return The desired brightness of the display normalized to the range [0, 1.0].
|
*/
|
public abstract float getBrightness(float lux, String packageName,
|
@ApplicationInfo.Category int category);
|
|
/**
|
* Returns the desired brightness of the display based on the current ambient lux.
|
*
|
* The returned brightness wil be in the range [0, 1.0], where 1.0 is the display at max
|
* brightness and 0 is the display at minimum brightness.
|
*
|
* @param lux The current ambient brightness in lux.
|
*
|
* @return The desired brightness of the display normalized to the range [0, 1.0].
|
*/
|
public float getBrightness(float lux) {
|
return getBrightness(lux, null /* packageName */, ApplicationInfo.CATEGORY_UNDEFINED);
|
}
|
|
/**
|
* Returns the current auto-brightness adjustment.
|
*
|
* The returned adjustment is a value in the range [-1.0, 1.0] such that
|
* {@code config_autoBrightnessAdjustmentMaxGamma<sup>-adjustment</sup>} is used to gamma
|
* correct the brightness curve.
|
*/
|
public abstract float getAutoBrightnessAdjustment();
|
|
/**
|
* Sets the auto-brightness adjustment.
|
*
|
* @param adjustment The desired auto-brightness adjustment.
|
* @return Whether the auto-brightness adjustment has changed.
|
*
|
* @Deprecated The auto-brightness adjustment should not be set directly, but rather inferred
|
* from user data points.
|
*/
|
public abstract boolean setAutoBrightnessAdjustment(float adjustment);
|
|
/**
|
* Converts the provided backlight value to nits if possible.
|
*
|
* Returns -1.0f if there's no available mapping for the backlight to nits.
|
*/
|
public abstract float convertToNits(int backlight);
|
|
/**
|
* Adds a user interaction data point to the brightness mapping.
|
*
|
* This data point <b>must</b> exist on the brightness curve as a result of this call. This is
|
* so that the next time we come to query what the screen brightness should be, we get what the
|
* user requested rather than immediately changing to some other value.
|
*
|
* Currently, we only keep track of one of these at a time to constrain what can happen to the
|
* curve.
|
*/
|
public abstract void addUserDataPoint(float lux, float brightness);
|
|
/**
|
* Removes any short term adjustments made to the curve from user interactions.
|
*
|
* Note that this does *not* reset the mapping to its initial state, any brightness
|
* configurations that have been applied will continue to be in effect. This solely removes the
|
* effects of user interactions on the model.
|
*/
|
public abstract void clearUserDataPoints();
|
|
/** @return True if there are any short term adjustments applied to the curve. */
|
public abstract boolean hasUserDataPoints();
|
|
/** @return True if the current brightness configuration is the default one. */
|
public abstract boolean isDefaultConfig();
|
|
/** @return The default brightness configuration. */
|
public abstract BrightnessConfiguration getDefaultConfig();
|
|
public abstract void dump(PrintWriter pw);
|
|
protected float normalizeAbsoluteBrightness(int brightness) {
|
brightness = MathUtils.constrain(brightness,
|
PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
|
return (float) brightness / PowerManager.BRIGHTNESS_ON;
|
}
|
|
private Pair<float[], float[]> insertControlPoint(
|
float[] luxLevels, float[] brightnessLevels, float lux, float brightness) {
|
final int idx = findInsertionPoint(luxLevels, lux);
|
final float[] newLuxLevels;
|
final float[] newBrightnessLevels;
|
if (idx == luxLevels.length) {
|
newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
|
newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
|
newLuxLevels[idx] = lux;
|
newBrightnessLevels[idx] = brightness;
|
} else if (luxLevels[idx] == lux) {
|
newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length);
|
newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length);
|
newBrightnessLevels[idx] = brightness;
|
} else {
|
newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
|
System.arraycopy(newLuxLevels, idx, newLuxLevels, idx+1, luxLevels.length - idx);
|
newLuxLevels[idx] = lux;
|
newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
|
System.arraycopy(newBrightnessLevels, idx, newBrightnessLevels, idx+1,
|
brightnessLevels.length - idx);
|
newBrightnessLevels[idx] = brightness;
|
}
|
smoothCurve(newLuxLevels, newBrightnessLevels, idx);
|
return Pair.create(newLuxLevels, newBrightnessLevels);
|
}
|
|
/**
|
* Returns the index of the first value that's less than or equal to {@code val}.
|
*
|
* This assumes that {@code arr} is sorted. If all values in {@code arr} are greater
|
* than val, then it will return the length of arr as the insertion point.
|
*/
|
private int findInsertionPoint(float[] arr, float val) {
|
for (int i = 0; i < arr.length; i++) {
|
if (val <= arr[i]) {
|
return i;
|
}
|
}
|
return arr.length;
|
}
|
|
private void smoothCurve(float[] lux, float[] brightness, int idx) {
|
if (mLoggingEnabled) {
|
PLOG.logCurve("unsmoothed curve", lux, brightness);
|
}
|
float prevLux = lux[idx];
|
float prevBrightness = brightness[idx];
|
// Smooth curve for data points above the newly introduced point
|
for (int i = idx+1; i < lux.length; i++) {
|
float currLux = lux[i];
|
float currBrightness = brightness[i];
|
float maxBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
|
float newBrightness = MathUtils.constrain(
|
currBrightness, prevBrightness, maxBrightness);
|
if (newBrightness == currBrightness) {
|
break;
|
}
|
prevLux = currLux;
|
prevBrightness = newBrightness;
|
brightness[i] = newBrightness;
|
}
|
// Smooth curve for data points below the newly introduced point
|
prevLux = lux[idx];
|
prevBrightness = brightness[idx];
|
for (int i = idx-1; i >= 0; i--) {
|
float currLux = lux[i];
|
float currBrightness = brightness[i];
|
float minBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
|
float newBrightness = MathUtils.constrain(
|
currBrightness, minBrightness, prevBrightness);
|
if (newBrightness == currBrightness) {
|
break;
|
}
|
prevLux = currLux;
|
prevBrightness = newBrightness;
|
brightness[i] = newBrightness;
|
}
|
if (mLoggingEnabled) {
|
PLOG.logCurve("smoothed curve", lux, brightness);
|
}
|
}
|
|
private float permissibleRatio(float currLux, float prevLux) {
|
return MathUtils.exp(MAX_GRAD
|
* (MathUtils.log(currLux + LUX_GRAD_SMOOTHING)
|
- MathUtils.log(prevLux + LUX_GRAD_SMOOTHING)));
|
}
|
|
protected float inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness,
|
float currentBrightness) {
|
float adjustment = 0;
|
float gamma = Float.NaN;
|
// Extreme edge cases: use a simpler heuristic, as proper gamma correction around the edges
|
// affects the curve rather drastically.
|
if (currentBrightness <= 0.1f || currentBrightness >= 0.9f) {
|
adjustment = (desiredBrightness - currentBrightness);
|
// Edge case: darkest adjustment possible.
|
} else if (desiredBrightness == 0) {
|
adjustment = -1;
|
// Edge case: brightest adjustment possible.
|
} else if (desiredBrightness == 1) {
|
adjustment = +1;
|
} else {
|
// current^gamma = desired => gamma = log[current](desired)
|
gamma = MathUtils.log(desiredBrightness) / MathUtils.log(currentBrightness);
|
// max^-adjustment = gamma => adjustment = -log[max](gamma)
|
adjustment = -MathUtils.log(gamma) / MathUtils.log(maxGamma);
|
}
|
adjustment = MathUtils.constrain(adjustment, -1, +1);
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "inferAutoBrightnessAdjustment: " + maxGamma + "^" + -adjustment + "=" +
|
MathUtils.pow(maxGamma, -adjustment) + " == " + gamma);
|
Slog.d(TAG, "inferAutoBrightnessAdjustment: " + currentBrightness + "^" + gamma + "=" +
|
MathUtils.pow(currentBrightness, gamma) + " == " + desiredBrightness);
|
}
|
return adjustment;
|
}
|
|
protected Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness,
|
float userLux, float userBrightness, float adjustment, float maxGamma) {
|
float[] newLux = lux;
|
float[] newBrightness = Arrays.copyOf(brightness, brightness.length);
|
if (mLoggingEnabled) {
|
PLOG.logCurve("unadjusted curve", newLux, newBrightness);
|
}
|
adjustment = MathUtils.constrain(adjustment, -1, 1);
|
float gamma = MathUtils.pow(maxGamma, -adjustment);
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "getAdjustedCurve: " + maxGamma + "^" + -adjustment + "=" +
|
MathUtils.pow(maxGamma, -adjustment) + " == " + gamma);
|
}
|
if (gamma != 1) {
|
for (int i = 0; i < newBrightness.length; i++) {
|
newBrightness[i] = MathUtils.pow(newBrightness[i], gamma);
|
}
|
}
|
if (mLoggingEnabled) {
|
PLOG.logCurve("gamma adjusted curve", newLux, newBrightness);
|
}
|
if (userLux != -1) {
|
Pair<float[], float[]> curve = insertControlPoint(newLux, newBrightness, userLux,
|
userBrightness);
|
newLux = curve.first;
|
newBrightness = curve.second;
|
if (mLoggingEnabled) {
|
PLOG.logCurve("gamma and user adjusted curve", newLux, newBrightness);
|
// This is done for comparison.
|
curve = insertControlPoint(lux, brightness, userLux, userBrightness);
|
PLOG.logCurve("user adjusted curve", curve.first ,curve.second);
|
}
|
}
|
return Pair.create(newLux, newBrightness);
|
}
|
|
/**
|
* A {@link BrightnessMappingStrategy} that maps from ambient room brightness directly to the
|
* backlight of the display.
|
*
|
* Since we don't have information about the display's physical brightness, any brightness
|
* configurations that are set are just ignored.
|
*/
|
private static class SimpleMappingStrategy extends BrightnessMappingStrategy {
|
// Lux control points
|
private final float[] mLux;
|
// Brightness control points normalized to [0, 1]
|
private final float[] mBrightness;
|
|
private Spline mSpline;
|
private float mMaxGamma;
|
private float mAutoBrightnessAdjustment;
|
private float mUserLux;
|
private float mUserBrightness;
|
|
public SimpleMappingStrategy(float[] lux, int[] brightness, float maxGamma) {
|
Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
|
"Lux and brightness arrays must not be empty!");
|
Preconditions.checkArgument(lux.length == brightness.length,
|
"Lux and brightness arrays must be the same length!");
|
Preconditions.checkArrayElementsInRange(lux, 0, Float.MAX_VALUE, "lux");
|
Preconditions.checkArrayElementsInRange(brightness,
|
0, Integer.MAX_VALUE, "brightness");
|
|
final int N = brightness.length;
|
mLux = new float[N];
|
mBrightness = new float[N];
|
for (int i = 0; i < N; i++) {
|
mLux[i] = lux[i];
|
mBrightness[i] = normalizeAbsoluteBrightness(brightness[i]);
|
}
|
|
mMaxGamma = maxGamma;
|
mAutoBrightnessAdjustment = 0;
|
mUserLux = -1;
|
mUserBrightness = -1;
|
if (mLoggingEnabled) {
|
PLOG.start("simple mapping strategy");
|
}
|
computeSpline();
|
}
|
|
@Override
|
public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
|
return false;
|
}
|
|
@Override
|
public float getBrightness(float lux, String packageName,
|
@ApplicationInfo.Category int category) {
|
return mSpline.interpolate(lux);
|
}
|
|
@Override
|
public float getAutoBrightnessAdjustment() {
|
return mAutoBrightnessAdjustment;
|
}
|
|
@Override
|
public boolean setAutoBrightnessAdjustment(float adjustment) {
|
adjustment = MathUtils.constrain(adjustment, -1, 1);
|
if (adjustment == mAutoBrightnessAdjustment) {
|
return false;
|
}
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " +
|
adjustment);
|
PLOG.start("auto-brightness adjustment");
|
}
|
mAutoBrightnessAdjustment = adjustment;
|
computeSpline();
|
return true;
|
}
|
|
@Override
|
public float convertToNits(int backlight) {
|
return -1.0f;
|
}
|
|
@Override
|
public void addUserDataPoint(float lux, float brightness) {
|
float unadjustedBrightness = getUnadjustedBrightness(lux);
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")");
|
PLOG.start("add user data point")
|
.logPoint("user data point", lux, brightness)
|
.logPoint("current brightness", lux, unadjustedBrightness);
|
}
|
float adjustment = inferAutoBrightnessAdjustment(mMaxGamma,
|
brightness /* desiredBrightness */,
|
unadjustedBrightness /* currentBrightness */);
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " +
|
adjustment);
|
}
|
mAutoBrightnessAdjustment = adjustment;
|
mUserLux = lux;
|
mUserBrightness = brightness;
|
computeSpline();
|
}
|
|
@Override
|
public void clearUserDataPoints() {
|
if (mUserLux != -1) {
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0");
|
PLOG.start("clear user data points")
|
.logPoint("user data point", mUserLux, mUserBrightness);
|
}
|
mAutoBrightnessAdjustment = 0;
|
mUserLux = -1;
|
mUserBrightness = -1;
|
computeSpline();
|
}
|
}
|
|
@Override
|
public boolean hasUserDataPoints() {
|
return mUserLux != -1;
|
}
|
|
@Override
|
public boolean isDefaultConfig() {
|
return true;
|
}
|
|
@Override
|
public BrightnessConfiguration getDefaultConfig() {
|
return null;
|
}
|
|
@Override
|
public void dump(PrintWriter pw) {
|
pw.println("SimpleMappingStrategy");
|
pw.println(" mSpline=" + mSpline);
|
pw.println(" mMaxGamma=" + mMaxGamma);
|
pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
|
pw.println(" mUserLux=" + mUserLux);
|
pw.println(" mUserBrightness=" + mUserBrightness);
|
}
|
|
private void computeSpline() {
|
Pair<float[], float[]> curve = getAdjustedCurve(mLux, mBrightness, mUserLux,
|
mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
|
mSpline = Spline.createSpline(curve.first, curve.second);
|
}
|
|
private float getUnadjustedBrightness(float lux) {
|
Spline spline = Spline.createSpline(mLux, mBrightness);
|
return spline.interpolate(lux);
|
}
|
}
|
|
/** A {@link BrightnessMappingStrategy} that maps from ambient room brightness to the physical
|
* range of the display, rather than to the range of the backlight control (typically 0-255).
|
*
|
* By mapping through the physical brightness, the curve becomes portable across devices and
|
* gives us more resolution in the resulting mapping.
|
*/
|
@VisibleForTesting
|
static class PhysicalMappingStrategy extends BrightnessMappingStrategy {
|
// The current brightness configuration.
|
private BrightnessConfiguration mConfig;
|
|
// A spline mapping from the current ambient light in lux to the desired display brightness
|
// in nits.
|
private Spline mBrightnessSpline;
|
|
// A spline mapping from nits to the corresponding backlight value, normalized to the range
|
// [0, 1.0].
|
private final Spline mNitsToBacklightSpline;
|
|
// The default brightness configuration.
|
private final BrightnessConfiguration mDefaultConfig;
|
|
// A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to
|
// a brightness in nits.
|
private Spline mBacklightToNitsSpline;
|
|
private float mMaxGamma;
|
private float mAutoBrightnessAdjustment;
|
private float mUserLux;
|
private float mUserBrightness;
|
|
public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
|
int[] backlight, float maxGamma) {
|
Preconditions.checkArgument(nits.length != 0 && backlight.length != 0,
|
"Nits and backlight arrays must not be empty!");
|
Preconditions.checkArgument(nits.length == backlight.length,
|
"Nits and backlight arrays must be the same length!");
|
Preconditions.checkNotNull(config);
|
Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits");
|
Preconditions.checkArrayElementsInRange(backlight,
|
PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON, "backlight");
|
|
mMaxGamma = maxGamma;
|
mAutoBrightnessAdjustment = 0;
|
mUserLux = -1;
|
mUserBrightness = -1;
|
|
// Setup the backlight spline
|
final int N = nits.length;
|
float[] normalizedBacklight = new float[N];
|
for (int i = 0; i < N; i++) {
|
normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]);
|
}
|
|
mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight);
|
mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
|
|
mDefaultConfig = config;
|
if (mLoggingEnabled) {
|
PLOG.start("physical mapping strategy");
|
}
|
mConfig = config;
|
computeSpline();
|
}
|
|
@Override
|
public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
|
if (config == null) {
|
config = mDefaultConfig;
|
}
|
if (config.equals(mConfig)) {
|
return false;
|
}
|
if (mLoggingEnabled) {
|
PLOG.start("brightness configuration");
|
}
|
mConfig = config;
|
computeSpline();
|
return true;
|
}
|
|
@Override
|
public float getBrightness(float lux, String packageName,
|
@ApplicationInfo.Category int category) {
|
float nits = mBrightnessSpline.interpolate(lux);
|
float backlight = mNitsToBacklightSpline.interpolate(nits);
|
// Correct the brightness according to the current application and its category, but
|
// only if no user data point is set (as this will oevrride the user setting).
|
if (mUserLux == -1) {
|
backlight = correctBrightness(backlight, packageName, category);
|
} else if (mLoggingEnabled) {
|
Slog.d(TAG, "user point set, correction not applied");
|
}
|
return backlight;
|
}
|
|
@Override
|
public float getAutoBrightnessAdjustment() {
|
return mAutoBrightnessAdjustment;
|
}
|
|
@Override
|
public boolean setAutoBrightnessAdjustment(float adjustment) {
|
adjustment = MathUtils.constrain(adjustment, -1, 1);
|
if (adjustment == mAutoBrightnessAdjustment) {
|
return false;
|
}
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " +
|
adjustment);
|
PLOG.start("auto-brightness adjustment");
|
}
|
mAutoBrightnessAdjustment = adjustment;
|
computeSpline();
|
return true;
|
}
|
|
@Override
|
public float convertToNits(int backlight) {
|
return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight));
|
}
|
|
@Override
|
public void addUserDataPoint(float lux, float brightness) {
|
float unadjustedBrightness = getUnadjustedBrightness(lux);
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")");
|
PLOG.start("add user data point")
|
.logPoint("user data point", lux, brightness)
|
.logPoint("current brightness", lux, unadjustedBrightness);
|
}
|
float adjustment = inferAutoBrightnessAdjustment(mMaxGamma,
|
brightness /* desiredBrightness */,
|
unadjustedBrightness /* currentBrightness */);
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " +
|
adjustment);
|
}
|
mAutoBrightnessAdjustment = adjustment;
|
mUserLux = lux;
|
mUserBrightness = brightness;
|
computeSpline();
|
}
|
|
@Override
|
public void clearUserDataPoints() {
|
if (mUserLux != -1) {
|
if (mLoggingEnabled) {
|
Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0");
|
PLOG.start("clear user data points")
|
.logPoint("user data point", mUserLux, mUserBrightness);
|
}
|
mAutoBrightnessAdjustment = 0;
|
mUserLux = -1;
|
mUserBrightness = -1;
|
computeSpline();
|
}
|
}
|
|
@Override
|
public boolean hasUserDataPoints() {
|
return mUserLux != -1;
|
}
|
|
@Override
|
public boolean isDefaultConfig() {
|
return mDefaultConfig.equals(mConfig);
|
}
|
|
@Override
|
public BrightnessConfiguration getDefaultConfig() {
|
return mDefaultConfig;
|
}
|
|
@Override
|
public void dump(PrintWriter pw) {
|
pw.println("PhysicalMappingStrategy");
|
pw.println(" mConfig=" + mConfig);
|
pw.println(" mBrightnessSpline=" + mBrightnessSpline);
|
pw.println(" mNitsToBacklightSpline=" + mNitsToBacklightSpline);
|
pw.println(" mMaxGamma=" + mMaxGamma);
|
pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
|
pw.println(" mUserLux=" + mUserLux);
|
pw.println(" mUserBrightness=" + mUserBrightness);
|
pw.println(" mDefaultConfig=" + mDefaultConfig);
|
}
|
|
private void computeSpline() {
|
Pair<float[], float[]> defaultCurve = mConfig.getCurve();
|
float[] defaultLux = defaultCurve.first;
|
float[] defaultNits = defaultCurve.second;
|
float[] defaultBacklight = new float[defaultNits.length];
|
for (int i = 0; i < defaultBacklight.length; i++) {
|
defaultBacklight[i] = mNitsToBacklightSpline.interpolate(defaultNits[i]);
|
}
|
Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBacklight, mUserLux,
|
mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
|
float[] lux = curve.first;
|
float[] backlight = curve.second;
|
float[] nits = new float[backlight.length];
|
for (int i = 0; i < nits.length; i++) {
|
nits[i] = mBacklightToNitsSpline.interpolate(backlight[i]);
|
}
|
mBrightnessSpline = Spline.createSpline(lux, nits);
|
}
|
|
private float getUnadjustedBrightness(float lux) {
|
Pair<float[], float[]> curve = mConfig.getCurve();
|
Spline spline = Spline.createSpline(curve.first, curve.second);
|
return mNitsToBacklightSpline.interpolate(spline.interpolate(lux));
|
}
|
|
private float correctBrightness(float brightness, String packageName, int category) {
|
if (packageName != null) {
|
BrightnessCorrection correction = mConfig.getCorrectionByPackageName(packageName);
|
if (correction != null) {
|
return correction.apply(brightness);
|
}
|
}
|
if (category != ApplicationInfo.CATEGORY_UNDEFINED) {
|
BrightnessCorrection correction = mConfig.getCorrectionByCategory(category);
|
if (correction != null) {
|
return correction.apply(brightness);
|
}
|
}
|
return brightness;
|
}
|
}
|
}
|