Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d3f82689 authored by Steven Thomas's avatar Steven Thomas
Browse files

Add default frame rate setting

Add a new priority PRIORITY_DEVICE_PEAK_REFRESH_RATE, and a new config
setting config_defaultRefreshRate which maps to that priority. This
allows an OEM to easily specify a default frame rate different from the
peak frame rate.

Bug: 148978450
Bug: 154648391

Test: - Added new unit tests to verify DisplayModeDirector handles min,
        peak, and default refresh rate settings correctly.

- Modified a Pixel 4 to set config_defaultRefreshRate to 60Hz, confirmed
  we default to 60, but that an app calling setFrameRate(90) switches us
  to 90.

- Confirmed that the "smooth display" and "force 90Hz" options on Pixel
  4 work correctly.

Change-Id: I00c82eea39a914b5b38506dd3143feb936749254
parent 33ecd883
Loading
Loading
Loading
Loading
+15 −3
Original line number Diff line number Diff line
@@ -4165,9 +4165,21 @@
     and a second time clipped to the fill level to indicate charge -->
    <bool name="config_batterymeterDualTone">false</bool>

    <!-- The default peak refresh rate for a given device. Change this value if you want to allow
         for higher refresh rates to be automatically used out of the box -->
    <integer name="config_defaultPeakRefreshRate">60</integer>
    <!-- The default refresh rate for a given device. Change this value to set a higher default
         refresh rate. If the hardware composer on the device supports display modes with a higher
         refresh rate than the default value specified here, the framework may use those higher
         refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
         setFrameRate().
         If a non-zero value is set for config_defaultPeakRefreshRate, then
         config_defaultRefreshRate may be set to 0, in which case the value set for
         config_defaultPeakRefreshRate will act as the default frame rate. -->
    <integer name="config_defaultRefreshRate">60</integer>

    <!-- The default peak refresh rate for a given device. Change this value if you want to prevent
         the framework from using higher refresh rates, even if display modes with higher refresh
         rates are available from hardware composer. Only has an effect if the value is
         non-zero. -->
    <integer name="config_defaultPeakRefreshRate">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
+1 −0
Original line number Diff line number Diff line
@@ -3773,6 +3773,7 @@
  <java-symbol type="string" name="bluetooth_airplane_mode_toast" />

  <!-- For high refresh rate displays -->
  <java-symbol type="integer" name="config_defaultRefreshRate" />
  <java-symbol type="integer" name="config_defaultPeakRefreshRate" />
  <java-symbol type="integer" name="config_defaultRefreshRateInZone" />
  <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
+69 −13
Original line number Diff line number Diff line
@@ -92,7 +92,7 @@ public class DisplayModeDirector {
    private final AppRequestObserver mAppRequestObserver;
    private final SettingsObserver mSettingsObserver;
    private final DisplayObserver mDisplayObserver;
    private final BrightnessObserver mBrightnessObserver;
    private BrightnessObserver mBrightnessObserver;

    private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
    private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
@@ -460,6 +460,21 @@ public class DisplayModeDirector {
        mVotesByDisplay = votesByDisplay;
    }

    @VisibleForTesting
    void injectBrightnessObserver(BrightnessObserver brightnessObserver) {
        mBrightnessObserver = brightnessObserver;
    }

    @VisibleForTesting
    DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
            float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
        synchronized (mLock) {
            mSettingsObserver.updateRefreshRateSettingLocked(
                    minRefreshRate, peakRefreshRate, defaultRefreshRate);
            return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY);
        }
    }

    /**
     * Listens for changes refresh rate coordination.
     */
@@ -666,14 +681,18 @@ public class DisplayModeDirector {

    @VisibleForTesting
    static final class Vote {
        // DEFAULT_FRAME_RATE votes for [0, DEFAULT]. As the lowest priority vote, it's overridden
        // 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.
        // 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;
        public static final int PRIORITY_LOW_BRIGHTNESS = 1;

        // 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;
        public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2;

        // We split the app request into different priorities in case we can satisfy one desire
        // without the other.
@@ -683,20 +702,20 @@ public class DisplayModeDirector {
        // @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;
        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 3;
        public static final int PRIORITY_APP_REQUEST_SIZE = 4;

        // 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;
        public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 5;

        // 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;
        public static final int PRIORITY_LOW_POWER_MODE = 6;

        // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
        // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.

        public static final int MIN_PRIORITY = PRIORITY_LOW_BRIGHTNESS;
        public static final int MIN_PRIORITY = PRIORITY_DEFAULT_REFRESH_RATE;
        public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;

        // The cutoff for the app request refresh rate range. Votes with priorities lower than this
@@ -740,6 +759,8 @@ public class DisplayModeDirector {

        public static String priorityToString(int priority) {
            switch (priority) {
                case PRIORITY_DEFAULT_REFRESH_RATE:
                    return "PRIORITY_DEFAULT_REFRESH_RATE";
                case PRIORITY_LOW_BRIGHTNESS:
                    return "PRIORITY_LOW_BRIGHTNESS";
                case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
@@ -776,12 +797,15 @@ public class DisplayModeDirector {

        private final Context mContext;
        private float mDefaultPeakRefreshRate;
        private float mDefaultRefreshRate;

        SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
            super(handler);
            mContext = context;
            mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
                    R.integer.config_defaultPeakRefreshRate);
            mDefaultRefreshRate =
                    (float) context.getResources().getInteger(R.integer.config_defaultRefreshRate);
        }

        public void observe() {
@@ -849,17 +873,48 @@ public class DisplayModeDirector {
                    Settings.System.MIN_REFRESH_RATE, 0f);
            float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
                    Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);

            updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
                    Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate)));
            updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
        }

        private void updateRefreshRateSettingLocked(
                float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
            // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
            // used to predict if we're going to be doing frequent refresh rate switching, and if
            // so, enable the brightness observer. The logic here is more complicated and fragile
            // than necessary, and we should improve it. See b/156304339 for more info.
            Vote peakVote = peakRefreshRate == 0f
                    ? null
                    : Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate));
            updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, peakVote);
            updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
                    Vote.forRefreshRates(minRefreshRate, Float.POSITIVE_INFINITY));
            Vote defaultVote =
                    defaultRefreshRate == 0f ? null : Vote.forRefreshRates(0f, defaultRefreshRate);
            updateVoteLocked(Vote.PRIORITY_DEFAULT_REFRESH_RATE, defaultVote);

            float maxRefreshRate;
            if (peakRefreshRate == 0f && defaultRefreshRate == 0f) {
                // We require that at least one of the peak or default refresh rate values are
                // set. The brightness observer requires that we're able to predict whether or not
                // we're going to do frequent refresh rate switching, and with the way the code is
                // currently written, we need either a default or peak refresh rate value for that.
                Slog.e(TAG, "Default and peak refresh rates are both 0. One of them should be set"
                        + " to a valid value.");
                maxRefreshRate = minRefreshRate;
            } else if (peakRefreshRate == 0f) {
                maxRefreshRate = defaultRefreshRate;
            } else if (defaultRefreshRate == 0f) {
                maxRefreshRate = peakRefreshRate;
            } else {
                maxRefreshRate = Math.min(defaultRefreshRate, peakRefreshRate);
            }

            mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, peakRefreshRate);
            mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, maxRefreshRate);
        }

        public void dumpLocked(PrintWriter pw) {
            pw.println("  SettingsObserver");
            pw.println("    mDefaultRefreshRate: " + mDefaultRefreshRate);
            pw.println("    mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate);
        }
    }
@@ -1014,7 +1069,8 @@ public class DisplayModeDirector {
     * {@link R.array#config_brightnessThresholdsOfPeakRefreshRate} and
     * {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
     */
    private class BrightnessObserver extends ContentObserver {
    @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);
+81 −14
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.display;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.os.Handler;
@@ -28,6 +29,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.display.DisplayModeDirector.BrightnessObserver;
import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
import com.android.server.display.DisplayModeDirector.Vote;

@@ -36,6 +38,7 @@ import com.google.common.truth.Truth;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

@SmallTest
@@ -52,16 +55,15 @@ public class DisplayModeDirectorTest {
        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
    }

    private DisplayModeDirector createDisplayModeDirectorWithDisplayFpsRange(
            int minFps, int maxFps) {
    private DisplayModeDirector createDirectorFromRefreshRateArray(
            float[] refreshRates, int baseModeId) {
        DisplayModeDirector director =
                new DisplayModeDirector(mContext, new Handler(Looper.getMainLooper()));
        int displayId = 0;
        int numModes = maxFps - minFps + 1;
        Display.Mode[] modes = new Display.Mode[numModes];
        for (int i = minFps; i <= maxFps; i++) {
            modes[i - minFps] = new Display.Mode(
                    /*modeId=*/i, /*width=*/1000, /*height=*/1000, /*refreshRate=*/i);
        Display.Mode[] modes = new Display.Mode[refreshRates.length];
        for (int i = 0; i < refreshRates.length; i++) {
            modes[i] = new Display.Mode(
                    /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]);
        }
        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
        supportedModesByDisplay.put(displayId, modes);
@@ -72,14 +74,22 @@ public class DisplayModeDirectorTest {
        return director;
    }

    private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) {
        int numRefreshRates = maxFps - minFps + 1;
        float[] refreshRates = new float[numRefreshRates];
        for (int i = 0; i < numRefreshRates; i++) {
            refreshRates[i] = minFps + i;
        }
        return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps);
    }

    @Test
    public void testDisplayModeVoting() {
        int displayId = 0;

        // With no votes present, DisplayModeDirector should allow any refresh rate.
        DesiredDisplayModeSpecs modeSpecs =
                createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs(
                        displayId);
                createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(displayId);
        Truth.assertThat(modeSpecs.baseModeId).isEqualTo(60);
        Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f);
        Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY);
@@ -92,7 +102,7 @@ public class DisplayModeDirectorTest {
        {
            int minFps = 60;
            int maxFps = 90;
            DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
            DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
            assertTrue(2 * numPriorities < maxFps - minFps + 1);
            SparseArray<Vote> votes = new SparseArray<>();
            SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
@@ -114,7 +124,7 @@ public class DisplayModeDirectorTest {
        // presence of higher priority votes.
        {
            assertTrue(numPriorities >= 2);
            DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
            DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
            SparseArray<Vote> votes = new SparseArray<>();
            SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
            votesByDisplay.put(displayId, votes);
@@ -131,7 +141,7 @@ public class DisplayModeDirectorTest {
    @Test
    public void testVotingWithFloatingPointErrors() {
        int displayId = 0;
        DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(50, 90);
        DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
        SparseArray<Vote> votes = new SparseArray<>();
        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
        votesByDisplay.put(displayId, votes);
@@ -154,7 +164,7 @@ public class DisplayModeDirectorTest {
        assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_SIZE);

        int displayId = 0;
        DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
        SparseArray<Vote> votes = new SparseArray<>();
        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
        votesByDisplay.put(displayId, votes);
@@ -202,7 +212,7 @@ public class DisplayModeDirectorTest {
                >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);

        int displayId = 0;
        DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
        SparseArray<Vote> votes = new SparseArray<>();
        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
        votesByDisplay.put(displayId, votes);
@@ -235,4 +245,61 @@ public class DisplayModeDirectorTest {
                .isWithin(FLOAT_TOLERANCE)
                .of(75);
    }

    void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps,
            float peakFps, float defaultFps, float primaryMin, float primaryMax,
            float appRequestMin, float appRequestMax) {
        DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(
                minFps, peakFps, defaultFps);
        Truth.assertThat(specs.primaryRefreshRateRange.min).isEqualTo(primaryMin);
        Truth.assertThat(specs.primaryRefreshRateRange.max).isEqualTo(primaryMax);
        Truth.assertThat(specs.appRequestRefreshRateRange.min).isEqualTo(appRequestMin);
        Truth.assertThat(specs.appRequestRefreshRateRange.max).isEqualTo(appRequestMax);
    }

    @Test
    public void testSpecsFromRefreshRateSettings() {
        // Confirm that, with varying settings for min, peak, and default refresh rate,
        // DesiredDisplayModeSpecs is calculated correctly.
        float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f};
        DisplayModeDirector director =
                createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);

        float inf = Float.POSITIVE_INFINITY;
        verifySpecsWithRefreshRateSettings(director, 0, 0, 0, 0, inf, 0, inf);
        verifySpecsWithRefreshRateSettings(director, 0, 0, 90, 0, 90, 0, inf);
        verifySpecsWithRefreshRateSettings(director, 0, 90, 0, 0, 90, 0, 90);
        verifySpecsWithRefreshRateSettings(director, 0, 90, 60, 0, 60, 0, 90);
        verifySpecsWithRefreshRateSettings(director, 0, 90, 120, 0, 90, 0, 90);
        verifySpecsWithRefreshRateSettings(director, 90, 0, 0, 90, inf, 0, inf);
        verifySpecsWithRefreshRateSettings(director, 90, 0, 120, 90, 120, 0, inf);
        verifySpecsWithRefreshRateSettings(director, 90, 0, 60, 90, inf, 0, inf);
        verifySpecsWithRefreshRateSettings(director, 90, 120, 0, 90, 120, 0, 120);
        verifySpecsWithRefreshRateSettings(director, 90, 60, 0, 90, 90, 0, 90);
        verifySpecsWithRefreshRateSettings(director, 60, 120, 90, 60, 90, 0, 120);
    }

    void verifyBrightnessObserverCall(DisplayModeDirector director, float minFps, float peakFps,
            float defaultFps, float brightnessObserverMin, float brightnessObserverMax) {
        BrightnessObserver brightnessObserver = Mockito.mock(BrightnessObserver.class);
        director.injectBrightnessObserver(brightnessObserver);
        director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(minFps, peakFps, defaultFps);
        verify(brightnessObserver)
                .onRefreshRateSettingChangedLocked(brightnessObserverMin, brightnessObserverMax);
    }

    @Test
    public void testBrightnessObserverCallWithRefreshRateSettings() {
        // Confirm that, with varying settings for min, peak, and default refresh rate, we make the
        // correct call to the brightness observer.
        float[] refreshRates = {60.f, 90.f, 120.f};
        DisplayModeDirector director =
                createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
        verifyBrightnessObserverCall(director, 0, 0, 0, 0, 0);
        verifyBrightnessObserverCall(director, 0, 0, 90, 0, 90);
        verifyBrightnessObserverCall(director, 0, 90, 0, 0, 90);
        verifyBrightnessObserverCall(director, 0, 90, 60, 0, 60);
        verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
        verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
    }
}