From f823057fe6be0387381f9705d3ae70db0f4680cb Mon Sep 17 00:00:00 2001 From: Xiang Wang Date: Tue, 2 Aug 2022 14:07:17 -0700 Subject: [PATCH 1/2] Add new GameManagerService methods to update and get scaling factor Change the scaling field type from string to boolean and use default value -1f instead of "1" or null to indicate that scaling is inactive Bug: b/240335717 Test: atest GameManagerServiceTests Change-Id: Ie29ac45d35baa11c5f1ae04b795a8832c51866cd --- .../java/android/app/IGameManagerService.aidl | 2 + .../server/app/GameManagerService.java | 115 +++++++++++++--- .../server/app/GameManagerShellCommand.java | 79 ++--------- .../server/app/GameManagerServiceTests.java | 128 ++++++++++++++++-- 4 files changed, 223 insertions(+), 101 deletions(-) diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl index e60a74acdc1c6..481e7b0050ab2 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -31,4 +31,6 @@ interface IGameManagerService { void setGameState(String packageName, in GameState gameState, int userId); GameModeInfo getGameModeInfo(String packageName, int userId); void setGameServiceProvider(String packageName); + void updateResolutionScalingFactor(String packageName, int gameMode, float scalingFactor, int userId); + float getResolutionScalingFactor(String packageName, int gameMode, int userId); } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index d5e893984bf37..60de31cf8cf46 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -649,6 +649,13 @@ public final class GameManagerService extends IGameManagerService.Stub { return xmlFound; } + GameModeConfiguration getOrAddDefaultGameModeConfiguration(int gameMode) { + synchronized (mModeConfigLock) { + mModeConfigs.putIfAbsent(gameMode, new GameModeConfiguration(gameMode)); + return mModeConfigs.get(gameMode); + } + } + /** * GameModeConfiguration contains all the values for all the interventions associated with * a game mode. @@ -659,17 +666,26 @@ public final class GameManagerService extends IGameManagerService.Stub { public static final String MODE_KEY = "mode"; public static final String SCALING_KEY = "downscaleFactor"; public static final String FPS_KEY = "fps"; - public static final String DEFAULT_SCALING = "1.0"; - public static final String DEFAULT_FPS = ""; public static final String ANGLE_KEY = "useAngle"; public static final String LOADING_BOOST_KEY = "loadingBoost"; + public static final float DEFAULT_SCALING = -1f; + public static final String DEFAULT_FPS = ""; + public static final boolean DEFAULT_USE_ANGLE = false; + public static final int DEFAULT_LOADING_BOOST_DURATION = -1; + private final @GameMode int mGameMode; - private String mScaling; - private String mFps; + private float mScaling = DEFAULT_SCALING; + private String mFps = DEFAULT_FPS; private final boolean mUseAngle; private final int mLoadingBoostDuration; + GameModeConfiguration(int gameMode) { + mGameMode = gameMode; + mUseAngle = DEFAULT_USE_ANGLE; + mLoadingBoostDuration = DEFAULT_LOADING_BOOST_DURATION; + } + GameModeConfiguration(KeyValueListParser parser) { mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED); // isGameModeOptedIn() returns if an app will handle all of the changes necessary @@ -677,7 +693,7 @@ public final class GameManagerService extends IGameManagerService.Stub { // GameManagerService) will not do anything for the app (like window scaling or // using ANGLE). mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode) - ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING); + ? DEFAULT_SCALING : parser.getFloat(SCALING_KEY, DEFAULT_SCALING); mFps = mAllowFpsOverride && !willGamePerformOptimizations(mGameMode) ? parser.getString(FPS_KEY, DEFAULT_FPS) : DEFAULT_FPS; @@ -686,17 +702,18 @@ public final class GameManagerService extends IGameManagerService.Stub { // - The app has not opted in to performing the work itself AND // - The Phenotype config has enabled it. mUseAngle = mAllowAngle && !willGamePerformOptimizations(mGameMode) - && parser.getBoolean(ANGLE_KEY, false); + && parser.getBoolean(ANGLE_KEY, DEFAULT_USE_ANGLE); - mLoadingBoostDuration = willGamePerformOptimizations(mGameMode) ? -1 - : parser.getInt(LOADING_BOOST_KEY, -1); + mLoadingBoostDuration = willGamePerformOptimizations(mGameMode) + ? DEFAULT_LOADING_BOOST_DURATION + : parser.getInt(LOADING_BOOST_KEY, DEFAULT_LOADING_BOOST_DURATION); } public int getGameMode() { return mGameMode; } - public synchronized String getScaling() { + public synchronized float getScaling() { return mScaling; } @@ -712,7 +729,7 @@ public final class GameManagerService extends IGameManagerService.Stub { return mLoadingBoostDuration; } - public synchronized void setScaling(String scaling) { + public synchronized void setScaling(float scaling) { mScaling = scaling; } @@ -740,7 +757,7 @@ public final class GameManagerService extends IGameManagerService.Stub { * Get the corresponding compat change id for the current scaling string. */ public long getCompatChangeId() { - return GameManagerService.getCompatChangeId(mScaling); + return GameManagerService.getCompatChangeId(String.valueOf(mScaling)); } } @@ -813,7 +830,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } /** - * Insert a new GameModeConfiguration + * Inserts a new GameModeConfiguration */ public void addModeConfig(GameModeConfiguration config) { if (config.isActive()) { @@ -937,7 +954,7 @@ public final class GameManagerService extends IGameManagerService.Stub { private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) { synchronized (mLock) { if (!mSettings.containsKey(userId)) { - Slog.w(TAG, "User ID '" + userId + "' does not have a Game Mode" + Slog.d(TAG, "User ID '" + userId + "' does not have a Game Mode" + " selected for package: '" + packageName + "'"); return GameManager.GAME_MODE_UNSUPPORTED; } @@ -1163,6 +1180,66 @@ public final class GameManagerService extends IGameManagerService.Stub { mGameServiceController.setGameServiceProvider(packageName); } + + /** + * Updates the resolution scaling factor for the package's target game mode and activates it. + * + * @param scalingFactor enable scaling override over any other compat scaling if positive, + * disable otherwise + * @throws SecurityException if caller doesn't have + * {@link android.Manifest.permission#MANAGE_GAME_MODE} + * permission. + * @throws IllegalArgumentException if the user ID provided doesn't exist. + */ + @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public void updateResolutionScalingFactor(String packageName, int gameMode, float scalingFactor, + int userId) throws SecurityException, IllegalArgumentException { + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + synchronized (mLock) { + if (!mSettings.containsKey(userId)) { + throw new IllegalArgumentException("User " + userId + " wasn't started"); + } + } + setGameModeConfigOverride(packageName, userId, gameMode, null /*fpsStr*/, + Float.toString(scalingFactor)); + } + + /** + * Gets the resolution scaling factor for the package's target game mode. + * + * @return scaling factor for the game mode if exists or negative value otherwise. + * @throws SecurityException if caller doesn't have + * {@link android.Manifest.permission#MANAGE_GAME_MODE} + * permission. + * @throws IllegalArgumentException if the user ID provided doesn't exist. + */ + @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public float getResolutionScalingFactor(String packageName, int gameMode, int userId) + throws SecurityException, IllegalArgumentException { + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + synchronized (mLock) { + if (!mSettings.containsKey(userId)) { + throw new IllegalArgumentException("User " + userId + " wasn't started"); + } + } + return getResolutionScalingFactorInternal(packageName, gameMode, userId); + } + + float getResolutionScalingFactorInternal(String packageName, int gameMode, int userId) { + final GamePackageConfiguration packageConfig = getConfig(packageName); + if (packageConfig == null) { + return GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING; + } + final GamePackageConfiguration.GameModeConfiguration modeConfig = + packageConfig.getGameModeConfiguration(gameMode); + if (modeConfig != null) { + return modeConfig.getScaling(); + } + return GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING; + } + /** * Notified when boot is completed. */ @@ -1319,6 +1396,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } long scaleId = modeConfig.getCompatChangeId(); if (scaleId == 0) { + disableCompatScale(packageName); Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for " + packageName); return; @@ -1408,7 +1486,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } // modify GameModeConfiguration intervention settings GamePackageConfiguration.GameModeConfiguration overrideModeConfig = - overrideConfig.getGameModeConfiguration(gameMode); + overrideConfig.getOrAddDefaultGameModeConfiguration(gameMode); if (fpsStr != null) { overrideModeConfig.setFpsStr(fpsStr); @@ -1417,10 +1495,7 @@ public final class GameManagerService extends IGameManagerService.Stub { GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS); } if (scaling != null) { - overrideModeConfig.setScaling(scaling); - } else { - overrideModeConfig.setScaling( - GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING); + overrideModeConfig.setScaling(Float.parseFloat(scaling)); } Slog.i(TAG, "Package Name: " + packageName + " FPS: " + String.valueOf(overrideModeConfig.getFps()) @@ -1470,7 +1545,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } // If the game mode to reset is the only mode other than standard mode, - // The override config is removed. + // the override config is removed. if (modes.length <= 2) { synchronized (mOverrideConfigLock) { mOverrideConfigs.remove(packageName); @@ -1646,7 +1721,7 @@ public final class GameManagerService extends IGameManagerService.Stub { final int useAngle = gameModeConfiguration.getUseAngle() ? 1 : 0; sb.append(TextUtils.formatSimple("angle=%d", useAngle)); sb.append(","); - final String scaling = gameModeConfiguration.getScaling(); + final float scaling = gameModeConfiguration.getScaling(); sb.append("scaling="); sb.append(scaling); sb.append(","); diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java index 470c320dbd7d2..487d19aa6aba5 100644 --- a/services/core/java/com/android/server/app/GameManagerShellCommand.java +++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java @@ -16,21 +16,6 @@ package com.android.server.app; -import static com.android.server.wm.CompatModePackages.DOWNSCALED; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_30; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_35; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_40; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_45; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_50; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_55; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_60; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_65; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_70; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_75; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_80; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_85; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_90; - import android.app.ActivityManager; import android.app.GameManager; import android.app.IGameManagerService; @@ -39,7 +24,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.ShellCommand; -import android.util.ArraySet; import java.io.PrintWriter; import java.util.Locale; @@ -53,23 +37,6 @@ public class GameManagerShellCommand extends ShellCommand { public GameManagerShellCommand() {} - private static final ArraySet DOWNSCALE_CHANGE_IDS = new ArraySet<>(new Long[]{ - DOWNSCALED, - DOWNSCALE_90, - DOWNSCALE_85, - DOWNSCALE_80, - DOWNSCALE_75, - DOWNSCALE_70, - DOWNSCALE_65, - DOWNSCALE_60, - DOWNSCALE_55, - DOWNSCALE_50, - DOWNSCALE_45, - DOWNSCALE_40, - DOWNSCALE_35, - DOWNSCALE_30, - }); - @Override public int onCommand(String cmd) { if (cmd == null) { @@ -212,11 +179,15 @@ public class GameManagerShellCommand extends ShellCommand { case "--downscale": if (downscaleRatio == null) { downscaleRatio = getNextArgRequired(); - if (downscaleRatio != null - && GameManagerService.getCompatChangeId(downscaleRatio) == 0 - && !downscaleRatio.equals("disable")) { - pw.println("Invalid scaling ratio '" + downscaleRatio + "'"); - return -1; + if ("disable".equals(downscaleRatio)) { + downscaleRatio = "-1"; + } else { + try { + Float.parseFloat(downscaleRatio); + } catch (NumberFormatException e) { + pw.println("Invalid scaling ratio '" + downscaleRatio + "'"); + return -1; + } } } else { pw.println("Duplicate option '" + option + "'"); @@ -249,40 +220,16 @@ public class GameManagerShellCommand extends ShellCommand { final GameManagerService gameManagerService = (GameManagerService) ServiceManager.getService(Context.GAME_SERVICE); - boolean batteryModeSupported = false; - boolean perfModeSupported = false; - int [] modes = gameManagerService.getAvailableGameModes(packageName); - - for (int mode : modes) { - if (mode == GameManager.GAME_MODE_PERFORMANCE) { - perfModeSupported = true; - } else if (mode == GameManager.GAME_MODE_BATTERY) { - batteryModeSupported = true; - } - } - switch (gameMode.toLowerCase(Locale.getDefault())) { case "2": case "performance": - if (perfModeSupported) { - gameManagerService.setGameModeConfigOverride(packageName, userId, - GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio); - } else { - pw.println("Game mode: " + gameMode + " not supported by " - + packageName); - return -1; - } + gameManagerService.setGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio); break; case "3": case "battery": - if (batteryModeSupported) { - gameManagerService.setGameModeConfigOverride(packageName, userId, - GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio); - } else { - pw.println("Game mode: " + gameMode + " not supported by " - + packageName); - return -1; - } + gameManagerService.setGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio); break; default: pw.println("Invalid game mode: " + gameMode); diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index d325e47f76879..b3a7ad0486660 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -578,7 +578,7 @@ public class GameManagerServiceTests { } private void checkDownscaling(GameManagerService gameManagerService, - int gameMode, String scaling) { + int gameMode, float scaling) { if (gameManagerService == null) { gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); startUser(gameManagerService, USER_ID_1); @@ -586,7 +586,7 @@ public class GameManagerServiceTests { } GameManagerService.GamePackageConfiguration config = gameManagerService.getConfig(mPackageName); - assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling); + assertEquals(scaling, config.getGameModeConfiguration(gameMode).getScaling(), 0.01f); } private void checkAngleEnabled(GameManagerService gameManagerService, int gameMode, @@ -715,7 +715,7 @@ public class GameManagerServiceTests { checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.3"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f); checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120); } @@ -735,7 +735,7 @@ public class GameManagerServiceTests { checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.5"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.5f); checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60); } @@ -757,9 +757,9 @@ public class GameManagerServiceTests { checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.3"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f); checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.5"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.5f); checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60); } @@ -782,7 +782,7 @@ public class GameManagerServiceTests { checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.5f); checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90); } @@ -805,7 +805,7 @@ public class GameManagerServiceTests { checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.7"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f); checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30); } @@ -829,9 +829,9 @@ public class GameManagerServiceTests { checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.5f); checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.7"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f); checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30); } @@ -858,9 +858,9 @@ public class GameManagerServiceTests { checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.3"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f); checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120); - checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.7"); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f); checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30); } @@ -933,7 +933,7 @@ public class GameManagerServiceTests { public void testInterventionAllowScalingDefault() throws Exception { mockDeviceConfigPerformance(); mockModifyGameModeGranted(); - checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, 0.5f); } /** @@ -944,7 +944,7 @@ public class GameManagerServiceTests { mockDeviceConfigPerformance(); mockInterventionAllowDownscaleFalse(); mockModifyGameModeGranted(); - checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, "1.0"); + checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, -1.0f); } /** @@ -956,7 +956,7 @@ public class GameManagerServiceTests { mockDeviceConfigPerformance(); mockInterventionAllowDownscaleTrue(); mockModifyGameModeGranted(); - checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, 0.5f); } /** @@ -1404,4 +1404,102 @@ public class GameManagerServiceTests { assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); } + + @Test + public void testUpdateResolutionScalingFactor() { + mockModifyGameModeGranted(); + mockDeviceConfigBattery(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + float scalingFactor = 0.123f; + gameManagerService.updateResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, scalingFactor, + USER_ID_1); + assertEquals(scalingFactor, gameManagerService.getResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f); + scalingFactor = 0.321f; + gameManagerService.updateResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, scalingFactor, + USER_ID_1); + assertEquals(scalingFactor, gameManagerService.getResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f); + } + + @Test + public void testUpdateResolutionScalingFactor_noDeviceConfig() { + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + float scalingFactor = 0.123f; + gameManagerService.updateResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, scalingFactor, + USER_ID_1); + assertEquals(scalingFactor, gameManagerService.getResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f); + scalingFactor = 0.321f; + gameManagerService.updateResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, scalingFactor, + USER_ID_1); + assertEquals(scalingFactor, gameManagerService.getResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, + USER_ID_1), 0.001f); + } + + @Test + public void testUpdateResolutionScalingFactor_permissionDenied() { + mockModifyGameModeDenied(); + mockDeviceConfigAll(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + float scalingFactor = 0.123f; + assertThrows(SecurityException.class, () -> { + gameManagerService.updateResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, scalingFactor, + USER_ID_1); + }); + mockModifyGameModeGranted(); + assertEquals(0.7f, gameManagerService.getResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f); + } + + @Test + public void testUpdateResolutionScalingFactor_noUserId() { + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_2); + final float scalingFactor = 0.123f; + assertThrows(IllegalArgumentException.class, () -> { + gameManagerService.updateResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, scalingFactor, + USER_ID_1); + }); + } + + @Test + public void testGetResolutionScalingFactor_permissionDenied() { + mockModifyGameModeDenied(); + mockDeviceConfigAll(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + assertThrows(SecurityException.class, () -> { + gameManagerService.getResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, USER_ID_1); + }); + } + + @Test + public void testGetResolutionScalingFactor_noUserId() { + mockModifyGameModeDenied(); + mockDeviceConfigAll(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_2); + assertEquals(-1f, gameManagerService.getResolutionScalingFactor(mPackageName, + GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f); + } } From c28fc68b676300f3ab63bf47847e5e085e1ab4de Mon Sep 17 00:00:00 2001 From: Xiang Wang Date: Wed, 3 Aug 2022 16:33:13 -0700 Subject: [PATCH 2/2] Support dynamic game resolution scaling factor CompatModePackages will read scaling factor from local game manager service directly and treat any positive value returned as compat scaling override (with highest priority). Bug: b/240335717 Test: atest GameManagerServiceTests WmTests:CompatModePackagesTests Test: adb shell cmd game set --mode 3 --downscale 0.39 $PKG Change-Id: I95e0ac4af6a2540ba53bfb449b61b1648f3790ad --- .../java/android/app/GameManagerInternal.java | 30 +++ .../server/app/GameManagerService.java | 183 ++---------------- .../android/server/wm/CompatModePackages.java | 15 ++ .../server/app/GameManagerServiceTests.java | 1 - .../server/wm/CompatModePackagesTests.java | 95 +++++++++ 5 files changed, 158 insertions(+), 166 deletions(-) create mode 100644 core/java/android/app/GameManagerInternal.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java diff --git a/core/java/android/app/GameManagerInternal.java b/core/java/android/app/GameManagerInternal.java new file mode 100644 index 0000000000000..c8ff2a84a536e --- /dev/null +++ b/core/java/android/app/GameManagerInternal.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 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 android.app; + +/** + * Game manager local system service interface. + * + * @hide Only for use within the system server. + */ + +public abstract class GameManagerInternal { + /** + * Used by the CompatModePackages to read game's compat scaling override. + */ + public abstract float getResolutionScalingFactor(String packageName, int userId); +} diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 60de31cf8cf46..854b818c095f3 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -25,20 +25,6 @@ import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscali import static com.android.internal.R.styleable.GameModeConfig_allowGameFpsOverride; import static com.android.internal.R.styleable.GameModeConfig_supportsBatteryGameMode; import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanceGameMode; -import static com.android.server.wm.CompatModePackages.DOWNSCALED; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_30; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_35; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_40; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_45; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_50; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_55; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_60; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_65; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_70; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_75; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_80; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_85; -import static com.android.server.wm.CompatModePackages.DOWNSCALE_90; import android.Manifest; import android.annotation.NonNull; @@ -48,10 +34,10 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.GameManager; import android.app.GameManager.GameMode; +import android.app.GameManagerInternal; import android.app.GameModeInfo; import android.app.GameState; import android.app.IGameManagerService; -import android.app.compat.PackageOverride; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -75,9 +61,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManagerInternal; import android.os.Process; -import android.os.RemoteException; import android.os.ResultReceiver; -import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserManager; import android.provider.DeviceConfig; @@ -92,8 +76,6 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.compat.CompatibilityOverrideConfig; -import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; @@ -140,10 +122,6 @@ public final class GameManagerService extends IGameManagerService.Stub { static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds static final int LOADING_BOOST_MAX_DURATION = 5 * 1000; // 5 seconds - static final PackageOverride COMPAT_ENABLED = new PackageOverride.Builder().setEnabled(true) - .build(); - static final PackageOverride COMPAT_DISABLED = new PackageOverride.Builder().setEnabled(false) - .build(); private static final String PACKAGE_NAME_MSG_KEY = "packageName"; private static final String USER_ID_MSG_KEY = "userId"; private static final String GAME_MODE_INTERVENTION_LIST_FILE_NAME = @@ -156,7 +134,6 @@ public final class GameManagerService extends IGameManagerService.Stub { private final Handler mHandler; private final PackageManager mPackageManager; private final UserManager mUserManager; - private final IPlatformCompat mPlatformCompat; private final PowerManagerInternal mPowerManagerInternal; private final File mSystemDir; @VisibleForTesting @@ -180,8 +157,6 @@ public final class GameManagerService extends IGameManagerService.Stub { mHandler = new SettingsHandler(looper); mPackageManager = mContext.getPackageManager(); mUserManager = mContext.getSystemService(UserManager.class); - mPlatformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mSystemDir = new File(Environment.getDataDirectory(), "system"); mSystemDir.mkdirs(); @@ -212,8 +187,6 @@ public final class GameManagerService extends IGameManagerService.Stub { mHandler = new SettingsHandler(looper); mPackageManager = mContext.getPackageManager(); mUserManager = mContext.getSystemService(UserManager.class); - mPlatformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mSystemDir = new File(dataDir, "system"); mSystemDir.mkdirs(); @@ -326,10 +299,6 @@ public final class GameManagerService extends IGameManagerService.Stub { break; } case POPULATE_GAME_MODE_SETTINGS: { - // Scan all game packages and re-enforce the configured compat mode overrides - // as the DeviceConfig may have be wiped/since last reboot and we can't risk - // having overrides configured for packages that no longer have any DeviceConfig - // and thus any way to escape compat mode. removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj); final int userId = (int) msg.obj; final String[] packageNames = getInstalledGamePackageNames(userId); @@ -412,39 +381,6 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - // Turn the raw string to the corresponding CompatChange id. - static long getCompatChangeId(String raw) { - switch (raw) { - case "0.3": - return DOWNSCALE_30; - case "0.35": - return DOWNSCALE_35; - case "0.4": - return DOWNSCALE_40; - case "0.45": - return DOWNSCALE_45; - case "0.5": - return DOWNSCALE_50; - case "0.55": - return DOWNSCALE_55; - case "0.6": - return DOWNSCALE_60; - case "0.65": - return DOWNSCALE_65; - case "0.7": - return DOWNSCALE_70; - case "0.75": - return DOWNSCALE_75; - case "0.8": - return DOWNSCALE_80; - case "0.85": - return DOWNSCALE_85; - case "0.9": - return DOWNSCALE_90; - } - return 0; - } - public enum FrameRate { FPS_DEFAULT(0), FPS_30(30), @@ -752,13 +688,6 @@ public final class GameManagerService extends IGameManagerService.Stub { + mUseAngle + ",Fps:" + mFps + ",Loading Boost Duration:" + mLoadingBoostDuration + "]"; } - - /** - * Get the corresponding compat change id for the current scaling string. - */ - public long getCompatChangeId() { - return GameManagerService.getCompatChangeId(String.valueOf(mScaling)); - } } public String getPackageName() { @@ -856,6 +785,14 @@ public final class GameManagerService extends IGameManagerService.Stub { } } + private final class LocalService extends GameManagerInternal { + @Override + public float getResolutionScalingFactor(String packageName, int userId) { + final int gameMode = getGameModeFromSettings(packageName, userId); + return getResolutionScalingFactorInternal(packageName, gameMode, userId); + } + } + /** * SystemService lifecycle for GameService. * @@ -866,13 +803,13 @@ public final class GameManagerService extends IGameManagerService.Stub { public Lifecycle(Context context) { super(context); + mService = new GameManagerService(context); } @Override public void onStart() { - final Context context = getContext(); - mService = new GameManagerService(context); publishBinderService(Context.GAME_SERVICE, mService); + mService.publishLocalService(); mService.registerDeviceConfigListener(); mService.registerPackageReceiver(); } @@ -955,7 +892,7 @@ public final class GameManagerService extends IGameManagerService.Stub { synchronized (mLock) { if (!mSettings.containsKey(userId)) { Slog.d(TAG, "User ID '" + userId + "' does not have a Game Mode" - + " selected for package: '" + packageName + "'"); + + " selected for package: '" + packageName + "'"); return GameManager.GAME_MODE_UNSUPPORTED; } @@ -1185,7 +1122,7 @@ public final class GameManagerService extends IGameManagerService.Stub { * Updates the resolution scaling factor for the package's target game mode and activates it. * * @param scalingFactor enable scaling override over any other compat scaling if positive, - * disable otherwise + * or disable the override otherwise * @throws SecurityException if caller doesn't have * {@link android.Manifest.permission#MANAGE_GAME_MODE} * permission. @@ -1315,28 +1252,6 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - /** - * @hide - */ - @VisibleForTesting - public void disableCompatScale(String packageName) { - final long uid = Binder.clearCallingIdentity(); - try { - Slog.i(TAG, "Disabling downscale for " + packageName); - final ArrayMap overrides = new ArrayMap<>(); - overrides.put(DOWNSCALED, COMPAT_DISABLED); - final CompatibilityOverrideConfig changeConfig = new CompatibilityOverrideConfig( - overrides); - try { - mPlatformCompat.putOverridesOnReleaseBuilds(changeConfig, packageName); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e); - } - } finally { - Binder.restoreCallingIdentity(uid); - } - } - /** * Remove frame rate override due to mode switch */ @@ -1350,61 +1265,6 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - private void enableCompatScale(String packageName, long scaleId) { - final long uid = Binder.clearCallingIdentity(); - try { - Slog.i(TAG, "Enabling downscale: " + scaleId + " for " + packageName); - final ArrayMap overrides = new ArrayMap<>(); - overrides.put(DOWNSCALED, COMPAT_ENABLED); - overrides.put(DOWNSCALE_30, COMPAT_DISABLED); - overrides.put(DOWNSCALE_35, COMPAT_DISABLED); - overrides.put(DOWNSCALE_40, COMPAT_DISABLED); - overrides.put(DOWNSCALE_45, COMPAT_DISABLED); - overrides.put(DOWNSCALE_50, COMPAT_DISABLED); - overrides.put(DOWNSCALE_55, COMPAT_DISABLED); - overrides.put(DOWNSCALE_60, COMPAT_DISABLED); - overrides.put(DOWNSCALE_65, COMPAT_DISABLED); - overrides.put(DOWNSCALE_70, COMPAT_DISABLED); - overrides.put(DOWNSCALE_75, COMPAT_DISABLED); - overrides.put(DOWNSCALE_80, COMPAT_DISABLED); - overrides.put(DOWNSCALE_85, COMPAT_DISABLED); - overrides.put(DOWNSCALE_90, COMPAT_DISABLED); - overrides.put(scaleId, COMPAT_ENABLED); - final CompatibilityOverrideConfig changeConfig = new CompatibilityOverrideConfig( - overrides); - try { - mPlatformCompat.putOverridesOnReleaseBuilds(changeConfig, packageName); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e); - } - } finally { - Binder.restoreCallingIdentity(uid); - } - } - - private void updateCompatModeDownscale(GamePackageConfiguration packageConfig, - String packageName, @GameMode int gameMode) { - - if (DEBUG) { - Slog.v(TAG, dumpDeviceConfigs()); - } - final GamePackageConfiguration.GameModeConfiguration modeConfig = - packageConfig.getGameModeConfiguration(gameMode); - if (modeConfig == null) { - Slog.i(TAG, "Game mode " + gameMode + " not found for " + packageName); - return; - } - long scaleId = modeConfig.getCompatChangeId(); - if (scaleId == 0) { - disableCompatScale(packageName); - Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for " - + packageName); - return; - } - - enableCompatScale(packageName, scaleId); - } - private int modeToBitmask(@GameMode int gameMode) { return (1 << gameMode); } @@ -1442,20 +1302,17 @@ public final class GameManagerService extends IGameManagerService.Stub { @GameMode int gameMode, @UserIdInt int userId) { if (gameMode == GameManager.GAME_MODE_STANDARD || gameMode == GameManager.GAME_MODE_UNSUPPORTED) { - disableCompatScale(packageName); resetFps(packageName, userId); return; } final GamePackageConfiguration packageConfig = getConfig(packageName); if (packageConfig == null) { - disableCompatScale(packageName); Slog.v(TAG, "Package configuration not found for " + packageName); return; } if (packageConfig.willGamePerformOptimizations(gameMode)) { return; } - updateCompatModeDownscale(packageConfig, packageName, gameMode); updateFps(packageConfig, packageName, gameMode, userId); updateUseAngle(packageName, gameMode); } @@ -1670,7 +1527,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } } } catch (Exception e) { - Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e); + Slog.e(TAG, "Failed to update configs for user " + userId + ": " + e); } final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE); @@ -1821,14 +1678,6 @@ public final class GameManagerService extends IGameManagerService.Stub { updateConfigsForUser(userId, true /*checkGamePackage*/, packageName); break; case ACTION_PACKAGE_REMOVED: - disableCompatScale(packageName); - // If EXTRA_REPLACING is true, it means there will be an - // ACTION_PACKAGE_ADDED triggered after this because this - // is an updated package that gets installed. Hence, disable - // resolution downscaling effort but avoid removing the server - // or commandline overriding configurations because those will - // not change but the package game mode configurations may change - // which may opt in and/or opt out some game mode configurations. if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) { synchronized (mOverrideConfigLock) { mOverrideConfigs.remove(packageName); @@ -1860,6 +1709,10 @@ public final class GameManagerService extends IGameManagerService.Stub { mDeviceConfigListener = new DeviceConfigListener(); } + private void publishLocalService() { + LocalServices.addService(GameManagerInternal.class, new LocalService()); + } + private String dumpDeviceConfigs() { StringBuilder out = new StringBuilder(); for (String key : mConfigs.keySet()) { diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java index 2ea043a5c623f..6f1945033af9f 100644 --- a/services/core/java/com/android/server/wm/CompatModePackages.java +++ b/services/core/java/com/android/server/wm/CompatModePackages.java @@ -24,6 +24,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import android.app.ActivityManager; import android.app.AppGlobals; +import android.app.GameManagerInternal; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; @@ -48,6 +49,7 @@ import android.util.TypedXmlSerializer; import android.util.Xml; import com.android.internal.protolog.common.ProtoLog; +import com.android.server.LocalServices; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -64,6 +66,7 @@ public final class CompatModePackages { private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private final ActivityTaskManagerService mService; + private GameManagerInternal mGameManager; private final AtomicFile mFile; // Compatibility state: no longer ask user to select the mode. @@ -375,6 +378,18 @@ public final class CompatModePackages { float getCompatScale(String packageName, int uid) { final UserHandle userHandle = UserHandle.getUserHandleForUid(uid); + if (mGameManager == null) { + mGameManager = LocalServices.getService(GameManagerInternal.class); + } + if (mGameManager != null) { + final int userId = userHandle.getIdentifier(); + final float scalingFactor = mGameManager.getResolutionScalingFactor(packageName, + userId); + if (scalingFactor > 0) { + return 1f / scalingFactor; + } + } + if (CompatChanges.isChangeEnabled(DOWNSCALED, packageName, userHandle)) { if (CompatChanges.isChangeEnabled(DOWNSCALE_90, packageName, userHandle)) { return 1f / 0.9f; diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index b3a7ad0486660..b53a2c69708d9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -201,7 +201,6 @@ public class GameManagerServiceTests { public void tearDown() throws Exception { LocalServices.removeServiceForTest(PowerManagerInternal.class); GameManagerService gameManagerService = new GameManagerService(mMockContext); - gameManagerService.disableCompatScale(mPackageName); if (mMockingSession != null) { mMockingSession.finishMocking(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java new file mode 100644 index 0000000000000..1f7b65e8b701c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 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.wm; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; + +import android.app.GameManagerInternal; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for the {@link CompatModePackages} class. + * + * Build/Install/Run: + * atest WmTests:CompatModePackagesTests + */ +@SmallTest +@Presubmit +public class CompatModePackagesTests extends SystemServiceTestsBase { + ActivityTaskManagerService mAtm; + GameManagerInternal mGm; + static final String TEST_PACKAGE = "compat.mode.packages"; + static final int TEST_USER_ID = 1; + + @Before + public void setUp() { + mAtm = mSystemServicesTestRule.getActivityTaskManagerService(); + mGm = mock(GameManagerInternal.class); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(GameManagerInternal.class); + } + + @Test + public void testGetCompatScale_gameManagerReturnsPositive() { + LocalServices.addService(GameManagerInternal.class, mGm); + float scale = 0.25f; + doReturn(scale).when(mGm).getResolutionScalingFactor(anyString(), anyInt()); + assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1 / scale, + 0.01f); + } + + @Test + public void testGetCompatScale_gameManagerReturnsZero() { + LocalServices.addService(GameManagerInternal.class, mGm); + float scale = 0f; + doReturn(scale).when(mGm).getResolutionScalingFactor(anyString(), anyInt()); + assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1f, + 0.01f); + } + + @Test + public void testGetCompatScale_gameManagerReturnsNegative() { + LocalServices.addService(GameManagerInternal.class, mGm); + float scale = -1f; + doReturn(scale).when(mGm).getResolutionScalingFactor(anyString(), anyInt()); + assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1f, + 0.01f); + } + + @Test + public void testGetCompatScale_noGameManager() { + assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1f, + 0.01f); + } + +}