DMD: Support 90hz only in the refresh rate zone

Also allow DeviceConfig to change the zone behavior
to 60 Hz only.

Change BrightObserver priority to be the lowest so application
request won't be overriden by BrightnessObserver

Bug: 139487676
Change-Id: I959550350c1ea72f764984226350ebc6e7de591c
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 5d8fa92..0b25dbd 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -829,23 +829,36 @@
     public interface DeviceConfig {
 
         /**
-         * Key for accessing the 60 hz only regions.
+         * Key for refresh rate in the zone defined by thresholds.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.integer#config_defaultZoneBehavior
+         */
+        String KEY_REFRESH_RATE_IN_ZONE = "refresh_rate_in_zone";
+
+        /**
+         * Key for accessing the display brightness thresholds for the configured refresh rate zone.
+         * The value will be a pair of comma separated integers representing the minimum and maximum
+         * thresholds of the zone, respectively, in display backlight units (i.e. [0, 255]).
          *
          * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
          * @see android.R.array#config_brightnessThresholdsOfPeakRefreshRate
          * @hide
          */
-        String KEY_PEAK_REFRESH_RATE_BRIGHTNESS_THRESHOLDS =
+        String KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS =
                 "peak_refresh_rate_brightness_thresholds";
 
         /**
-         * Key for accessing the 60 hz only regions.
+         * Key for accessing the ambient brightness thresholds for the configured refresh rate zone.
+         * The value will be a pair of comma separated integers representing the minimum and maximum
+         * thresholds of the zone, respectively, in lux.
          *
          * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
-         * @see android.R.array#config_brightnessThresholdsOfPeakRefreshRate
+         * @see android.R.array#config_ambientThresholdsOfPeakRefreshRate
          * @hide
          */
-        String KEY_PEAK_REFRESH_RATE_AMBIENT_THRESHOLDS = "peak_refresh_rate_ambient_thresholds";
+        String KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS =
+                "peak_refresh_rate_ambient_thresholds";
 
         /**
          * Key for default peak refresh rate
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index df87981..1577a22 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4167,6 +4167,10 @@
         -->
     </integer-array>
 
+    <!-- Default refresh rate in the zone defined by brightness and ambient thresholds.
+         If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
+    <integer name="config_defaultRefreshRateInZone">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 92c5d2c..0d9c4b3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3791,6 +3791,7 @@
 
   <!-- For high refresh rate displays -->
   <java-symbol type="integer" name="config_defaultPeakRefreshRate" />
+  <java-symbol type="integer" name="config_defaultRefreshRateInZone" />
   <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
   <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
 
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 97fd02f..500a242 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -69,6 +69,7 @@
     private static final int MSG_ALLOWED_MODES_CHANGED = 1;
     private static final int MSG_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;
 
     // Special ID used to indicate that given vote is to be applied globally, rather than to a
     // specific display.
@@ -440,23 +441,48 @@
                     mSettingsObserver.onDeviceConfigDefaultPeakRefreshRateChanged(
                             defaultPeakRefreshRate);
                     break;
+
+                case MSG_REFRESH_RATE_IN_ZONE_CHANGED:
+                    int refreshRateInZone = msg.arg1;
+                    mBrightnessObserver.onDeviceConfigRefreshRateInZoneChanged(
+                            refreshRateInZone);
+                    break;
             }
         }
     }
 
     private static final class Vote {
-        // We split the app request into two priorities in case we can satisfy one desire without
-        // the other.
-        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 0;
-        public static final int PRIORITY_APP_REQUEST_SIZE = 1;
-        public static final int PRIORITY_USER_SETTING_REFRESH_RATE = 2;
-        public static final int PRIORITY_LOW_BRIGHTNESS = 3;
-        public static final int PRIORITY_LOW_POWER_MODE = 4;
+        // LOW_BRIGHTNESS 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 = 0;
+
+        // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
+        // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
+        public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 1;
+
+        // We split the app request into different priorities in case we can satisfy one desire
+        // without the other.
+
+        // Application can specify preferred refresh rate with below attrs.
+        // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
+        // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
+        // System also forces some apps like blacklisted app to run at a lower refresh rate.
+        // @see android.R.array#config_highRefreshRateBlacklist
+        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 2;
+        public static final int PRIORITY_APP_REQUEST_SIZE = 3;
+
+        // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
+        // of low priority voters. It votes [0, max(PEAK, MIN)]
+        public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 4;
+
+        // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
+        public static final int PRIORITY_LOW_POWER_MODE = 5;
 
         // Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as
         // appropriate, as well as priorityToString.
 
-        public static final int MIN_PRIORITY = PRIORITY_APP_REQUEST_REFRESH_RATE;
+        public static final int MIN_PRIORITY = PRIORITY_LOW_BRIGHTNESS;
         public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
 
         /**
@@ -500,12 +526,16 @@
 
         public static String priorityToString(int priority) {
             switch (priority) {
+                case PRIORITY_LOW_BRIGHTNESS:
+                    return "PRIORITY_LOW_BRIGHTNESS";
+                case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
+                    return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE";
                 case PRIORITY_APP_REQUEST_REFRESH_RATE:
                     return "PRIORITY_APP_REQUEST_REFRESH_RATE";
                 case PRIORITY_APP_REQUEST_SIZE:
                     return "PRIORITY_APP_REQUEST_SIZE";
-                case PRIORITY_USER_SETTING_REFRESH_RATE:
-                    return "PRIORITY_USER_SETTING_REFRESH_RATE";
+                case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
+                    return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE";
                 case PRIORITY_LOW_POWER_MODE:
                     return "PRIORITY_LOW_POWER_MODE";
                 default:
@@ -608,12 +638,11 @@
             float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
                     Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
 
-            if (peakRefreshRate < minRefreshRate) {
-                peakRefreshRate = minRefreshRate;
-            }
+            updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
+                    Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate)));
+            updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
+                    Vote.forRefreshRates(minRefreshRate, Float.POSITIVE_INFINITY));
 
-            Vote vote = Vote.forRefreshRates(minRefreshRate, peakRefreshRate);
-            updateVoteLocked(Vote.PRIORITY_USER_SETTING_REFRESH_RATE, vote);
             mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, peakRefreshRate);
         }
 
@@ -655,6 +684,7 @@
                 refreshRateVote = null;
                 sizeVote = null;
             }
+
             updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
             updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
             return;
@@ -799,6 +829,8 @@
         private boolean mRefreshRateChangeable = false;
         private boolean mLowPowerModeEnabled = false;
 
+        private int mRefreshRateInZone;
+
         BrightnessObserver(Context context, Handler handler) {
             super(handler);
             mContext = context;
@@ -816,6 +848,7 @@
 
         public void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
+
             // DeviceConfig is accessible after system ready.
             int[] brightnessThresholds = mDeviceConfigDisplaySettings.getBrightnessThresholds();
             int[] ambientThresholds = mDeviceConfigDisplaySettings.getAmbientThresholds();
@@ -825,6 +858,8 @@
                 mDisplayBrightnessThresholds = brightnessThresholds;
                 mAmbientBrightnessThresholds = ambientThresholds;
             }
+
+            mRefreshRateInZone = mDeviceConfigDisplaySettings.getRefreshRateInZone();
             restartObserver();
             mDeviceConfigDisplaySettings.startListening();
         }
@@ -864,8 +899,16 @@
             restartObserver();
         }
 
+        public void onDeviceConfigRefreshRateInZoneChanged(int refreshRate) {
+            if (refreshRate != mRefreshRateInZone) {
+                mRefreshRateInZone = refreshRate;
+                restartObserver();
+            }
+        }
+
         public void dumpLocked(PrintWriter pw) {
             pw.println("  BrightnessObserver");
+            pw.println("    mRefreshRateInZone: " + mRefreshRateInZone);
 
             for (int d: mDisplayBrightnessThresholds) {
                 pw.println("    mDisplayBrightnessThreshold: " + d);
@@ -952,6 +995,10 @@
          * to value changes.
          */
         private boolean checkShouldObserve(int[] a) {
+            if (mRefreshRateInZone <= 0) {
+                return false;
+            }
+
             for (int d: a) {
                 if (d >= 0) {
                     return true;
@@ -961,37 +1008,42 @@
             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];
+
+                if (disp >= 0 && ambi >= 0) {
+                    if (brightness <= disp && mAmbientLux <= ambi) {
+                        return true;
+                    }
+                } else if (disp >= 0) {
+                    if (brightness <= disp) {
+                        return true;
+                    }
+                } else if (ambi >= 0) {
+                    if (mAmbientLux <= ambi) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
         private void onBrightnessChangedLocked() {
             int brightness = Settings.System.getInt(mContext.getContentResolver(),
                     Settings.System.SCREEN_BRIGHTNESS, -1);
 
             Vote vote = null;
-            for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
-                int disp = mDisplayBrightnessThresholds[i];
-                int ambi = mAmbientBrightnessThresholds[i];
-
-                if (disp >= 0 && ambi >= 0) {
-                    if (brightness <= disp && mAmbientLux <= ambi) {
-                        vote = Vote.forRefreshRates(0f, 60f);
-                    }
-                } else if (disp >= 0) {
-                    if (brightness <= disp) {
-                        vote = Vote.forRefreshRates(0f, 60f);
-                    }
-                } else if (ambi >= 0) {
-                    if (mAmbientLux <= ambi) {
-                        vote = Vote.forRefreshRates(0f, 60f);
-                    }
-                }
-
-                if (vote != null) {
-                    break;
-                }
+            boolean insideZone = isInsideZone(brightness, mAmbientLux);
+            if (insideZone) {
+                vote = Vote.forRefreshRates(mRefreshRateInZone, mRefreshRateInZone);
             }
 
             if (DEBUG) {
                 Slog.d(TAG, "Display brightness " + brightness + ", ambient lux " +  mAmbientLux +
-                        (vote != null ? " 60hz only" : " no refresh rate limit"));
+                        ", Vote " + vote);
             }
             updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote);
         }
@@ -1133,7 +1185,6 @@
     }
 
     private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
-
         public DeviceConfigDisplaySettings() {
         }
 
@@ -1147,7 +1198,8 @@
          */
         public int[] getBrightnessThresholds() {
             return getIntArrayProperty(
-                    DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_BRIGHTNESS_THRESHOLDS);
+                    DisplayManager.DeviceConfig.
+                            KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS);
         }
 
         /*
@@ -1155,7 +1207,8 @@
          */
         public int[] getAmbientThresholds() {
             return getIntArrayProperty(
-                    DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_AMBIENT_THRESHOLDS);
+                    DisplayManager.DeviceConfig.
+                            KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS);
         }
 
         /*
@@ -1172,17 +1225,32 @@
             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();
         }
 
         private int[] getIntArrayProperty(String prop) {