diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index b103defbbe215..b7ef10a5d45d2 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -199,13 +199,12 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onPropertiesChanged(Properties properties) { synchronized (mDeviceConfigLock) { - for (String key : properties.getKeyset()) { + for (final String packageName : properties.getKeyset()) { try { // Check if the package is installed before caching it. - final String packageName = keyToPackageName(key); mPackageManager.getPackageInfo(packageName, 0); final GamePackageConfiguration config = - GamePackageConfiguration.fromProperties(key, properties); + GamePackageConfiguration.fromProperties(packageName, properties); if (config.isValid()) { putConfig(config); } else { @@ -290,8 +289,8 @@ public final class GameManagerService extends IGameManagerService.Stub { private final String mPackageName; private final ArrayMap mModeConfigs; - private GamePackageConfiguration(String keyName) { - mPackageName = keyToPackageName(keyName); + private GamePackageConfiguration(String packageName) { + mPackageName = packageName; mModeConfigs = new ArrayMap<>(); } @@ -563,9 +562,9 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - private void loadDeviceConfigLocked() { + void loadDeviceConfigLocked() { final List packages = mPackageManager.getInstalledPackages(0); - final String[] packageNames = packages.stream().map(e -> packageNameToKey(e.packageName)) + final String[] packageNames = packages.stream().map(e -> e.packageName) .toArray(String[]::new); synchronized (mDeviceConfigLock) { final Properties properties = DeviceConfig.getProperties( @@ -680,8 +679,7 @@ public final class GameManagerService extends IGameManagerService.Stub { case ACTION_PACKAGE_CHANGED: synchronized (mDeviceConfigLock) { Properties properties = DeviceConfig.getProperties( - DeviceConfig.NAMESPACE_GAME_OVERLAY, - packageNameToKey(packageName)); + DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName); for (String key : properties.getKeyset()) { GamePackageConfiguration config = GamePackageConfiguration.fromProperties(key, @@ -692,7 +690,9 @@ public final class GameManagerService extends IGameManagerService.Stub { break; case ACTION_PACKAGE_REMOVED: disableCompatScale(packageName); - mConfigs.remove(packageName); + synchronized (mDeviceConfigLock) { + mConfigs.remove(packageName); + } break; default: // do nothing @@ -710,23 +710,6 @@ public final class GameManagerService extends IGameManagerService.Stub { mDeviceConfigListener = new DeviceConfigListener(); } - /** - * Valid package name characters are [a-zA-Z0-9_] with a '.' delimiter. Policy keys can only use - * [a-zA-Z0-9_] so we must handle periods. We do this by appending a '_' to any existing - * sequence of '_', then we replace all '.' chars with '_'; - */ - private static String packageNameToKey(String name) { - return name.replaceAll("(_+)", "_$1").replaceAll("\\.", "_"); - } - - /** - * Replace the last '_' in a sequence with '.' (this can be one or more chars), then replace the - * resulting special case '_.' with just '_' to get the original package name. - */ - private static String keyToPackageName(String key) { - return key.replaceAll("(_)(?!\\1)", ".").replaceAll("_\\.", "_"); - } - private String dumpDeviceConfigs() { StringBuilder out = new StringBuilder(); for (String key : mConfigs.keySet()) { diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index e4b650cad0550..17a5dccb57da9 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -28,6 +28,8 @@ + diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java similarity index 52% rename from services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java rename to services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index edc0d468dbe18..a8d8a90e89358 100644 --- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -16,51 +16,66 @@ package com.android.server.app; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; import android.Manifest; import android.app.GameManager; import android.content.Context; import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; +import android.provider.DeviceConfig; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @SmallTest @Presubmit public class GameManagerServiceTests { - + @Mock MockContext mMockContext; private static final String TAG = "GameServiceTests"; private static final String PACKAGE_NAME_INVALID = "com.android.app"; private static final int USER_ID_1 = 1001; private static final int USER_ID_2 = 1002; + private MockitoSession mMockingSession; + private String mPackageName; + @Mock + private PackageManager mMockPackageManager; + // Stolen from ConnectivityServiceTest.MockContext - static class MockContext extends ContextWrapper { + class MockContext extends ContextWrapper { private static final String TAG = "MockContext"; // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant private final HashMap mMockedPermissions = new HashMap<>(); - @Mock - private final MockPackageManager mMockPackageManager; - MockContext(Context base) { super(base); - mMockPackageManager = new MockPackageManager(); } /** @@ -112,15 +127,31 @@ public class GameManagerServiceTests { } } - @Mock - private MockContext mMockContext; - - private String mPackageName; - @Before public void setUp() throws Exception { + mMockingSession = mockitoSession() + .initMocks(this) + .mockStatic(DeviceConfig.class) + .strictness(Strictness.WARN) + .startMocking(); mMockContext = new MockContext(InstrumentationRegistry.getContext()); mPackageName = mMockContext.getPackageName(); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.category = ApplicationInfo.CATEGORY_GAME; + final PackageInfo pi = new PackageInfo(); + pi.packageName = mPackageName; + final List packages = new ArrayList<>(); + packages.add(pi); + when(mMockPackageManager.getInstalledPackages(anyInt())).thenReturn(packages); + when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); + } + + @After + public void tearDown() throws Exception { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } } private void mockModifyGameModeGranted() { @@ -133,6 +164,60 @@ public class GameManagerServiceTests { PackageManager.PERMISSION_DENIED); } + private void mockDeviceConfigDefault() { + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, "").build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigNone() { + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigPerformance() { + String configString = "mode=2,downscaleFactor=0.5"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigBattery() { + String configString = "mode=3,downscaleFactor=0.7"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigAll() { + String configString = "mode=3,downscaleFactor=0.7:mode=2,downscaleFactor=0.5"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigInvalid() { + String configString = "mode=2,downscaleFactor=0.55"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigMalformed() { + String configString = "adsljckv=nin3rn9hn1231245:8795tq=21ewuydg"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + /** * By default game mode is not supported. */ @@ -190,7 +275,6 @@ public class GameManagerServiceTests { public void testGetGameModeInvalidPackageName() { GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); - try { assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_INVALID, @@ -268,4 +352,137 @@ public class GameManagerServiceTests { assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameManagerService.getGameMode(mPackageName, USER_ID_2)); } + + /** + * Phonesky device config exists, but is only propagating the default value. + */ + @Test + public void testDeviceConfigDefault() { + mockDeviceConfigDefault(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Phonesky device config does not exists. + */ + @Test + public void testDeviceConfigNone() { + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Phonesky device config for performance mode exists and is valid. + */ + @Test + public void testDeviceConfigPerformance() { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + boolean perfModeExists = false; + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_PERFORMANCE) { + perfModeExists = true; + } + } + assertEquals(modes.length, 1); + assertTrue(perfModeExists); + } + + /** + * Phonesky device config for battery mode exists and is valid. + */ + @Test + public void testDeviceConfigBattery() { + mockDeviceConfigBattery(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + boolean batteryModeExists = false; + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_BATTERY) { + batteryModeExists = true; + } + } + assertEquals(modes.length, 1); + assertTrue(batteryModeExists); + } + + /** + * Phonesky device configs for both battery and performance modes exists and are valid. + */ + @Test + public void testDeviceConfigAll() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + boolean batteryModeExists = false; + boolean perfModeExists = false; + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_BATTERY) { + batteryModeExists = true; + } else if (mode == GameManager.GAME_MODE_PERFORMANCE) { + perfModeExists = true; + } + } + assertTrue(batteryModeExists); + assertTrue(perfModeExists); + } + + /** + * Phonesky device config contains values that parse correctly but are not valid in game mode. + */ + @Test + public void testDeviceConfigInvalid() { + mockDeviceConfigInvalid(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Phonesky device config is garbage. + */ + @Test + public void testDeviceConfigMalformed() { + mockDeviceConfigMalformed(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } }