DO NOT MERGE ANYWHERE: Add dynamic illuminance hysteresis support

Bug: 18572096

Change-Id: Ie0ff1990b8f4a3d435328834871f04a6e2bd5e97
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f745b52..b2d53c3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1082,6 +1082,40 @@
     <integer-array name="config_autoBrightnessKeyboardBacklightValues">
     </integer-array>
 
+    <!-- Array of hysteresis constraint values for brightening, represented as tenths of a
+         percent. The length of this array is assumed to be one greater than
+         config_dynamicHysteresisLuxLevels. The brightening threshold is calculated as
+         lux * (1.0f + CONSTRAINT_VALUE). When the current lux is higher than this threshold,
+         the screen brightness is recalculated. See the config_dynamicHysteresisLuxLevels
+         description for how the constraint value is chosen. -->
+    <integer-array name="config_dynamicHysteresisBrightLevels">
+        <item>100</item>
+    </integer-array>
+
+    <!-- Array of hysteresis constraint values for darkening, represented as tenths of a
+         percent. The length of this array is assumed to be one greater than
+         config_dynamicHysteresisLuxLevels. The darkening threshold is calculated as
+         lux * (1.0f - CONSTRAINT_VALUE). When the current lux is lower than this threshold,
+         the screen brightness is recalculated. See the config_dynamicHysteresisLuxLevels
+         description for how the constraint value is chosen. -->
+    <integer-array name="config_dynamicHysteresisDarkLevels">
+        <item>200</item>
+    </integer-array>
+
+    <!-- Array of ambient lux threshold values. This is used for determining hysteresis constraint
+         values by calculating the index to use for lookup and then setting the constraint value
+         to the corresponding value of the array. The new brightening hysteresis constraint value
+         is the n-th element of config_dynamicHysteresisBrightLevels, and the new darkening
+         hysteresis constraint value is the n-th element of config_dynamicHysteresisDarkLevels.
+
+         The (zero-based) index is calculated as follows: (MAX is the largest index of the array)
+         condition                      calculated index
+         value < lux[0]                 0
+         lux[n] <= value < lux[n+1]     n+1
+         lux[MAX] <= value              MAX+1 -->
+    <integer-array name="config_dynamicHysteresisLuxLevels">
+    </integer-array>
+
     <!-- Amount of time it takes for the light sensor to warm up in milliseconds.
          For this time after the screen turns on, the Power Manager
          will not debounce light sensor readings -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4fcc1d3..89823a3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1607,6 +1607,9 @@
   <java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" />
   <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" />
   <java-symbol type="array" name="config_autoBrightnessLevels" />
+  <java-symbol type="array" name="config_dynamicHysteresisBrightLevels" />
+  <java-symbol type="array" name="config_dynamicHysteresisDarkLevels" />
+  <java-symbol type="array" name="config_dynamicHysteresisLuxLevels" />
   <java-symbol type="array" name="config_protectedNetworks" />
   <java-symbol type="array" name="config_statusBarIcons" />
   <java-symbol type="array" name="config_tether_bluetooth_regexs" />
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index e15bca6..d9444a5 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -56,12 +56,6 @@
     // Period of time in which to consider light samples in milliseconds.
     private static final int AMBIENT_LIGHT_HORIZON = 10000;
 
-    // Hysteresis constraints for brightening or darkening.
-    // The recent lux must have changed by at least this fraction relative to the
-    // current ambient lux before a change will be considered.
-    private static final float BRIGHTENING_LIGHT_HYSTERESIS = 0.10f;
-    private static final float DARKENING_LIGHT_HYSTERESIS = 0.20f;
-
     // The intercept used for the weighting calculation. This is used in order to keep all possible
     // weighting values positive.
     private static final int WEIGHTING_INTERCEPT = AMBIENT_LIGHT_HORIZON;
@@ -95,7 +89,7 @@
     private static final int MSG_UPDATE_AMBIENT_LUX = 1;
     private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
 
-    // Callbacks for requesting updates to the the display's power state
+    // Callbacks for requesting updates to the display's power state
     private final Callbacks mCallbacks;
 
     // The sensor manager.
@@ -132,6 +126,9 @@
     // and only then decide whether to change brightness.
     private final boolean mResetAmbientLuxAfterWarmUpConfig;
 
+    // accessor object for determining thresholds to change brightness dynamically
+    private final HysteresisLevels mDynamicHysteresis;
+
     // Amount of time to delay auto-brightness after screen on while waiting for
     // the light sensor to warm-up in milliseconds.
     // May be 0 if no warm-up is required.
@@ -197,7 +194,8 @@
             SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime,
             int brightnessMin, int brightnessMax, float dozeScaleFactor,
             int lightSensorRate, long brighteningLightDebounceConfig,
-            long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig) {
+            long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
+            HysteresisLevels dynamicHysteresis) {
         mCallbacks = callbacks;
         mTwilight = LocalServices.getService(TwilightManager.class);
         mSensorManager = sensorManager;
@@ -210,6 +208,7 @@
         mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
         mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
         mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
+        mDynamicHysteresis = dynamicHysteresis;
 
         mHandler = new AutomaticBrightnessHandler(looper);
         mAmbientLightRingBuffer = new AmbientLightRingBuffer(mLightSensorRate);
@@ -327,8 +326,8 @@
 
     private void setAmbientLux(float lux) {
         mAmbientLux = lux;
-        mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
-        mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
+        mBrighteningLuxThreshold = mDynamicHysteresis.getBrighteningThreshold(lux);
+        mDarkeningLuxThreshold = mDynamicHysteresis.getDarkeningThreshold(lux);
     }
 
     private float calculateAmbientLux(long now) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 78fa343..3f0b8b0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -72,7 +72,7 @@
     private static final String TAG = "DisplayPowerController";
     private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
 
-    private static boolean DEBUG = false;
+    private static final boolean DEBUG = false;
     private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
 
     // If true, uses the color fade on animation.
@@ -317,6 +317,15 @@
         boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
                 com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
 
+        int[] brightLevels = resources.getIntArray(
+                com.android.internal.R.array.config_dynamicHysteresisBrightLevels);
+        int[] darkLevels = resources.getIntArray(
+                com.android.internal.R.array.config_dynamicHysteresisDarkLevels);
+        int[] luxLevels = resources.getIntArray(
+                com.android.internal.R.array.config_dynamicHysteresisLuxLevels);
+        HysteresisLevels dynamicHysteresis = new HysteresisLevels(
+                brightLevels, darkLevels, luxLevels);
+
         if (mUseSoftwareAutoBrightnessConfig) {
             int[] lux = resources.getIntArray(
                     com.android.internal.R.array.config_autoBrightnessLevels);
@@ -353,7 +362,7 @@
                         lightSensorWarmUpTimeConfig, screenBrightnessRangeMinimum,
                         mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate,
                         brighteningLightDebounce, darkeningLightDebounce,
-                        autoBrightnessResetAmbientLuxAfterWarmUp);
+                        autoBrightnessResetAmbientLuxAfterWarmUp, dynamicHysteresis);
             }
         }
 
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
new file mode 100644
index 0000000..b062225
--- /dev/null
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 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.util.Slog;
+
+/**
+ * A helper class for handling access to illuminance hysteresis level values.
+ */
+final class HysteresisLevels {
+    private static final String TAG = "HysteresisLevels";
+
+    // Default hysteresis constraints for brightening or darkening.
+    // The recent lux must have changed by at least this fraction relative to the
+    // current ambient lux before a change will be considered.
+    private static final float DEFAULT_BRIGHTENING_HYSTERESIS = 0.10f;
+    private static final float DEFAULT_DARKENING_HYSTERESIS = 0.20f;
+
+    private static final boolean DEBUG = false;
+
+    private final float[] mBrightLevels;
+    private final float[] mDarkLevels;
+    private final float[] mLuxLevels;
+
+  /**
+   * Creates a {@code HysteresisLevels} object with the given equal-length
+   * integer arrays.
+   * @param brightLevels an array of brightening hysteresis constraint constants
+   * @param darkLevels an array of darkening hysteresis constraint constants
+   * @param luxLevels a monotonically increasing array of illuminance
+   *                  thresholds in units of lux
+   */
+    public HysteresisLevels(int[] brightLevels, int[] darkLevels, int[] luxLevels) {
+        if (brightLevels.length != darkLevels.length || darkLevels.length != luxLevels.length + 1) {
+            throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
+        }
+        mBrightLevels = setArrayFormat(brightLevels, 1000.0f);
+        mDarkLevels = setArrayFormat(darkLevels, 1000.0f);
+        mLuxLevels = setArrayFormat(luxLevels, 1.0f);
+    }
+
+    /**
+     * Return the brightening hysteresis threshold for the given lux level.
+     */
+    public float getBrighteningThreshold(float lux) {
+        float brightConstant = getReferenceLevel(lux, mBrightLevels);
+        float brightThreshold = lux * (1.0f + brightConstant);
+        if (DEBUG) {
+            Slog.d(TAG, "bright hysteresis constant=: " + brightConstant + ", threshold="
+                + brightThreshold + ", lux=" + lux);
+        }
+        return brightThreshold;
+    }
+
+    /**
+     * Return the darkening hysteresis threshold for the given lux level.
+     */
+    public float getDarkeningThreshold(float lux) {
+        float darkConstant = getReferenceLevel(lux, mDarkLevels);
+        float darkThreshold = lux * (1.0f - darkConstant);
+        if (DEBUG) {
+            Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
+                + darkThreshold + ", lux=" + lux);
+        }
+        return darkThreshold;
+    }
+
+    /**
+     * Return the hysteresis constant for the closest lux threshold value to the
+     * current illuminance from the given array.
+     */
+    private float getReferenceLevel(float lux, float[] referenceLevels) {
+        int index = 0;
+        while (mLuxLevels.length > index && lux >= mLuxLevels[index]) {
+            ++index;
+        }
+        return referenceLevels[index];
+    }
+
+    /**
+     * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
+     */
+    private float[] setArrayFormat(int[] configArray, float divideFactor) {
+        float[] levelArray = new float[configArray.length];
+        for (int index = 0; levelArray.length > index; ++index) {
+            levelArray[index] = (float)configArray[index] / divideFactor;
+        }
+        return levelArray;
+    }
+}