diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 36d31800856093ee8bc637e2e8c3b30f3d3026ed..5c64389e8c27bbf87d90cbcfe31d4151228cce8f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4866,7 +4866,8 @@ public final class Settings { "display_color_mode_vendor_hint"; /** - * The user selected min refresh rate in frames per second. + * The user selected min refresh rate in frames per second. If infinite, the user wants + * the highest possible refresh rate. * * If this isn't set, 0 will be used. * @hide @@ -4875,7 +4876,8 @@ public final class Settings { public static final String MIN_REFRESH_RATE = "min_refresh_rate"; /** - * The user selected peak refresh rate in frames per second. + * The user selected peak refresh rate in frames per second. If infinite, the user wants + * the highest possible refresh rate. * * If this isn't set, the system falls back to a device specific default. * @hide diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..e55c64199f4562d5e28734a8edc1a2c1e9162ce6 --- /dev/null +++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.display; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.util.Log; +import android.view.Display; + +/** + * Constants and utility methods for refresh rate settings. + */ +public class RefreshRateSettingsUtils { + + private static final String TAG = "RefreshRateSettingsUtils"; + + public static final float DEFAULT_REFRESH_RATE = 60f; + + /** + * Find the highest refresh rate among all the modes of the default display. + * + * @param context The context + * @return The highest refresh rate + */ + public static float findHighestRefreshRateForDefaultDisplay(Context context) { + final DisplayManager dm = context.getSystemService(DisplayManager.class); + final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY); + + if (display == null) { + Log.w(TAG, "No valid default display device"); + return DEFAULT_REFRESH_RATE; + } + + float maxRefreshRate = DEFAULT_REFRESH_RATE; + for (Display.Mode mode : display.getSupportedModes()) { + if (mode.getRefreshRate() > maxRefreshRate) { + maxRefreshRate = mode.getRefreshRate(); + } + } + return maxRefreshRate; + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index 248c60cb4fe9bd277b07af5ba43713d54913bf69..f5d9475fb0494067ec9dee6584179a2f8639d243 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -101,5 +101,7 @@ public class SystemSettings { Settings.System.CAMERA_FLASH_NOTIFICATION, Settings.System.SCREEN_FLASH_NOTIFICATION, Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, + Settings.System.PEAK_REFRESH_RATE, + Settings.System.MIN_REFRESH_RATE, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 29f27f74bca476d00a9d489147d79069371db8ef..410269f240e0cfa5a18c0ed45a09e97cf4d8ec53 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -21,6 +21,7 @@ import static android.provider.settings.validators.SettingsValidators.ANY_STRING import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_FLOAT_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.URI_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.VIBRATION_INTENSITY_VALIDATOR; @@ -236,5 +237,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.CAMERA_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR); VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR); VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR); + VALIDATORS.put(System.PEAK_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR); + VALIDATORS.put(System.MIN_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 34d3d446530bd4233c71577719f3899d37bbaaac..46cd725ad582ea68d94d9917d3b565d1ab1e8cc7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -121,6 +121,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; +import com.android.internal.display.RefreshRateSettingsUtils; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FrameworkStatsLog; import com.android.providers.settings.SettingsState.Setting; @@ -3878,7 +3879,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 222; + private static final int SETTINGS_VERSION = 223; private final int mUserId; @@ -5935,10 +5936,6 @@ public class SettingsProvider extends ContentProvider { if (currentVersion == 218) { // Version 219: Removed - // TODO(b/211737588): Back up the Smooth Display setting - // Future upgrades to the `peak_refresh_rate` and `min_refresh_rate` settings - // should account for the database in a non-upgraded and upgraded (change id: - // Ib2cb2dd100f06f5452083b7606109a486e795a0e) state. currentVersion = 219; } @@ -6004,6 +6001,56 @@ public class SettingsProvider extends ContentProvider { currentVersion = 222; } + // Version 222: Set peak refresh rate and min refresh rate to infinity if it's + // meant to be the highest possible refresh rate. This is needed so that we can + // back up and restore those settings on other devices. Other devices might have + // different highest possible refresh rates. + if (currentVersion == 222) { + final SettingsState systemSettings = getSystemSettingsLocked(userId); + final Setting peakRefreshRateSetting = + systemSettings.getSettingLocked(Settings.System.PEAK_REFRESH_RATE); + final Setting minRefreshRateSetting = + systemSettings.getSettingLocked(Settings.System.MIN_REFRESH_RATE); + float highestRefreshRate = RefreshRateSettingsUtils + .findHighestRefreshRateForDefaultDisplay(getContext()); + + if (!peakRefreshRateSetting.isNull()) { + try { + float peakRefreshRate = + Float.parseFloat(peakRefreshRateSetting.getValue()); + if (Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) { + systemSettings.insertSettingLocked( + Settings.System.PEAK_REFRESH_RATE, + String.valueOf(Float.POSITIVE_INFINITY), + /* tag= */ null, + /* makeDefault= */ false, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } catch (NumberFormatException e) { + // Do nothing. Leave the value as is. + } + } + + if (!minRefreshRateSetting.isNull()) { + try { + float minRefreshRate = + Float.parseFloat(minRefreshRateSetting.getValue()); + if (Math.round(minRefreshRate) == Math.round(highestRefreshRate)) { + systemSettings.insertSettingLocked( + Settings.System.MIN_REFRESH_RATE, + String.valueOf(Float.POSITIVE_INFINITY), + /* tag= */ null, + /* makeDefault= */ false, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } catch (NumberFormatException e) { + // Do nothing. Leave the value as is. + } + } + + currentVersion = 223; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 093bde7d672c6e0093ce58a367136caa91f9cbda..7bca944033d95bdecffcca0e7093c9b76ac8502f 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -98,8 +98,6 @@ public class SettingsBackupTest { Settings.System.VOLUME_VOICE, // deprecated since API 2? Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug? Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only - Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities - Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities Settings.System.SCREEN_BRIGHTNESS_FLOAT, Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index e66fa5b3d51616e248178cea7e0ccdae71d00b57..f7b98691f3d78647d312a30cea35094703d3b607 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -55,6 +55,10 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY, Flags::enableModeLimitForExternalDisplay); + private final FlagState mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState = new FlagState( + Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE, + Flags::backUpSmoothDisplayAndForcePeakRefreshRate); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); @@ -108,6 +112,10 @@ public class DisplayManagerFlags { return mDisplayOffloadFlagState.isEnabled(); } + public boolean isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled() { + return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled(); + } + private static class FlagState { private final String mName; diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 542f26cbec693fd19afb16cf740b57f798662d8d..c24b3ca262be04c49163fc1fb055b688960778a1 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -72,3 +72,11 @@ flag { bug: "299521647" is_fixed_read_only: true } + +flag { + name: "back_up_smooth_display_and_force_peak_refresh_rate" + namespace: "display_manager" + description: "Feature flag for backing up Smooth Display and Force Peak Refresh Rate" + bug: "211737588" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 71ea8cc30405881cbeca8f9f49292afedafcc24f..ca23844044caee7601cba8384ec08f86a0615264 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -22,6 +22,7 @@ import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT; import static android.view.Display.Mode.INVALID_MODE_ID; import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE; +import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay; import android.annotation.IntegerRes; import android.annotation.NonNull; @@ -176,6 +177,8 @@ public class DisplayModeDirector { private final boolean mIsDisplaysRefreshRatesSynchronizationEnabled; + private final boolean mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled; + public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler, @NonNull DisplayManagerFlags displayManagerFlags) { this(context, handler, new RealInjector(context), displayManagerFlags); @@ -191,6 +194,8 @@ public class DisplayModeDirector { .isExternalDisplayLimitModeEnabled(); mIsDisplaysRefreshRatesSynchronizationEnabled = displayManagerFlags .isDisplaysRefreshRatesSynchronizationEnabled(); + mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags + .isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled(); mContext = context; mHandler = new DisplayModeDirectorHandler(handler.getLooper()); mInjector = injector; @@ -1193,8 +1198,7 @@ public class DisplayModeDirector { public void observe() { final ContentResolver cr = mContext.getContentResolver(); mInjector.registerPeakRefreshRateObserver(cr, this); - cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this, - UserHandle.USER_SYSTEM); + mInjector.registerMinRefreshRateObserver(cr, this); cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM); cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/, @@ -1292,10 +1296,34 @@ public class DisplayModeDirector { private void updateRefreshRateSettingLocked() { final ContentResolver cr = mContext.getContentResolver(); + float highestRefreshRate = findHighestRefreshRateForDefaultDisplay(mContext); + float minRefreshRate = Settings.System.getFloatForUser(cr, Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId()); + if (Float.isInfinite(minRefreshRate)) { + // Infinity means that we want the highest possible refresh rate + minRefreshRate = highestRefreshRate; + + if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) { + // The flag had been turned off, we need to restore the original value + Settings.System.putFloatForUser(cr, + Settings.System.MIN_REFRESH_RATE, minRefreshRate, cr.getUserId()); + } + } + float peakRefreshRate = Settings.System.getFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId()); + if (Float.isInfinite(peakRefreshRate)) { + // Infinity means that we want the highest possible refresh rate + peakRefreshRate = highestRefreshRate; + + if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) { + // The flag had been turned off, we need to restore the original value + Settings.System.putFloatForUser(cr, + Settings.System.PEAK_REFRESH_RATE, peakRefreshRate, cr.getUserId()); + } + } + updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate); } @@ -3082,6 +3110,7 @@ public class DisplayModeDirector { interface Injector { Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE); + Uri MIN_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE); @NonNull DeviceConfigInterface getDeviceConfig(); @@ -3089,6 +3118,9 @@ public class DisplayModeDirector { void registerPeakRefreshRateObserver(@NonNull ContentResolver cr, @NonNull ContentObserver observer); + void registerMinRefreshRateObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer); + void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, Handler handler); @@ -3139,6 +3171,13 @@ public class DisplayModeDirector { observer, UserHandle.USER_SYSTEM); } + @Override + public void registerMinRefreshRateObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + cr.registerContentObserver(MIN_REFRESH_RATE_URI, false /*notifyDescendants*/, + observer, UserHandle.USER_SYSTEM); + } + @Override public void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5c50acb13f305671cb212e9f997cbeb2e6c04f52 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import android.hardware.display.DisplayManager; +import android.testing.TestableContext; +import android.view.Display; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.display.RefreshRateSettingsUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RefreshRateSettingsUtilsTest { + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + @Mock + private DisplayManager mDisplayManagerMock; + @Mock + private Display mDisplayMock; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext.addMockSystemService(DisplayManager.class, mDisplayManagerMock); + + Display.Mode[] modes = new Display.Mode[]{ + new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600, + /* refreshRate= */ 60), + new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600, + /* refreshRate= */ 120), + new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600, + /* refreshRate= */ 90) + }; + + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); + when(mDisplayMock.getSupportedModes()).thenReturn(modes); + } + + @Test + public void testFindHighestRefreshRateForDefaultDisplay() { + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null); + assertEquals(DEFAULT_REFRESH_RATE, + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), + /* delta= */ 0); + + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); + assertEquals(120, + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), + /* delta= */ 0); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index b8c18e070397fae63245ea37e8f4d703d7100a70..c4f72b307eb79e10527fa8ba6dc3c6cefa705bd9 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -27,6 +27,7 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_R import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.server.display.mode.Vote.INVALID_SIZE; import static com.google.common.truth.Truth.assertThat; @@ -85,10 +86,12 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.internal.display.BrightnessSynchronizer; +import com.android.internal.display.RefreshRateSettingsUtils; import com.android.internal.os.BackgroundThread; import com.android.internal.util.Preconditions; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; +import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.TestUtils; import com.android.server.display.feature.DisplayManagerFlags; @@ -106,7 +109,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.util.ArrayList; @@ -252,9 +255,15 @@ public class DisplayModeDirectorTest { @Mock private DisplayManagerFlags mDisplayManagerFlags; + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = + new ExtendedMockitoRule.Builder(this) + .setStrictness(Strictness.LENIENT) + .spyStatic(RefreshRateSettingsUtils.class) + .build(); + @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext); when(mContext.getContentResolver()).thenReturn(resolver); @@ -1469,6 +1478,94 @@ public class DisplayModeDirectorTest { assertThat(vote.disableRefreshRateSwitching).isTrue(); } + @Test + public void testPeakRefreshRate_FlagEnabled() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(true); + float highestRefreshRate = 130; + doReturn(highestRefreshRate).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setPeakRefreshRate(Float.POSITIVE_INFINITY); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ + highestRefreshRate); + } + + @Test + public void testPeakRefreshRate_FlagDisabled() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(false); + float peakRefreshRate = 130; + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setPeakRefreshRate(peakRefreshRate); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ + peakRefreshRate); + } + + @Test + public void testMinRefreshRate_FlagEnabled() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(true); + float highestRefreshRate = 130; + doReturn(highestRefreshRate).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setMinRefreshRate(Float.POSITIVE_INFINITY); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + } + + @Test + public void testMinRefreshRate_FlagDisabled() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(false); + float minRefreshRate = 130; + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setMinRefreshRate(minRefreshRate); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + } + @Test public void testSensorRegistration() { // First, configure brightness zones or DMD won't register for sensor data. @@ -3167,6 +3264,13 @@ public class DisplayModeDirectorTest { waitForIdleSync(); } + private void setMinRefreshRate(float fps) { + Settings.System.putFloat(mContext.getContentResolver(), Settings.System.MIN_REFRESH_RATE, + fps); + mInjector.notifyMinRefreshRateChanged(); + waitForIdleSync(); + } + private static SensorManager createMockSensorManager(Sensor... sensors) { SensorManager sensorManager = mock(SensorManager.class); when(sensorManager.getSensorList(anyInt())).then((invocation) -> { @@ -3216,6 +3320,7 @@ public class DisplayModeDirectorTest { private final SensorManagerInternal mSensorManagerInternal; private ContentObserver mPeakRefreshRateObserver; + private ContentObserver mMinRefreshRateObserver; FakesInjector() { this(null, null, null); @@ -3246,6 +3351,12 @@ public class DisplayModeDirectorTest { mPeakRefreshRateObserver = observer; } + @Override + public void registerMinRefreshRateObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + mMinRefreshRateObserver = observer; + } + @Override public void registerDisplayListener(DisplayListener listener, Handler handler) {} @@ -3318,5 +3429,12 @@ public class DisplayModeDirectorTest { PEAK_REFRESH_RATE_URI); } } + + void notifyMinRefreshRateChanged() { + if (mMinRefreshRateObserver != null) { + mMinRefreshRateObserver.dispatchChange(false /*selfChange*/, + MIN_REFRESH_RATE_URI); + } + } } }