diff --git a/api/current.txt b/api/current.txt index 9b5481407d5ac..860ae0907f3c1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22876,6 +22876,7 @@ package android.util { method public static float cos(float); method public static float exp(float); method public static float floor(float); + method public static float hypot(float, float); method public static float sin(float); method public static float sqrt(float); } diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java index 1d4eda41473c7..e05169ad6ddfb 100644 --- a/core/java/android/util/FloatMath.java +++ b/core/java/android/util/FloatMath.java @@ -80,4 +80,14 @@ public class FloatMath { * @return the exponential of value */ public static native float exp(float value); + + /** + * Returns {@code sqrt(}{@code x}{@code 2}{@code +} + * {@code y}{@code 2}{@code )}. + * + * @param x a float number + * @param y a float number + * @return the hypotenuse + */ + public static native float hypot(float x, float y); } diff --git a/core/java/android/util/Spline.java b/core/java/android/util/Spline.java new file mode 100644 index 0000000000000..ed027eb18166e --- /dev/null +++ b/core/java/android/util/Spline.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2012 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 android.util; + +/** + * Performs spline interpolation given a set of control points. + * @hide + */ +public final class Spline { + private final float[] mX; + private final float[] mY; + private final float[] mM; + + private Spline(float[] x, float[] y, float[] m) { + mX = x; + mY = y; + mM = m; + } + + /** + * Creates a monotone cubic spline from a given set of control points. + * + * The spline is guaranteed to pass through each control point exactly. + * Moreover, assuming the control points are monotonic (Y is non-decreasing or + * non-increasing) then the interpolated values will also be monotonic. + * + * This function uses the Fritsch-Carlson method for computing the spline parameters. + * http://en.wikipedia.org/wiki/Monotone_cubic_interpolation + * + * @param x The X component of the control points, strictly increasing. + * @param y The Y component of the control points, monotonic. + * @return + * + * @throws IllegalArgumentException if the X or Y arrays are null, have + * different lengths or have fewer than 2 values. + * @throws IllegalArgumentException if the control points are not monotonic. + */ + public static Spline createMonotoneCubicSpline(float[] x, float[] y) { + if (x == null || y == null || x.length != y.length || x.length < 2) { + throw new IllegalArgumentException("There must be at least two control " + + "points and the arrays must be of equal length."); + } + + final int n = x.length; + float[] d = new float[n - 1]; // could optimize this out + float[] m = new float[n]; + + // Compute slopes of secant lines between successive points. + for (int i = 0; i < n - 1; i++) { + float h = x[i + 1] - x[i]; + if (h <= 0f) { + throw new IllegalArgumentException("The control points must all " + + "have strictly increasing X values."); + } + d[i] = (y[i + 1] - y[i]) / h; + } + + // Initialize the tangents as the average of the secants. + m[0] = d[0]; + for (int i = 1; i < n - 1; i++) { + m[i] = (d[i - 1] + d[i]) * 0.5f; + } + m[n - 1] = d[n - 2]; + + // Update the tangents to preserve monotonicity. + for (int i = 0; i < n - 1; i++) { + if (d[i] == 0f) { // successive Y values are equal + m[i] = 0f; + m[i + 1] = 0f; + } else { + float a = m[i] / d[i]; + float b = m[i + 1] / d[i]; + if (a < 0f || b < 0f) { + throw new IllegalArgumentException("The control points must have " + + "monotonic Y values."); + } + float h = FloatMath.hypot(a, b); + if (h > 9f) { + float t = 3f / h; + m[i] = t * a * d[i]; + m[i + 1] = t * b * d[i]; + } + } + } + return new Spline(x, y, m); + } + + /** + * Interpolates the value of Y = f(X) for given X. + * Clamps X to the domain of the spline. + * + * @param x The X value. + * @return The interpolated Y = f(X) value. + */ + public float interpolate(float x) { + // Handle the boundary cases. + final int n = mX.length; + if (Float.isNaN(x)) { + return x; + } + if (x <= mX[0]) { + return mY[0]; + } + if (x >= mX[n - 1]) { + return mY[n - 1]; + } + + // Find the index 'i' of the last point with smaller X. + // We know this will be within the spline due to the boundary tests. + int i = 0; + while (x >= mX[i + 1]) { + i += 1; + if (x == mX[i]) { + return mY[i]; + } + } + + // Perform cubic Hermite spline interpolation. + float h = mX[i + 1] - mX[i]; + float t = (x - mX[i]) / h; + return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t) + + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t; + } + + // For debugging. + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + final int n = mX.length; + str.append("["); + for (int i = 0; i < n; i++) { + if (i != 0) { + str.append(", "); + } + str.append("(").append(mX[i]); + str.append(", ").append(mY[i]); + str.append(": ").append(mM[i]).append(")"); + } + str.append("]"); + return str.toString(); + } +} diff --git a/core/jni/android_util_FloatMath.cpp b/core/jni/android_util_FloatMath.cpp index e30756b5dee3b..529fbe9b64a88 100644 --- a/core/jni/android_util_FloatMath.cpp +++ b/core/jni/android_util_FloatMath.cpp @@ -29,6 +29,10 @@ public: static float ExpF(JNIEnv* env, jobject clazz, float x) { return expf(x); } + + static float HypotF(JNIEnv* env, jobject clazz, float x, float y) { + return hypotf(x, y); + } }; static JNINativeMethod gMathUtilsMethods[] = { @@ -38,6 +42,7 @@ static JNINativeMethod gMathUtilsMethods[] = { {"cos", "(F)F", (void*) MathUtilsGlue::CosF}, {"sqrt", "(F)F", (void*) MathUtilsGlue::SqrtF}, {"exp", "(F)F", (void*) MathUtilsGlue::ExpF}, + {"hypot", "(FF)F", (void*) MathUtilsGlue::HypotF}, }; int register_android_util_FloatMath(JNIEnv* env) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ee0ff8ed04500..e3c957beb7e94 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -533,13 +533,22 @@ 2 @@ -552,6 +561,7 @@ @@ -559,6 +569,7 @@ @@ -566,6 +577,7 @@ diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index 54e2b574a59c8..b0c79fa13d95c 100644 --- a/services/java/com/android/server/power/DisplayPowerController.java +++ b/services/java/com/android/server/power/DisplayPowerController.java @@ -33,11 +33,11 @@ import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.util.Slog; +import android.util.Spline; import android.util.TimeUtils; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -98,9 +98,9 @@ final class DisplayPowerController { // average of light samples. Different constants are used // to calculate the average light level when adapting to brighter or // dimmer environments. - // This parameter only controls the averaging of light samples. - private static final long BRIGHTENING_LIGHT_TIME_CONSTANT = 1500; - private static final long DIMMING_LIGHT_TIME_CONSTANT = 3000; + // This parameter only controls the filtering of light samples. + private static final long BRIGHTENING_LIGHT_TIME_CONSTANT = 500; + private static final long DIMMING_LIGHT_TIME_CONSTANT = 2000; // Stability requirements in milliseconds for accepting a new brightness // level. This is used for debouncing the light sensor. Different constants @@ -144,8 +144,7 @@ final class DisplayPowerController { // Auto-brightness. private boolean mUseSoftwareAutoBrightnessConfig; - private int[] mAutoBrightnessLevelsConfig; - private int[] mAutoBrightnessLcdBacklightValuesConfig; + private Spline mScreenAutoBrightnessSpline; // Amount of time to delay auto-brightness after screen on while waiting for // the light sensor to warm-up in milliseconds. @@ -289,17 +288,18 @@ final class DisplayPowerController { mUseSoftwareAutoBrightnessConfig = resources.getBoolean( com.android.internal.R.bool.config_automatic_brightness_available); if (mUseSoftwareAutoBrightnessConfig) { - mAutoBrightnessLevelsConfig = resources.getIntArray( + int[] lux = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels); - mAutoBrightnessLcdBacklightValuesConfig = resources.getIntArray( + int[] screenBrightness = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); - if (mAutoBrightnessLcdBacklightValuesConfig.length - != mAutoBrightnessLevelsConfig.length + 1) { + + mScreenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness); + if (mScreenAutoBrightnessSpline == null) { Slog.e(TAG, "Error in config.xml. config_autoBrightnessLcdBacklightValues " - + "(size " + mAutoBrightnessLcdBacklightValuesConfig.length + ") " - + "should have exactly one more entry than " - + "config_autoBrightnessLevels (size " - + mAutoBrightnessLevelsConfig.length + "). " + + "(size " + screenBrightness.length + ") " + + "must be monotic and have exactly one more entry than " + + "config_autoBrightnessLevels (size " + lux.length + ") " + + "which must be strictly increasing. " + "Auto-brightness will be disabled."); mUseSoftwareAutoBrightnessConfig = false; } @@ -322,6 +322,31 @@ final class DisplayPowerController { } } + private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) { + try { + final int n = brightness.length; + float[] x = new float[n]; + float[] y = new float[n]; + y[0] = brightness[0]; + for (int i = 1; i < n; i++) { + x[i] = lux[i - 1]; + y[i] = brightness[i]; + } + + Spline spline = Spline.createMonotoneCubicSpline(x, y); + if (false) { + Slog.d(TAG, "Auto-brightness spline: " + spline); + for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) { + Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v))); + } + } + return spline; + } catch (IllegalArgumentException ex) { + Slog.e(TAG, "Could not create auto-brightness spline.", ex); + return null; + } + } + /** * Returns true if the proximity sensor screen-off function is available. */ @@ -768,13 +793,13 @@ final class DisplayPowerController { return; } - final int newScreenAutoBrightness = mapLuxToBrightness(mLightMeasurement, - mAutoBrightnessLevelsConfig, - mAutoBrightnessLcdBacklightValuesConfig); + final int newScreenAutoBrightness = interpolateBrightness( + mScreenAutoBrightnessSpline, mLightMeasurement); if (mScreenAutoBrightness != newScreenAutoBrightness) { if (DEBUG) { Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness=" - + mScreenAutoBrightness); + + mScreenAutoBrightness + "newScreenAutoBrightness=" + + newScreenAutoBrightness); } mScreenAutoBrightness = newScreenAutoBrightness; @@ -784,20 +809,8 @@ final class DisplayPowerController { } } - /** - * Maps a light sensor measurement in lux to a brightness value given - * a table of lux breakpoint values and a table of brightnesses that - * is one element larger. - */ - private static int mapLuxToBrightness(float lux, - int[] fromLux, int[] toBrightness) { - // TODO implement interpolation and possibly range expansion - int level = 0; - final int count = fromLux.length; - while (level < count && lux >= fromLux[level]) { - level += 1; - } - return toBrightness[level]; + private static int interpolateBrightness(Spline spline, float lux) { + return Math.min(255, Math.max(0, (int)Math.round(spline.interpolate(lux)))); } private void sendOnStateChanged() { @@ -839,10 +852,7 @@ final class DisplayPowerController { pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig); pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig); - pw.println(" mAutoBrightnessLevelsConfig=" - + Arrays.toString(mAutoBrightnessLevelsConfig)); - pw.println(" mAutoBrightnessLcdBacklightValuesConfig=" - + Arrays.toString(mAutoBrightnessLcdBacklightValuesConfig)); + pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline); pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); if (Looper.myLooper() == mHandler.getLooper()) {