Merge "Add Settings switch to disable Game default frame rate" into main

This commit is contained in:
Andy Yu
2023-12-20 21:59:42 +00:00
committed by Android (Google) Code Review
9 changed files with 411 additions and 1 deletions

View File

@@ -139,6 +139,7 @@
<uses-permission android:name="android.permission.REMAP_MODIFIER_KEYS" />
<uses-permission android:name="android.permission.ACCESS_GPU_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
<application
android:name=".SettingsApplication"

View File

@@ -5,6 +5,13 @@ package: "com.android.settings.flags"
# NOTE: All Settings flags share the same Flags class, so prefix our
# flags with 'development' to prevent naming collision.
flag {
name: "development_game_default_frame_rate"
namespace: "game"
description: "This flag guards the new behavior with the addition of Game Default Frame Rate feature."
bug: "286084594"
}
flag {
name: "development_hdr_sdr_ratio"
namespace: "core_graphics"

View File

@@ -2639,8 +2639,12 @@
<string name="display_white_balance_summary"></string>
<!-- Display settings screen, setting option name to change Fold setting -->
<string name="fold_lock_behavior_title">Continue using apps on fold</string>
<!-- Display settings screen, game default frame rate settings title [CHAR LIMIT=30] -->
<string name="disable_game_default_frame_rate_title">Disable default frame rate for games</string>
<!-- Display settings screen, game default frame rate settings summary [CHAR LIMIT=NONE] -->
<string name="disable_game_default_frame_rate_summary">Disable limiting the maximum frame rate for games at <xliff:g id="frame_rate" example="60">%1$d</xliff:g> Hz.</string>
<!-- Display settings screen, peak refresh rate settings title [CHAR LIMIT=30] -->
<string name="peak_refresh_rate_title">Smooth Display</string>
<string name="peak_refresh_rate_title">Smooth display</string>
<!-- Display settings screen, peak refresh rate settings summary [CHAR LIMIT=NONE] -->
<string name="peak_refresh_rate_summary">Automatically raises the refresh rate up to <xliff:g name="refresh_rate" example="120">%1$d</xliff:g> Hz for some content. Increases battery usage.</string>
<!-- Display developer settings: Force to the highest refresh rate [CHAR LIMIT=NONE] -->

View File

@@ -255,6 +255,11 @@
android:title="@string/enable_angle_as_system_driver"
android:summary="@string/enable_angle_as_system_driver_summary" />
<SwitchPreferenceCompat
android:key="disable_game_default_frame_rate"
android:title="@string/disable_game_default_frame_rate_title"
android:summary="@string/disable_game_default_frame_rate_summary"/>
<Preference
android:key="graphics_driver_dashboard"
android:title="@string/graphics_driver_dashboard_title"

View File

@@ -688,6 +688,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
controllers.add(new HardwareLayersUpdatesPreferenceController(context));
controllers.add(new DebugGpuOverdrawPreferenceController(context));
controllers.add(new DebugNonRectClipOperationsPreferenceController(context));
controllers.add(new GameDefaultFrameRatePreferenceController(context));
controllers.add(new ForceDarkPreferenceController(context));
controllers.add(new EnableBlursPreferenceController(context));
controllers.add(new ForceMSAAPreferenceController(context));

View File

@@ -0,0 +1,66 @@
/*
* 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.settings.development;
import android.annotation.NonNull;
import android.os.SystemProperties;
/**
* Wrapper interface to access {@link SystemProperties}.
*
* @hide
*/
public interface DevelopmentSystemPropertiesWrapper {
/**
* Get the String value for the given {@code key}.
*
* @param key the key to lookup
* @param def the default value in case the property is not set or empty
* @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
* string otherwise
*/
@NonNull
String get(@NonNull String key, @NonNull String def);
/**
* Set the value for the given {@code key} to {@code val}.
*
* @throws IllegalArgumentException if the {@code val} exceeds 91 characters
* @throws RuntimeException if the property cannot be set, for example, if it was blocked by
* SELinux. libc will log the underlying reason.
*/
void set(@NonNull String key, @NonNull String val);
/**
* Get the Integer value for the given {@code key}.
*
* @param key the key to lookup
* @param def the default value in case the property is not set or empty
* @return if the {@code key} isn't found, return {@code def} if it isn't null, not parsable
* or an empty string otherwise
*/
@NonNull
int getInt(@NonNull String key, @NonNull int def);
/**
* Get the boolean value for the given {@code key}.
*
* @param key the key to lookup
* @param def the default value in case the property is not set or empty
* @return if the {@code key} isn't found, return {@code def}.
*/
boolean getBoolean(@NonNull String key, @NonNull boolean def);
}

View File

@@ -0,0 +1,146 @@
/*
* 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.settings.development;
import android.app.IGameManagerService;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.flags.Flags;
import com.android.settingslib.development.DeveloperOptionsPreferenceController;
public class GameDefaultFrameRatePreferenceController extends DeveloperOptionsPreferenceController
implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
private static final String TAG = "GameDefFrameRatePrefCtr";
private static final String DISABLE_GAME_DEFAULT_FRAME_RATE_KEY =
"disable_game_default_frame_rate";
private final IGameManagerService mGameManagerService;
static final String PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED =
"debug.graphics.game_default_frame_rate.disabled";
private final DevelopmentSystemPropertiesWrapper mSysProps;
private int mGameDefaultFrameRateValue;
@VisibleForTesting
static class Injector {
public DevelopmentSystemPropertiesWrapper createSystemPropertiesWrapper() {
return new DevelopmentSystemPropertiesWrapper() {
@Override
public String get(String key, String def) {
return SystemProperties.get(key, def);
}
@Override
public boolean getBoolean(String key, boolean def) {
return SystemProperties.getBoolean(key, def);
}
@Override
public int getInt(String key, int def) {
return SystemProperties.getInt(key, def);
}
@Override
public void set(String key, String val) {
SystemProperties.set(key, val);
}
};
}
}
public GameDefaultFrameRatePreferenceController(Context context) {
super(context);
mGameManagerService = IGameManagerService.Stub.asInterface(
ServiceManager.getService(Context.GAME_SERVICE));
mSysProps = new Injector().createSystemPropertiesWrapper();
mGameDefaultFrameRateValue = mSysProps.getInt(
"ro.surface_flinger.game_default_frame_rate_override", 60);
}
@VisibleForTesting
GameDefaultFrameRatePreferenceController(Context context,
IGameManagerService gameManagerService,
Injector injector) {
super(context);
mGameManagerService = gameManagerService;
mSysProps = injector.createSystemPropertiesWrapper();
}
@Override
public String getPreferenceKey() {
return DISABLE_GAME_DEFAULT_FRAME_RATE_KEY;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean isDisabled = (Boolean) newValue;
try {
mGameManagerService.toggleGameDefaultFrameRate(!isDisabled);
updateGameDefaultPreferenceSetting();
} catch (RemoteException e) {
// intentional no-op
}
return true;
}
private void updateGameDefaultPreferenceSetting() {
final boolean isDisabled =
mSysProps.getBoolean(PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED,
false);
((TwoStatePreference) mPreference).setChecked(isDisabled);
mPreference.setSummary(mContext.getString(
R.string.disable_game_default_frame_rate_summary,
mGameDefaultFrameRateValue));
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
updateGameDefaultPreferenceSetting();
}
@Override
public boolean isAvailable() {
return Flags.developmentGameDefaultFrameRate();
}
@Override
protected void onDeveloperOptionsSwitchDisabled() {
super.onDeveloperOptionsSwitchDisabled();
final TwoStatePreference preference = (TwoStatePreference) mPreference;
if (preference.isChecked()) {
// When the developer option is disabled, we should set everything
// to off, that is, enabling game default frame rate.
try {
mGameManagerService.toggleGameDefaultFrameRate(true);
} catch (RemoteException e) {
// intentional no-op
}
}
preference.setChecked(false);
}
}

View File

@@ -1,3 +1,6 @@
# GameDefaultFrameRatePreferenceController
per-file GameDefaultFrameRatePreferenceController.java=file:platform/frameworks/base:/GAME_MANAGER_OWNERS
# ShowHdrSdrRatioPreferenceController
per-file ShowHdrSdrRatioPreferenceController.java=file:platform/frameworks/native:/services/surfaceflinger/OWNERS

View File

@@ -0,0 +1,177 @@
/*
* 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.settings.development;
import static com.android.settings.development.GameDefaultFrameRatePreferenceController.Injector;
import static com.android.settings.development.GameDefaultFrameRatePreferenceController.PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.IGameManagerService;
import android.content.Context;
import android.os.RemoteException;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settings.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class GameDefaultFrameRatePreferenceControllerTest {
@Mock
private Context mContext;
@Mock
private PreferenceScreen mScreen;
@Mock
private TwoStatePreference mPreference;
@Mock
private IGameManagerService mGameManagerService;
@Mock
private DevelopmentSystemPropertiesWrapper mSysPropsMock;
private GameDefaultFrameRatePreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new GameDefaultFrameRatePreferenceController(mContext, mGameManagerService,
new Injector(){
@Override
public DevelopmentSystemPropertiesWrapper createSystemPropertiesWrapper() {
return mSysPropsMock;
}
});
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
mController.displayPreference(mScreen);
}
@Test
public void onPreferenceChange_settingEnabled_shouldChecked() throws RemoteException {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVELOPMENT_GAME_DEFAULT_FRAME_RATE);
assertTrue(mController.isAvailable());
when(mSysPropsMock.getBoolean(
ArgumentMatchers.eq(PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED),
ArgumentMatchers.eq(false)))
.thenReturn(true);
mController.onPreferenceChange(mPreference, true /* new value */);
verify(mPreference).setChecked(true);
}
@Test
public void onPreferenceChange_settingDisabled_shouldUnchecked() throws RemoteException {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVELOPMENT_GAME_DEFAULT_FRAME_RATE);
assertTrue(mController.isAvailable());
when(mSysPropsMock.getBoolean(
ArgumentMatchers.eq(PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED),
ArgumentMatchers.eq(false)))
.thenReturn(false);
mController.onPreferenceChange(mPreference, false /* new value */);
verify(mPreference).setChecked(false);
}
@Test
public void updateState_settingEnabled_shouldChecked() throws RemoteException {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVELOPMENT_GAME_DEFAULT_FRAME_RATE);
assertTrue(mController.isAvailable());
when(mSysPropsMock.getBoolean(
ArgumentMatchers.eq(PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED),
ArgumentMatchers.eq(false)))
.thenReturn(true);
mController.updateState(mPreference);
verify(mPreference).setChecked(true);
}
@Test
public void updateState_settingDisabled_shouldUnchecked() throws RemoteException {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVELOPMENT_GAME_DEFAULT_FRAME_RATE);
assertTrue(mController.isAvailable());
when(mSysPropsMock.getBoolean(
ArgumentMatchers.eq(PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED),
ArgumentMatchers.eq(false)))
.thenReturn(false);
mController.updateState(mPreference);
verify(mPreference).setChecked(false);
}
@Test
public void settingNotAvailable_flagsOff() {
mSetFlagsRule.disableFlags(Flags.FLAG_DEVELOPMENT_GAME_DEFAULT_FRAME_RATE);
mController = new GameDefaultFrameRatePreferenceController(
mContext, mGameManagerService, new Injector());
assertFalse(mController.isAvailable());
}
@Test
public void settingAvailable_flagsOn() {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVELOPMENT_GAME_DEFAULT_FRAME_RATE);
mController = new GameDefaultFrameRatePreferenceController(
mContext, mGameManagerService, new Injector());
assertTrue(mController.isAvailable());
}
@Test
public void onDeveloperOptionsSwitchDisabled_preferenceUnchecked_shouldNotTurnOffPreference()
throws RemoteException {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVELOPMENT_GAME_DEFAULT_FRAME_RATE);
when(mSysPropsMock.getBoolean(
ArgumentMatchers.eq(PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED),
ArgumentMatchers.eq(false)))
.thenReturn(false);
assertTrue(mController.isAvailable());
when(mPreference.isChecked()).thenReturn(false);
mController.onDeveloperOptionsSwitchDisabled();
verify(mPreference).setChecked(false);
verify(mPreference).setEnabled(false);
}
@Test
public void onDeveloperOptionsSwitchDisabled_preferenceChecked_shouldTurnOffPreference()
throws RemoteException {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVELOPMENT_GAME_DEFAULT_FRAME_RATE);
when(mSysPropsMock.getBoolean(
ArgumentMatchers.eq(PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED),
ArgumentMatchers.eq(false)))
.thenReturn(true);
assertTrue(mController.isAvailable());
when(mPreference.isChecked()).thenReturn(true);
mController.onDeveloperOptionsSwitchDisabled();
verify(mPreference).setChecked(false);
verify(mPreference).setEnabled(false);
}
}