Set fixed fps when ambient or display brightness is high

Force the display to stay at fixed fps when ambient and
display brightness are high.

Bug: 166581675
Test: atest DisplayModeDirectorTest
Test: no fps transition in the blocking zone
Change-Id: Idf0dfe0b9ef907a4158f36f6dfda19286206fb3f
Merged-In: Idf0dfe0b9ef907a4158f36f6dfda19286206fb3f
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 550601a..5f02eb6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4138,6 +4138,35 @@
          If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
     <integer name="config_defaultRefreshRateInZone">0</integer>
 
+    <!-- The display uses different gamma curves for different refresh rates. It's hard for panel
+         vendor to tune the curves to have exact same brightness for different refresh rate. So
+         flicker could be observed at switch time. The issue can be observed on the screen with
+         even full white content at the high brightness. To prevent flickering, we support fixed
+         refresh rates if the display and ambient brightness are equal to or above the provided
+         thresholds. You can define multiple threshold levels as higher brightness environments
+         may have lower display brightness requirements for the flickering is visible. And the
+         high brightness environment could have higher threshold.
+         For example, fixed refresh rate if
+             display brightness >= disp0 && ambient brightness >= amb0
+             || display brightness >= disp1 && ambient brightness >= amb1 -->
+    <integer-array translatable="false" name="config_highDisplayBrightnessThresholdsOfFixedRefreshRate">
+         <!--
+           <item>disp0</item>
+           <item>disp1</item>
+        -->
+    </integer-array>
+
+    <integer-array translatable="false" name="config_highAmbientBrightnessThresholdsOfFixedRefreshRate">
+         <!--
+           <item>amb0</item>
+           <item>amb1</item>
+        -->
+    </integer-array>
+
+    <!-- Default refresh rate in the high zone defined by brightness and ambient thresholds.
+         If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
+    <integer name="config_fixedRefreshRateInHighZone">0</integer>
+
     <!-- The type of the light sensor to be used by the display framework for things like
          auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. -->
     <string name="config_displayLightSensorType" translatable="false" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 06f357e..b80e8e1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3783,6 +3783,11 @@
   <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
   <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
 
+  <!-- For fixed refresh rate displays in high brightness-->
+  <java-symbol type="integer" name="config_fixedRefreshRateInHighZone" />
+  <java-symbol type="array" name="config_highDisplayBrightnessThresholdsOfFixedRefreshRate" />
+  <java-symbol type="array" name="config_highAmbientBrightnessThresholdsOfFixedRefreshRate" />
+
   <!-- For Auto-Brightness -->
   <java-symbol type="string" name="config_displayLightSensorType" />
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 4bba0d8..7585d6b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -1,4 +1,11 @@
 filegroup {
+    name: "services.core-sources-deviceconfig-interface",
+    srcs: [
+         "java/com/android/server/utils/DeviceConfigInterface.java"
+    ],
+}
+
+filegroup {
     name: "services.core-sources",
     srcs: ["java/**/*.java"],
     path: "java",
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 3c05080..2a60839a 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -46,8 +46,10 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.utils.DeviceConfigInterface;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -64,9 +66,9 @@
     private static final boolean DEBUG = false;
 
     private static final int MSG_REFRESH_RATE_RANGE_CHANGED = 1;
-    private static final int MSG_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
+    private static final int MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
     private static final int MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED = 3;
-    private static final int MSG_REFRESH_RATE_IN_ZONE_CHANGED = 4;
+    private static final int MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED = 4;
 
     // Special ID used to indicate that given vote is to be applied globally, rather than to a
     // specific display.
@@ -79,6 +81,13 @@
     private final Context mContext;
 
     private final DisplayModeDirectorHandler mHandler;
+    private final Injector mInjector;
+
+    private final AppRequestObserver mAppRequestObserver;
+    private final SettingsObserver mSettingsObserver;
+    private final DisplayObserver mDisplayObserver;
+    private final DeviceConfigInterface mDeviceConfig;
+    private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
 
     // A map from the display ID to the collection of votes and their priority. The latter takes
     // the form of another map from the priority to the vote itself so that each priority is
@@ -89,17 +98,19 @@
     // A map from the display ID to the default mode of that display.
     private SparseArray<Display.Mode> mDefaultModeByDisplay;
 
-    private final AppRequestObserver mAppRequestObserver;
-    private final SettingsObserver mSettingsObserver;
-    private final DisplayObserver mDisplayObserver;
     private BrightnessObserver mBrightnessObserver;
 
-    private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
     private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
 
     public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
+        this(context, handler, new RealInjector());
+    }
+
+    public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
+            @NonNull Injector injector) {
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
+        mInjector = injector;
         mVotesByDisplay = new SparseArray<>();
         mSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay =  new SparseArray<>();
@@ -108,6 +119,7 @@
         mDisplayObserver = new DisplayObserver(context, handler);
         mBrightnessObserver = new BrightnessObserver(context, handler);
         mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
+        mDeviceConfig = injector.getDeviceConfig();
     }
 
     /**
@@ -349,6 +361,23 @@
     }
 
     /**
+     * Retrieve the Vote for the given display and priority. Intended only for testing purposes.
+     *
+     * @param displayId the display to query for
+     * @param priority the priority of the vote to return
+     * @return the vote corresponding to the given {@code displayId} and {@code priority},
+     *         or {@code null} if there isn't one
+     */
+    @VisibleForTesting
+    @Nullable
+    Vote getVote(int displayId, int priority) {
+        synchronized (mLock) {
+            SparseArray<Vote> votes = getVotesLocked(displayId);
+            return votes.get(priority);
+        }
+    }
+
+    /**
      * Print the object's state and debug information into the given stream.
      *
      * @param pw The stream to dump information to.
@@ -466,6 +495,17 @@
     }
 
     @VisibleForTesting
+    BrightnessObserver getBrightnessObserver() {
+        return mBrightnessObserver;
+    }
+
+    @VisibleForTesting
+    SettingsObserver getSettingsObserver() {
+        return mSettingsObserver;
+    }
+
+
+    @VisibleForTesting
     DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
             float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
         synchronized (mLock) {
@@ -475,6 +515,13 @@
         }
     }
 
+    @VisibleForTesting
+    void updateSettingForHighZone(int refreshRate, int[] brightnessThresholds,
+            int[] ambientThresholds) {
+        mBrightnessObserver.updateThresholdsRefreshRateForHighZone(refreshRate,
+                brightnessThresholds, ambientThresholds);
+    }
+
     /**
      * Listens for changes refresh rate coordination.
      */
@@ -493,15 +540,10 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_BRIGHTNESS_THRESHOLDS_CHANGED:
+                case MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED:
                     Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
-
-                    if (thresholds != null) {
-                        mBrightnessObserver.onDeviceConfigThresholdsChanged(
-                                thresholds.first, thresholds.second);
-                    } else {
-                        mBrightnessObserver.onDeviceConfigThresholdsChanged(null, null);
-                    }
+                    mBrightnessObserver.onDeviceConfigLowBrightnessThresholdsChanged(
+                            thresholds.first, thresholds.second);
                     break;
 
                 case MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED:
@@ -510,9 +552,9 @@
                             defaultPeakRefreshRate);
                     break;
 
-                case MSG_REFRESH_RATE_IN_ZONE_CHANGED:
+                case MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED:
                     int refreshRateInZone = msg.arg1;
-                    mBrightnessObserver.onDeviceConfigRefreshRateInZoneChanged(
+                    mBrightnessObserver.onDeviceConfigRefreshRateInLowZoneChanged(
                             refreshRateInZone);
                     break;
 
@@ -685,10 +727,11 @@
         // by all other considerations. It acts to set a default frame rate for a device.
         public static final int PRIORITY_DEFAULT_REFRESH_RATE = 0;
 
-        // LOW_BRIGHTNESS votes for a single refresh rate like [60,60], [90,90] or null.
+        // FLICKER votes for a single refresh rate like [60,60], [90,90] or null.
         // If the higher voters result is a range, it will fix the rate to a single choice.
-        // It's used to avoid rate switch in certain conditions.
-        public static final int PRIORITY_LOW_BRIGHTNESS = 1;
+        // It's used to avoid refresh rate switches in certain conditions which may result in the
+        // user seeing the display flickering when the switches occur.
+        public static final int PRIORITY_FLICKER = 1;
 
         // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
         // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
@@ -761,8 +804,8 @@
             switch (priority) {
                 case PRIORITY_DEFAULT_REFRESH_RATE:
                     return "PRIORITY_DEFAULT_REFRESH_RATE";
-                case PRIORITY_LOW_BRIGHTNESS:
-                    return "PRIORITY_LOW_BRIGHTNESS";
+                case PRIORITY_FLICKER:
+                    return "PRIORITY_FLICKER";
                 case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
                     return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE";
                 case PRIORITY_APP_REQUEST_REFRESH_RATE:
@@ -787,7 +830,8 @@
         }
     }
 
-    private final class SettingsObserver extends ContentObserver {
+    @VisibleForTesting
+    final class SettingsObserver extends ContentObserver {
         private final Uri mPeakRefreshRateSetting =
                 Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
         private final Uri mMinRefreshRateSetting =
@@ -810,8 +854,7 @@
 
         public void observe() {
             final ContentResolver cr = mContext.getContentResolver();
-            cr.registerContentObserver(mPeakRefreshRateSetting, false /*notifyDescendants*/, this,
-                    UserHandle.USER_SYSTEM);
+            mInjector.registerPeakRefreshRateObserver(cr, this);
             cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this,
                     UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
@@ -829,6 +872,13 @@
             }
         }
 
+        public void setDefaultRefreshRate(float refreshRate) {
+            synchronized (mLock) {
+                mDefaultRefreshRate = refreshRate;
+                updateRefreshRateSettingLocked();
+            }
+        }
+
         public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) {
             if (defaultPeakRefreshRate == null) {
                 defaultPeakRefreshRate = (float) mContext.getResources().getInteger(
@@ -1033,6 +1083,7 @@
         @Override
         public void onDisplayChanged(int displayId) {
             updateDisplayModes(displayId);
+            // TODO: Break the coupling between DisplayObserver and BrightnessObserver.
             mBrightnessObserver.onDisplayChanged(displayId);
         }
 
@@ -1071,15 +1122,16 @@
      */
     @VisibleForTesting
     public class BrightnessObserver extends ContentObserver {
-        // TODO: brightnessfloat: change this to the float setting
-        private final Uri mDisplayBrightnessSetting =
-                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
         private final static int LIGHT_SENSOR_RATE_MS = 250;
-        private int[] mDisplayBrightnessThresholds;
-        private int[] mAmbientBrightnessThresholds;
+        private int[] mLowDisplayBrightnessThresholds;
+        private int[] mLowAmbientBrightnessThresholds;
+        private int[] mHighDisplayBrightnessThresholds;
+        private int[] mHighAmbientBrightnessThresholds;
         // valid threshold if any item from the array >= 0
-        private boolean mShouldObserveDisplayChange;
-        private boolean mShouldObserveAmbientChange;
+        private boolean mShouldObserveDisplayLowChange;
+        private boolean mShouldObserveAmbientLowChange;
+        private boolean mShouldObserveDisplayHighChange;
+        private boolean mShouldObserveAmbientHighChange;
 
         private SensorManager mSensorManager;
         private Sensor mLightSensor;
@@ -1087,46 +1139,114 @@
         // Take it as low brightness before valid sensor data comes
         private float mAmbientLux = -1.0f;
         private AmbientFilter mAmbientFilter;
+        private int mBrightness = -1;
 
         private final Context mContext;
 
-        // Enable light sensor only when mShouldObserveAmbientChange is true, screen is on, peak
-        // refresh rate changeable and low power mode off. After initialization, these states will
+        // Enable light sensor only when mShouldObserveAmbientLowChange is true or
+        // mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate
+        // changeable and low power mode off. After initialization, these states will
         // be updated from the same handler thread.
-        private boolean mScreenOn = false;
+        private boolean mDefaultDisplayOn = false;
         private boolean mRefreshRateChangeable = false;
         private boolean mLowPowerModeEnabled = false;
 
-        private int mRefreshRateInZone;
+        private int mRefreshRateInLowZone;
+        private int mRefreshRateInHighZone;
 
         BrightnessObserver(Context context, Handler handler) {
             super(handler);
             mContext = context;
-            mDisplayBrightnessThresholds = context.getResources().getIntArray(
+            mLowDisplayBrightnessThresholds = context.getResources().getIntArray(
                     R.array.config_brightnessThresholdsOfPeakRefreshRate);
-            mAmbientBrightnessThresholds = context.getResources().getIntArray(
+            mLowAmbientBrightnessThresholds = context.getResources().getIntArray(
                     R.array.config_ambientThresholdsOfPeakRefreshRate);
 
-            if (mDisplayBrightnessThresholds.length != mAmbientBrightnessThresholds.length) {
-                throw new RuntimeException("display brightness threshold array and ambient "
-                        + "brightness threshold array have different length");
+            if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
+                throw new RuntimeException("display low brightness threshold array and ambient "
+                        + "brightness threshold array have different length: "
+                        + "displayBrightnessThresholds="
+                        + Arrays.toString(mLowDisplayBrightnessThresholds)
+                        + ", ambientBrightnessThresholds="
+                        + Arrays.toString(mLowAmbientBrightnessThresholds));
             }
+
+            mHighDisplayBrightnessThresholds = context.getResources().getIntArray(
+                    R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+            mHighAmbientBrightnessThresholds = context.getResources().getIntArray(
+                    R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+            if (mHighDisplayBrightnessThresholds.length
+                    != mHighAmbientBrightnessThresholds.length) {
+                throw new RuntimeException("display high brightness threshold array and ambient "
+                        + "brightness threshold array have different length: "
+                        + "displayBrightnessThresholds="
+                        + Arrays.toString(mHighDisplayBrightnessThresholds)
+                        + ", ambientBrightnessThresholds="
+                        + Arrays.toString(mHighAmbientBrightnessThresholds));
+            }
+            mRefreshRateInHighZone = context.getResources().getInteger(
+                    R.integer.config_fixedRefreshRateInHighZone);
+        }
+
+        /**
+         * @return the refresh to lock to when in a low brightness zone
+         */
+        @VisibleForTesting
+        int getRefreshRateInLowZone() {
+            return mRefreshRateInLowZone;
+        }
+
+        /**
+         * @return the display brightness thresholds for the low brightness zones
+         */
+        @VisibleForTesting
+        int[] getLowDisplayBrightnessThresholds() {
+            return mLowDisplayBrightnessThresholds;
+        }
+
+        /**
+         * @return the ambient brightness thresholds for the low brightness zones
+         */
+        @VisibleForTesting
+        int[] getLowAmbientBrightnessThresholds() {
+            return mLowAmbientBrightnessThresholds;
+        }
+
+        public void registerLightSensor(SensorManager sensorManager, Sensor lightSensor) {
+            mSensorManager = sensorManager;
+            mLightSensor = lightSensor;
+
+            mSensorManager.registerListener(mLightSensorListener,
+                    mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
+        }
+
+        public void updateThresholdsRefreshRateForHighZone(int refreshRate,
+                int[] brightnessThresholds, int[] ambientThresholds) {
+            mRefreshRateInHighZone = refreshRate;
+            mHighDisplayBrightnessThresholds = brightnessThresholds;
+            mHighAmbientBrightnessThresholds = ambientThresholds;
         }
 
         public void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
+            final ContentResolver cr = mContext.getContentResolver();
+            mBrightness = Settings.System.getIntForUser(cr,
+                    Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
 
             // DeviceConfig is accessible after system ready.
-            int[] brightnessThresholds = mDeviceConfigDisplaySettings.getBrightnessThresholds();
-            int[] ambientThresholds = mDeviceConfigDisplaySettings.getAmbientThresholds();
+            int[] lowDisplayBrightnessThresholds =
+                    mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds();
+            int[] lowAmbientBrightnessThresholds =
+                    mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds();
 
-            if (brightnessThresholds != null && ambientThresholds != null
-                    && brightnessThresholds.length == ambientThresholds.length) {
-                mDisplayBrightnessThresholds = brightnessThresholds;
-                mAmbientBrightnessThresholds = ambientThresholds;
+            if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null
+                    && lowDisplayBrightnessThresholds.length
+                    == lowAmbientBrightnessThresholds.length) {
+                mLowDisplayBrightnessThresholds = lowDisplayBrightnessThresholds;
+                mLowAmbientBrightnessThresholds = lowAmbientBrightnessThresholds;
             }
 
-            mRefreshRateInZone = mDeviceConfigDisplaySettings.getRefreshRateInZone();
+            mRefreshRateInLowZone = mDeviceConfigDisplaySettings.getRefreshRateInLowZone();
             restartObserver();
             mDeviceConfigDisplaySettings.startListening();
         }
@@ -1138,7 +1258,7 @@
                 updateSensorStatus();
                 if (!changeable) {
                     // Revoke previous vote from BrightnessObserver
-                    updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, null);
+                    updateVoteLocked(Vote.PRIORITY_FLICKER, null);
                 }
             }
         }
@@ -1150,25 +1270,25 @@
             }
         }
 
-        public void onDeviceConfigThresholdsChanged(int[] brightnessThresholds,
+        public void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds,
                 int[] ambientThresholds) {
-            if (brightnessThresholds != null && ambientThresholds != null
-                    && brightnessThresholds.length == ambientThresholds.length) {
-                mDisplayBrightnessThresholds = brightnessThresholds;
-                mAmbientBrightnessThresholds = ambientThresholds;
+            if (displayThresholds != null && ambientThresholds != null
+                    && displayThresholds.length == ambientThresholds.length) {
+                mLowDisplayBrightnessThresholds = displayThresholds;
+                mLowAmbientBrightnessThresholds = ambientThresholds;
             } else {
                 // Invalid or empty. Use device default.
-                mDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+                mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray(
                         R.array.config_brightnessThresholdsOfPeakRefreshRate);
-                mAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+                mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray(
                         R.array.config_ambientThresholdsOfPeakRefreshRate);
             }
             restartObserver();
         }
 
-        public void onDeviceConfigRefreshRateInZoneChanged(int refreshRate) {
-            if (refreshRate != mRefreshRateInZone) {
-                mRefreshRateInZone = refreshRate;
+        public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) {
+            if (refreshRate != mRefreshRateInLowZone) {
+                mRefreshRateInLowZone = refreshRate;
                 restartObserver();
             }
         }
@@ -1176,48 +1296,95 @@
         public void dumpLocked(PrintWriter pw) {
             pw.println("  BrightnessObserver");
             pw.println("    mAmbientLux: " + mAmbientLux);
-            pw.println("    mRefreshRateInZone: " + mRefreshRateInZone);
+            pw.println("    mBrightness: " + mBrightness);
+            pw.println("    mDefaultDisplayOn: " + mDefaultDisplayOn);
+            pw.println("    mLowPowerModeEnabled: " + mLowPowerModeEnabled);
+            pw.println("    mRefreshRateChangeable: " + mRefreshRateChangeable);
+            pw.println("    mShouldObserveDisplayLowChange: " + mShouldObserveDisplayLowChange);
+            pw.println("    mShouldObserveAmbientLowChange: " + mShouldObserveAmbientLowChange);
+            pw.println("    mRefreshRateInLowZone: " + mRefreshRateInLowZone);
 
-            for (int d: mDisplayBrightnessThresholds) {
-                pw.println("    mDisplayBrightnessThreshold: " + d);
+            for (int d : mLowDisplayBrightnessThresholds) {
+                pw.println("    mDisplayLowBrightnessThreshold: " + d);
             }
 
-            for (int d: mAmbientBrightnessThresholds) {
-                pw.println("    mAmbientBrightnessThreshold: " + d);
+            for (int d : mLowAmbientBrightnessThresholds) {
+                pw.println("    mAmbientLowBrightnessThreshold: " + d);
+            }
+
+            pw.println("    mShouldObserveDisplayHighChange: " + mShouldObserveDisplayHighChange);
+            pw.println("    mShouldObserveAmbientHighChange: " + mShouldObserveAmbientHighChange);
+            pw.println("    mRefreshRateInHighZone: " + mRefreshRateInHighZone);
+
+            for (int d : mHighDisplayBrightnessThresholds) {
+                pw.println("    mDisplayHighBrightnessThresholds: " + d);
+            }
+
+            for (int d : mHighAmbientBrightnessThresholds) {
+                pw.println("    mAmbientHighBrightnessThresholds: " + d);
             }
 
             mLightSensorListener.dumpLocked(pw);
+
+            if (mAmbientFilter != null) {
+                IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                ipw.setIndent("    ");
+                mAmbientFilter.dump(ipw);
+            }
         }
 
         public void onDisplayChanged(int displayId) {
             if (displayId == Display.DEFAULT_DISPLAY) {
-                onScreenOn(isDefaultDisplayOn());
+                updateDefaultDisplayState();
             }
         }
 
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
             synchronized (mLock) {
-                onBrightnessChangedLocked();
+                final ContentResolver cr = mContext.getContentResolver();
+                int brightness = Settings.System.getIntForUser(cr,
+                        Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
+                if (brightness != mBrightness) {
+                    mBrightness = brightness;
+                    onBrightnessChangedLocked();
+                }
             }
         }
 
         private void restartObserver() {
-            mShouldObserveDisplayChange = checkShouldObserve(mDisplayBrightnessThresholds);
-            mShouldObserveAmbientChange = checkShouldObserve(mAmbientBrightnessThresholds);
-
             final ContentResolver cr = mContext.getContentResolver();
-            if (mShouldObserveDisplayChange) {
-                // Content Service does not check if an listener has already been registered.
-                // To ensure only one listener is registered, force an unregistration first.
-                cr.unregisterContentObserver(this);
-                cr.registerContentObserver(mDisplayBrightnessSetting,
-                        false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM);
+
+            if (mRefreshRateInLowZone > 0) {
+                mShouldObserveDisplayLowChange = hasValidThreshold(
+                        mLowDisplayBrightnessThresholds);
+                mShouldObserveAmbientLowChange = hasValidThreshold(
+                        mLowAmbientBrightnessThresholds);
             } else {
-                cr.unregisterContentObserver(this);
+                mShouldObserveDisplayLowChange = false;
+                mShouldObserveAmbientLowChange = false;
             }
 
-            if (mShouldObserveAmbientChange) {
+            if (mRefreshRateInHighZone > 0) {
+                mShouldObserveDisplayHighChange = hasValidThreshold(
+                        mHighDisplayBrightnessThresholds);
+                mShouldObserveAmbientHighChange = hasValidThreshold(
+                        mHighAmbientBrightnessThresholds);
+            } else {
+                mShouldObserveDisplayHighChange = false;
+                mShouldObserveAmbientHighChange = false;
+            }
+
+            if (mShouldObserveDisplayLowChange || mShouldObserveDisplayHighChange) {
+                // Content Service does not check if an listener has already been registered.
+                // To ensure only one listener is registered, force an unregistration first.
+                mInjector.unregisterBrightnessObserver(cr, this);
+                mInjector.registerBrightnessObserver(cr, this);
+            } else {
+                mInjector.unregisterBrightnessObserver(cr, this);
+            }
+
+            if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
                 Resources resources = mContext.getResources();
                 String lightSensorType = resources.getString(
                         com.android.internal.R.string.config_displayLightSensorType);
@@ -1243,8 +1410,6 @@
 
                     mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res);
                     mLightSensor = lightSensor;
-
-                    onScreenOn(isDefaultDisplayOn());
                 }
             } else {
                 mAmbientFilter = null;
@@ -1263,11 +1428,7 @@
          * Checks to see if at least one value is positive, in which case it is necessary to listen
          * to value changes.
          */
-        private boolean checkShouldObserve(int[] a) {
-            if (mRefreshRateInZone <= 0) {
-                return false;
-            }
-
+        private boolean hasValidThreshold(int[] a) {
             for (int d: a) {
                 if (d >= 0) {
                     return true;
@@ -1277,13 +1438,13 @@
             return false;
         }
 
-        private boolean isInsideZone(int brightness, float lux) {
-            for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
-                int disp = mDisplayBrightnessThresholds[i];
-                int ambi = mAmbientBrightnessThresholds[i];
+        private boolean isInsideLowZone(int brightness, float lux) {
+            for (int i = 0; i < mLowDisplayBrightnessThresholds.length; i++) {
+                int disp = mLowDisplayBrightnessThresholds[i];
+                int ambi = mLowAmbientBrightnessThresholds[i];
 
                 if (disp >= 0 && ambi >= 0) {
-                    if (brightness <= disp && mAmbientLux <= ambi) {
+                    if (brightness <= disp && lux <= ambi) {
                         return true;
                     }
                 } else if (disp >= 0) {
@@ -1291,7 +1452,7 @@
                         return true;
                     }
                 } else if (ambi >= 0) {
-                    if (mAmbientLux <= ambi) {
+                    if (lux <= ambi) {
                         return true;
                     }
                 }
@@ -1299,27 +1460,77 @@
 
             return false;
         }
-        // TODO: brightnessfloat: make it use float not int
-        private void onBrightnessChangedLocked() {
-            int brightness = Settings.System.getInt(mContext.getContentResolver(),
-                    Settings.System.SCREEN_BRIGHTNESS, -1);
 
+        private boolean isInsideHighZone(int brightness, float lux) {
+            for (int i = 0; i < mHighDisplayBrightnessThresholds.length; i++) {
+                int disp = mHighDisplayBrightnessThresholds[i];
+                int ambi = mHighAmbientBrightnessThresholds[i];
+
+                if (disp >= 0 && ambi >= 0) {
+                    if (brightness >= disp && lux >= ambi) {
+                        return true;
+                    }
+                } else if (disp >= 0) {
+                    if (brightness >= disp) {
+                        return true;
+                    }
+                } else if (ambi >= 0) {
+                    if (lux >= ambi) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+        private void onBrightnessChangedLocked() {
             Vote vote = null;
-            boolean insideZone = isInsideZone(brightness, mAmbientLux);
-            if (insideZone) {
-                vote = Vote.forRefreshRates(mRefreshRateInZone, mRefreshRateInZone);
+
+            if (mBrightness < 0) {
+                // Either the setting isn't available or we shouldn't be observing yet anyways.
+                // Either way, just bail out since there's nothing we can do here.
+                return;
+            }
+
+            boolean insideLowZone = hasValidLowZone() && isInsideLowZone(mBrightness, mAmbientLux);
+            if (insideLowZone) {
+                vote = Vote.forRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone);
+            }
+
+            boolean insideHighZone = hasValidHighZone()
+                    && isInsideHighZone(mBrightness, mAmbientLux);
+            if (insideHighZone) {
+                vote = Vote.forRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone);
             }
 
             if (DEBUG) {
-                Slog.d(TAG, "Display brightness " + brightness + ", ambient lux " +  mAmbientLux +
-                        ", Vote " + vote);
+                Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " +  mAmbientLux
+                        + ", Vote " + vote);
             }
-            updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote);
+            updateVoteLocked(Vote.PRIORITY_FLICKER, vote);
         }
 
-        private void onScreenOn(boolean on) {
-            if (mScreenOn != on) {
-                mScreenOn = on;
+        private boolean hasValidLowZone() {
+            return mRefreshRateInLowZone > 0
+                    && (mShouldObserveDisplayLowChange || mShouldObserveAmbientLowChange);
+        }
+
+        private boolean hasValidHighZone() {
+            return mRefreshRateInHighZone > 0
+                    && (mShouldObserveDisplayHighChange || mShouldObserveAmbientHighChange);
+        }
+
+        private void updateDefaultDisplayState() {
+            Display display = mContext.getSystemService(DisplayManager.class)
+                    .getDisplay(Display.DEFAULT_DISPLAY);
+            boolean defaultDisplayOn = display != null && display.getState() != Display.STATE_OFF;
+            setDefaultDisplayState(defaultDisplayOn);
+        }
+
+        @VisibleForTesting
+        public void setDefaultDisplayState(boolean on) {
+            if (mDefaultDisplayOn != on) {
+                mDefaultDisplayOn = on;
                 updateSensorStatus();
             }
         }
@@ -1329,8 +1540,8 @@
                 return;
             }
 
-            if (mShouldObserveAmbientChange && mScreenOn && !mLowPowerModeEnabled
-                    && mRefreshRateChangeable) {
+            if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
+                     && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
                 mSensorManager.registerListener(mLightSensorListener,
                         mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
             } else {
@@ -1339,11 +1550,8 @@
             }
         }
 
-        private boolean isDefaultDisplayOn() {
-            final Display display = mContext.getSystemService(DisplayManager.class)
-                    .getDisplay(Display.DEFAULT_DISPLAY);
-            return display.getState() != Display.STATE_OFF
-                    && mContext.getSystemService(PowerManager.class).isInteractive();
+        private boolean isDeviceActive() {
+            return mDefaultDisplayOn && mInjector.isDeviceInteractive(mContext);
         }
 
         private final class LightSensorEventListener implements SensorEventListener {
@@ -1361,23 +1569,33 @@
                     Slog.d(TAG, "On sensor changed: " + mLastSensorData);
                 }
 
-                boolean zoneChanged = isDifferentZone(mLastSensorData, mAmbientLux);
-                if (zoneChanged && mLastSensorData < mAmbientLux) {
-                    // Easier to see flicker at lower brightness environment. Forget the history to
-                    // get immediate response.
-                    mAmbientFilter.clear();
+                boolean lowZoneChanged = isDifferentZone(mLastSensorData, mAmbientLux,
+                        mLowAmbientBrightnessThresholds);
+                boolean highZoneChanged = isDifferentZone(mLastSensorData, mAmbientLux,
+                        mHighAmbientBrightnessThresholds);
+                if ((lowZoneChanged && mLastSensorData < mAmbientLux)
+                        || (highZoneChanged && mLastSensorData > mAmbientLux)) {
+                    // Easier to see flicker at lower brightness environment or high brightness
+                    // environment. Forget the history to get immediate response.
+                    if (mAmbientFilter != null) {
+                        mAmbientFilter.clear();
+                    }
                 }
 
                 long now = SystemClock.uptimeMillis();
-                mAmbientFilter.addValue(now, mLastSensorData);
+                if (mAmbientFilter != null) {
+                    mAmbientFilter.addValue(now, mLastSensorData);
+                }
 
                 mHandler.removeCallbacks(mInjectSensorEventRunnable);
                 processSensorData(now);
 
-                if (zoneChanged && mLastSensorData > mAmbientLux) {
+                if ((lowZoneChanged && mLastSensorData > mAmbientLux)
+                        || (highZoneChanged && mLastSensorData < mAmbientLux)) {
                     // Sensor may not report new event if there is no brightness change.
                     // Need to keep querying the temporal filter for the latest estimation,
-                    // until enter in higher lux zone or is interrupted by a new sensor event.
+                    // until sensor readout and filter estimation are in the same zone or
+                    // is interrupted by a new sensor event.
                     mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS);
                 }
             }
@@ -1392,17 +1610,19 @@
             }
 
             private void processSensorData(long now) {
-                mAmbientLux = mAmbientFilter.getEstimate(now);
+                if (mAmbientFilter != null) {
+                    mAmbientLux = mAmbientFilter.getEstimate(now);
+                } else {
+                    mAmbientLux = mLastSensorData;
+                }
 
                 synchronized (mLock) {
                     onBrightnessChangedLocked();
                 }
             }
 
-            private boolean isDifferentZone(float lux1, float lux2) {
-                for (int z = 0; z < mAmbientBrightnessThresholds.length; z++) {
-                    final float boundary = mAmbientBrightnessThresholds[z];
-
+            private boolean isDifferentZone(float lux1, float lux2, int[] luxThresholds) {
+                for (final float boundary : luxThresholds) {
                     // Test each boundary. See if the current value and the new value are at
                     // different sides.
                     if ((lux1 <= boundary && lux2 > boundary)
@@ -1422,7 +1642,10 @@
                     processSensorData(now);
 
                     // Inject next event if there is a possible zone change.
-                    if (isDifferentZone(mLastSensorData, mAmbientLux)) {
+                    if (isDifferentZone(mLastSensorData, mAmbientLux,
+                            mLowAmbientBrightnessThresholds)
+                            || isDifferentZone(mLastSensorData, mAmbientLux,
+                            mHighAmbientBrightnessThresholds)) {
                         mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS);
                     }
                 }
@@ -1435,14 +1658,14 @@
         }
 
         public void startListening() {
-            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+            mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                     BackgroundThread.getExecutor(), this);
         }
 
         /*
          * Return null if no such property or wrong format (not comma separated integers).
          */
-        public int[] getBrightnessThresholds() {
+        public int[] getLowDisplayBrightnessThresholds() {
             return getIntArrayProperty(
                     DisplayManager.DeviceConfig.
                             KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS);
@@ -1451,17 +1674,29 @@
         /*
          * Return null if no such property or wrong format (not comma separated integers).
          */
-        public int[] getAmbientThresholds() {
+        public int[] getLowAmbientBrightnessThresholds() {
             return getIntArrayProperty(
                     DisplayManager.DeviceConfig.
                             KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS);
         }
 
+        public int getRefreshRateInLowZone() {
+            int defaultRefreshRateInZone = mContext.getResources().getInteger(
+                    R.integer.config_defaultRefreshRateInZone);
+
+            int refreshRate = mDeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_ZONE,
+                    defaultRefreshRateInZone);
+
+            return refreshRate;
+        }
+
         /*
          * Return null if no such property
          */
         public Float getDefaultPeakRefreshRate() {
-            float defaultPeakRefreshRate = DeviceConfig.getFloat(
+            float defaultPeakRefreshRate = mDeviceConfig.getFloat(
                     DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                     DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
 
@@ -1471,36 +1706,25 @@
             return defaultPeakRefreshRate;
         }
 
-        public int getRefreshRateInZone() {
-            int defaultRefreshRateInZone = mContext.getResources().getInteger(
-                    R.integer.config_defaultRefreshRateInZone);
-
-            int refreshRate = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_ZONE,
-                    defaultRefreshRateInZone);
-
-            return refreshRate;
-        }
-
         @Override
         public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
-            int[] brightnessThresholds = getBrightnessThresholds();
-            int[] ambientThresholds = getAmbientThresholds();
             Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
-            int refreshRateInZone = getRefreshRateInZone();
-
-            mHandler.obtainMessage(MSG_BRIGHTNESS_THRESHOLDS_CHANGED,
-                    new Pair<int[], int[]>(brightnessThresholds, ambientThresholds))
-                    .sendToTarget();
             mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
                     defaultPeakRefreshRate).sendToTarget();
-            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_ZONE_CHANGED, refreshRateInZone,
-                    0).sendToTarget();
+
+            int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds();
+            int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds();
+            int refreshRateInLowZone = getRefreshRateInLowZone();
+
+            mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED,
+                    new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
+                    .sendToTarget();
+            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, 0)
+                    .sendToTarget();
         }
 
         private int[] getIntArrayProperty(String prop) {
-            String strArray = DeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
+            String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
                     null);
 
             if (strArray != null) {
@@ -1527,4 +1751,59 @@
         }
     }
 
+    interface Injector {
+        // TODO: brightnessfloat: change this to the float setting
+        Uri DISPLAY_BRIGHTNESS_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
+        Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
+
+        @NonNull
+        DeviceConfigInterface getDeviceConfig();
+
+        void registerBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer);
+
+        void unregisterBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer);
+
+        void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer);
+
+        boolean isDeviceInteractive(@NonNull Context context);
+    }
+
+    @VisibleForTesting
+    static class RealInjector implements Injector {
+
+        @Override
+        @NonNull
+        public DeviceConfigInterface getDeviceConfig() {
+            return DeviceConfigInterface.REAL;
+        }
+
+        @Override
+        public void registerBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.registerContentObserver(DISPLAY_BRIGHTNESS_URI, false /*notifyDescendants*/,
+                    observer, UserHandle.USER_SYSTEM);
+        }
+
+        @Override
+        public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.unregisterContentObserver(observer);
+        }
+
+        @Override
+        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
+                    observer, UserHandle.USER_SYSTEM);
+        }
+
+        @Override
+        public boolean isDeviceInteractive(@NonNull Context ctx) {
+            return ctx.getSystemService(PowerManager.class).isInteractive();
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/utils/DeviceConfigInterface.java b/services/core/java/com/android/server/utils/DeviceConfigInterface.java
similarity index 90%
rename from services/core/java/com/android/server/wm/utils/DeviceConfigInterface.java
rename to services/core/java/com/android/server/utils/DeviceConfigInterface.java
index ab7e7f6..ff60903 100644
--- a/services/core/java/com/android/server/wm/utils/DeviceConfigInterface.java
+++ b/services/core/java/com/android/server/utils/DeviceConfigInterface.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.utils;
+package com.android.server.utils;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -54,6 +54,11 @@
     boolean getBoolean(@NonNull String namespace, @NonNull String name, boolean defaultValue);
 
     /**
+     * @see DeviceConfig#getFloat
+     */
+    float getFloat(@NonNull String namespace, @NonNull String name, float defaultValue);
+
+    /**
      * @see DeviceConfig#addOnPropertiesChangedListener
      */
     void addOnPropertiesChangedListener(@NonNull String namespace, @NonNull Executor executor,
@@ -96,6 +101,12 @@
         }
 
         @Override
+        public float getFloat(@NonNull String namespace, @NonNull String name,
+                float defaultValue) {
+            return DeviceConfig.getFloat(namespace, name, defaultValue);
+        }
+
+        @Override
         public void addOnPropertiesChangedListener(String namespace, Executor executor,
                 DeviceConfig.OnPropertiesChangedListener listener) {
             DeviceConfig.addOnPropertiesChangedListener(namespace, executor, listener);
diff --git a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
index d9cf637..09ab004 100644
--- a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
+++ b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
@@ -27,7 +27,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
-import com.android.server.wm.utils.DeviceConfigInterface;
+import com.android.server.utils.DeviceConfigInterface;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index b0c5dbc..a5ebf9a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -23,7 +23,7 @@
 import android.provider.DeviceConfig;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.utils.DeviceConfigInterface;
+import com.android.server.utils.DeviceConfigInterface;
 
 import java.io.PrintWriter;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b7a2eb3..d9594a4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -282,8 +282,8 @@
 import com.android.server.power.ShutdownThread;
 import com.android.server.protolog.ProtoLogImpl;
 import com.android.server.protolog.common.ProtoLog;
+import com.android.server.utils.DeviceConfigInterface;
 import com.android.server.utils.PriorityDump;
-import com.android.server.wm.utils.DeviceConfigInterface;
 
 import java.io.BufferedWriter;
 import java.io.DataInputStream;
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 979f4e1..e57097e 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -48,7 +48,6 @@
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
-
     ],
 
     aidl: {
@@ -110,6 +109,7 @@
         "utils/**/*.java",
         "utils/**/*.kt",
         "utils-mockito/**/*.kt",
+        ":services.core-sources-deviceconfig-interface",
     ],
     static_libs: [
         "junit",
@@ -126,6 +126,7 @@
         "utils/**/*.java",
         "utils/**/*.kt",
         "utils-mockito/**/*.kt",
+        ":services.core-sources-deviceconfig-interface",
     ],
     static_libs: [
         "junit",
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 43a396d..4ee6a55 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -16,49 +16,96 @@
 
 package com.android.server.display;
 
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_ZONE;
 
+import static com.android.server.display.DisplayModeDirector.Vote.PRIORITY_FLICKER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.ContextWrapper;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.display.DisplayModeDirector.BrightnessObserver;
 import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
 import com.android.server.display.DisplayModeDirector.Vote;
+import com.android.server.testutils.FakeDeviceConfigInterface;
 
 import com.google.common.truth.Truth;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DisplayModeDirectorTest {
     // The tolerance within which we consider something approximately equals.
+    private static final String TAG = "DisplayModeDirectorTest";
+    private static final boolean DEBUG = false;
     private static final float FLOAT_TOLERANCE = 0.01f;
 
     private Context mContext;
+    private FakesInjector mInjector;
+    private Handler mHandler;
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        mInjector = new FakesInjector();
+        mHandler = new Handler(Looper.getMainLooper());
     }
 
     private DisplayModeDirector createDirectorFromRefreshRateArray(
             float[] refreshRates, int baseModeId) {
         DisplayModeDirector director =
-                new DisplayModeDirector(mContext, new Handler(Looper.getMainLooper()));
+                new DisplayModeDirector(mContext, mHandler, mInjector);
         int displayId = 0;
         Display.Mode[] modes = new Display.Mode[refreshRates.length];
         for (int i = 0; i < refreshRates.length; i++) {
@@ -159,9 +206,9 @@
     }
 
     @Test
-    public void testBrightnessHasLowerPriorityThanUser() {
-        assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_REFRESH_RATE);
-        assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_SIZE);
+    public void testFlickerHasLowerPriorityThanUser() {
+        assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_REFRESH_RATE);
+        assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_SIZE);
 
         int displayId = 0;
         DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
@@ -169,7 +216,7 @@
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(displayId, votes);
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90));
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
@@ -177,7 +224,7 @@
 
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90));
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
@@ -185,7 +232,7 @@
 
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90));
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
@@ -193,7 +240,7 @@
 
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 60));
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
@@ -202,10 +249,10 @@
 
     @Test
     public void testAppRequestRefreshRateRange() {
-        // Confirm that the app request range doesn't include low brightness or min refresh rate
-        // settings, but does include everything else.
+        // Confirm that the app request range doesn't include flicker or min refresh rate settings,
+        // but does include everything else.
         assertTrue(
-                Vote.PRIORITY_LOW_BRIGHTNESS < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
+                PRIORITY_FLICKER < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
         assertTrue(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE
                 < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
         assertTrue(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE
@@ -216,7 +263,7 @@
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(displayId, votes);
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
@@ -302,4 +349,307 @@
         verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
         verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
     }
+
+    @Test
+    public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+        SensorManager sensorManager = createMockSensorManager(createLightSensor());
+
+        final int initialRefreshRate = 60;
+        mInjector.getDeviceConfig().setRefreshRateInLowZone(initialRefreshRate);
+        director.start(sensorManager);
+        assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
+                .isEqualTo(initialRefreshRate);
+
+        final int updatedRefreshRate = 90;
+        mInjector.getDeviceConfig().setRefreshRateInLowZone(updatedRefreshRate);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
+                .isEqualTo(updatedRefreshRate);
+    }
+
+    @Test
+    public void testBrightnessObserverThresholdsInZone() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+        SensorManager sensorManager = createMockSensorManager(createLightSensor());
+
+        final int[] initialDisplayThresholds = { 10 };
+        final int[] initialAmbientThresholds = { 20 };
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setLowDisplayBrightnessThresholds(initialDisplayThresholds);
+        config.setLowAmbientBrightnessThresholds(initialAmbientThresholds);
+        director.start(sensorManager);
+
+        assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
+                .isEqualTo(initialDisplayThresholds);
+        assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
+                .isEqualTo(initialAmbientThresholds);
+
+        final int[] updatedDisplayThresholds = { 9, 14 };
+        final int[] updatedAmbientThresholds = { -1, 19 };
+        config.setLowDisplayBrightnessThresholds(updatedDisplayThresholds);
+        config.setLowAmbientBrightnessThresholds(updatedAmbientThresholds);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
+                .isEqualTo(updatedDisplayThresholds);
+        assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
+                .isEqualTo(updatedAmbientThresholds);
+    }
+
+    @Test
+    public void testLockFpsForLowZone() throws Exception {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(true);
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInLowZone(90);
+        config.setLowDisplayBrightnessThresholds(new int[] { 10 });
+        config.setLowAmbientBrightnessThresholds(new int[] { 20 });
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+        director.start(sensorManager);
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        setBrightness(10);
+        // Sensor reads 20 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
+        assertVoteForRefreshRateLocked(vote, 90 /*fps*/);
+
+        setBrightness(125);
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
+        assertThat(vote).isNull();
+    }
+
+    @Test
+    public void testLockFpsForHighZone() throws Exception {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(true);
+        director.updateSettingForHighZone(60, new int[] {255}, new int[] {8000});
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+        director.start(sensorManager);
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        setBrightness(100);
+        // Sensor reads 2000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
+        assertThat(vote).isNull();
+
+        setBrightness(255);
+        // Sensor reads 9000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
+        assertVoteForRefreshRateLocked(vote, 60 /*fps*/);
+    }
+
+    private void assertVoteForRefreshRateLocked(Vote vote, float refreshRate) {
+        assertThat(vote).isNotNull();
+        final DisplayModeDirector.RefreshRateRange expectedRange =
+                new DisplayModeDirector.RefreshRateRange(refreshRate, refreshRate);
+        assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
+    }
+
+    private static class FakeDeviceConfig extends FakeDeviceConfigInterface {
+        @Override
+        public String getProperty(String namespace, String name) {
+            Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
+            return super.getProperty(namespace, name);
+        }
+
+        @Override
+        public void addOnPropertiesChangedListener(
+                String namespace,
+                Executor executor,
+                DeviceConfig.OnPropertiesChangedListener listener) {
+            Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
+            super.addOnPropertiesChangedListener(namespace, executor, listener);
+        }
+
+        void setRefreshRateInLowZone(int fps) {
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_ZONE,
+                    String.valueOf(fps));
+        }
+
+        void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
+            String thresholds = toPropertyValue(brightnessThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Brightness Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        void setLowAmbientBrightnessThresholds(int[] ambientThresholds) {
+            String thresholds = toPropertyValue(ambientThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Ambient Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        @NonNull
+        private static String toPropertyValue(@NonNull int[] intArray) {
+            return Arrays.stream(intArray)
+                    .mapToObj(Integer::toString)
+                    .collect(Collectors.joining(","));
+        }
+    }
+
+    private void setBrightness(int brightness) {
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS,
+                brightness);
+        mInjector.notifyBrightnessChanged();
+        waitForIdleSync();
+    }
+
+    private void setPeakRefreshRate(float fps) {
+        Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
+                 fps);
+        mInjector.notifyPeakRefreshRateChanged();
+        waitForIdleSync();
+    }
+
+    private static SensorManager createMockSensorManager(Sensor... sensors) {
+        SensorManager sensorManager = Mockito.mock(SensorManager.class);
+        when(sensorManager.getSensorList(anyInt())).then((invocation) -> {
+            List<Sensor> requestedSensors = new ArrayList<>();
+            int type = invocation.getArgument(0);
+            for (Sensor sensor : sensors) {
+                if (sensor.getType() == type || type == Sensor.TYPE_ALL) {
+                    requestedSensors.add(sensor);
+                }
+            }
+            return requestedSensors;
+        });
+
+        when(sensorManager.getDefaultSensor(anyInt())).then((invocation) -> {
+            int type = invocation.getArgument(0);
+            for (Sensor sensor : sensors) {
+                if (sensor.getType() == type) {
+                    return sensor;
+                }
+            }
+            return null;
+        });
+        return sensorManager;
+    }
+
+    private static Sensor createLightSensor() {
+        try {
+            return TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        } catch (Exception e) {
+            // There's nothing we can do if this fails, just throw a RuntimeException so that we
+            // don't have to mark every function that might call this as throwing Exception
+            throw new RuntimeException("Failed to create a light sensor", e);
+        }
+    }
+
+    private void waitForIdleSync() {
+        mHandler.runWithScissors(() -> { }, 500 /*timeout*/);
+    }
+
+    static class FakesInjector implements DisplayModeDirector.Injector {
+        private final FakeDeviceConfig mDeviceConfig;
+        private ContentObserver mBrightnessObserver;
+        private ContentObserver mPeakRefreshRateObserver;
+
+        FakesInjector() {
+            mDeviceConfig = new FakeDeviceConfig();
+        }
+
+        @NonNull
+        public FakeDeviceConfig getDeviceConfig() {
+            return mDeviceConfig;
+        }
+
+        @Override
+        public void registerBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            if (mBrightnessObserver != null) {
+                throw new IllegalStateException("Tried to register a second brightness observer");
+            }
+            mBrightnessObserver = observer;
+        }
+
+        @Override
+        public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mBrightnessObserver = null;
+        }
+
+        void notifyBrightnessChanged() {
+            if (mBrightnessObserver != null) {
+                mBrightnessObserver.dispatchChange(false /*selfChange*/, DISPLAY_BRIGHTNESS_URI);
+            }
+        }
+
+        @Override
+        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mPeakRefreshRateObserver = observer;
+        }
+
+        void notifyPeakRefreshRateChanged() {
+            if (mPeakRefreshRateObserver != null) {
+                mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
+                        PEAK_REFRESH_RATE_URI);
+            }
+        }
+
+        @Override
+        public boolean isDeviceInteractive(@NonNull Context context) {
+            return true;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/FakeDeviceConfigInterface.java b/services/tests/servicestests/utils/com/android/server/testutils/FakeDeviceConfigInterface.java
similarity index 92%
rename from services/tests/wmtests/src/com/android/server/wm/utils/FakeDeviceConfigInterface.java
rename to services/tests/servicestests/utils/com/android/server/testutils/FakeDeviceConfigInterface.java
index 2904a5b7..a67f645 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/FakeDeviceConfigInterface.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/FakeDeviceConfigInterface.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.utils;
+package com.android.server.testutils;
 
 import android.annotation.NonNull;
 import android.provider.DeviceConfig;
@@ -22,6 +22,7 @@
 import android.util.Pair;
 
 import com.android.internal.util.Preconditions;
+import com.android.server.utils.DeviceConfigInterface;
 
 import java.lang.reflect.Constructor;
 import java.util.HashMap;
@@ -122,6 +123,19 @@
     }
 
     @Override
+    public float getFloat(String namespace, String name, float defaultValue) {
+        String value = getProperty(namespace, name);
+        if (value == null) {
+            return defaultValue;
+        }
+        try {
+            return Float.parseFloat(value);
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
+    @Override
     public boolean getBoolean(String namespace, String name, boolean defaultValue) {
         String value = getProperty(namespace, name);
         return value != null ? Boolean.parseBoolean(value) : defaultValue;
diff --git a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
index 56cb447..a85e1db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
@@ -31,7 +31,7 @@
 
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
-import com.android.server.wm.utils.FakeDeviceConfigInterface;
+import com.android.server.testutils.FakeDeviceConfigInterface;
 
 import org.junit.After;
 import org.junit.Test;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerConstantsTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerConstantsTest.java
index 5210011..7a0ef0d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerConstantsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerConstantsTest.java
@@ -32,7 +32,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wm.utils.FakeDeviceConfigInterface;
+import com.android.server.testutils.FakeDeviceConfigInterface;
 
 import org.junit.After;
 import org.junit.Before;