Loading services/core/java/com/android/server/app/GameManagerService.java +182 −2 Original line number Diff line number Diff line Loading @@ -60,6 +60,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; Loading @@ -68,6 +69,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; Loading @@ -77,9 +79,12 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.text.TextUtils; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.AttributeSet; import android.util.KeyValueListParser; import android.util.Slog; Loading @@ -90,6 +95,7 @@ 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; import com.android.server.LocalServices; import com.android.server.ServiceThread; Loading @@ -99,9 +105,17 @@ import com.android.server.SystemService.TargetUser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedWriter; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; /** Loading @@ -122,6 +136,7 @@ public final class GameManagerService extends IGameManagerService.Stub { static final int POPULATE_GAME_MODE_SETTINGS = 3; static final int SET_GAME_STATE = 4; static final int CANCEL_GAME_LOADING_MODE = 5; static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6; static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds static final int LOADING_BOOST_MAX_DURATION = 5 * 1000; // 5 seconds Loading @@ -131,6 +146,8 @@ public final class GameManagerService extends IGameManagerService.Stub { .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 = "game_mode_intervention.list"; private final Context mContext; private final Object mLock = new Object(); Loading @@ -138,8 +155,12 @@ public final class GameManagerService extends IGameManagerService.Stub { private final Object mOverrideConfigLock = new Object(); 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 final AtomicFile mGameModeInterventionListFile; private DeviceConfigListener mDeviceConfigListener; @GuardedBy("mLock") private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>(); Loading @@ -158,9 +179,53 @@ public final class GameManagerService extends IGameManagerService.Stub { mContext = context; 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(); FileUtils.setPermissions(mSystemDir.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1); mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir, GAME_MODE_INTERVENTION_LIST_FILE_NAME)); FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(), FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP, -1, -1); if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) { mGameServiceController = new GameServiceController( context, BackgroundThread.getExecutor(), new GameServiceProviderSelectorImpl( context.getResources(), context.getPackageManager()), new GameServiceProviderInstanceFactoryImpl(context)); } else { mGameServiceController = null; } } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) GameManagerService(Context context, Looper looper, File dataDir) { mContext = context; 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(); FileUtils.setPermissions(mSystemDir.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1); mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir, GAME_MODE_INTERVENTION_LIST_FILE_NAME)); FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(), FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP, -1, -1); if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) { mGameServiceController = new GameServiceController( context, BackgroundThread.getExecutor(), Loading Loading @@ -306,6 +371,22 @@ public final class GameManagerService extends IGameManagerService.Stub { mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false); break; } case WRITE_GAME_MODE_INTERVENTION_LIST_FILE: { final int userId = (int) msg.obj; if (userId < 0) { Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId); synchronized (mLock) { removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null); } break; } Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null); writeGameModeInterventionsToFile(userId); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); break; } } } } Loading Loading @@ -955,6 +1036,11 @@ public final class GameManagerService extends IGameManagerService.Stub { } } updateInterventions(packageName, gameMode, userId); final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE); msg.obj = userId; if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) { mHandler.sendMessage(msg); } } /** Loading Loading @@ -1520,15 +1606,98 @@ public final class GameManagerService extends IGameManagerService.Stub { final int newGameMode = getNewGameMode(gameMode, config); if (newGameMode != gameMode) { setGameMode(packageName, newGameMode, userId); } } else { // Make sure we handle the case when the interventions are changed while // the game mode remains the same. We call only updateInterventions() here. updateInterventions(packageName, gameMode, userId); } } } catch (Exception e) { Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e); } final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE); msg.obj = userId; if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) { mHandler.sendMessage(msg); } } /* Write the interventions and mode of each game to file /system/data/game_mode_intervention.list Each line will contain the information of each game, separated by tab. The format of the output is: <package name> <UID> <current mode> <game mode 1> <interventions> <game mode 2> <interventions> For example: com.android.app1 1425 1 2 angle=0,scaling=1.0,fps=60 3 angle=1,scaling=0.5,fps=30 */ private void writeGameModeInterventionsToFile(@UserIdInt int userId) { FileOutputStream fileOutputStream = null; BufferedWriter bufferedWriter; try { fileOutputStream = mGameModeInterventionListFile.startWrite(); bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream, Charset.defaultCharset())); final StringBuilder sb = new StringBuilder(); final List<String> installedGamesList = getInstalledGamePackageNamesByAllUsers(userId); for (final String packageName : installedGamesList) { GamePackageConfiguration packageConfig = getConfig(packageName); if (packageConfig == null) { continue; } sb.append(packageName); sb.append("\t"); sb.append(mPackageManager.getPackageUidAsUser(packageName, userId)); sb.append("\t"); sb.append(getGameMode(packageName, userId)); sb.append("\t"); final int[] modes = packageConfig.getAvailableGameModes(); for (int mode : modes) { final GamePackageConfiguration.GameModeConfiguration gameModeConfiguration = packageConfig.getGameModeConfiguration(mode); if (gameModeConfiguration == null) { continue; } sb.append(mode); sb.append("\t"); final int useAngle = gameModeConfiguration.getUseAngle() ? 1 : 0; sb.append(TextUtils.formatSimple("angle=%d", useAngle)); sb.append(","); final String scaling = gameModeConfiguration.getScaling(); sb.append("scaling="); sb.append(scaling); sb.append(","); final int fps = gameModeConfiguration.getFps(); sb.append(TextUtils.formatSimple("fps=%d", fps)); sb.append("\t"); } sb.append("\n"); } bufferedWriter.append(sb); bufferedWriter.flush(); FileUtils.sync(fileOutputStream); mGameModeInterventionListFile.finishWrite(fileOutputStream); } catch (Exception e) { mGameModeInterventionListFile.failWrite(fileOutputStream); Slog.wtf(TAG, "Failed to write game_mode_intervention.list, exception " + e); } return; } private String[] getInstalledGamePackageNames(int userId) { private int[] getAllUserIds(@UserIdInt int currentUserId) { final List<UserInfo> users = mUserManager.getUsers(); int[] userIds = new int[users.size()]; for (int i = 0; i < userIds.length; ++i) { userIds[i] = users.get(i).id; } if (currentUserId != -1) { userIds = ArrayUtils.appendInt(userIds, currentUserId); } return userIds; } private String[] getInstalledGamePackageNames(@UserIdInt int userId) { final List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(0, userId); return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category Loading @@ -1537,6 +1706,17 @@ public final class GameManagerService extends IGameManagerService.Stub { .toArray(String[]::new); } private List<String> getInstalledGamePackageNamesByAllUsers(@UserIdInt int currentUserId) { HashSet<String> packageSet = new HashSet<>(); final int[] userIds = getAllUserIds(currentUserId); for (int userId : userIds) { packageSet.addAll(Arrays.asList(getInstalledGamePackageNames(userId))); } return new ArrayList<>(packageSet); } /** * @hide */ Loading services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +159 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ 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.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; Loading Loading @@ -49,6 +50,7 @@ import android.content.res.XmlResourceParser; import android.hardware.power.Mode; import android.os.Bundle; import android.os.PowerManagerInternal; import android.os.UserManager; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; Loading @@ -69,6 +71,8 @@ import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import java.io.File; import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; Loading @@ -79,7 +83,7 @@ import java.util.function.Supplier; @Presubmit public class GameManagerServiceTests { @Mock MockContext mMockContext; private static final String TAG = "GameServiceTests"; private static final String TAG = "GameManagerServiceTests"; 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; Loading @@ -91,6 +95,8 @@ public class GameManagerServiceTests { private PackageManager mMockPackageManager; @Mock private PowerManagerInternal mMockPowerManager; @Mock private UserManager mMockUserManager; // Stolen from ConnectivityServiceTest.MockContext class MockContext extends ContextWrapper { Loading Loading @@ -150,6 +156,15 @@ public class GameManagerServiceTests { public PackageManager getPackageManager() { return mMockPackageManager; } @Override public Object getSystemService(String name) { switch (name) { case Context.USER_SERVICE: return mMockUserManager; } throw new UnsupportedOperationException("Couldn't find system service: " + name); } } @Before Loading Loading @@ -198,6 +213,19 @@ public class GameManagerServiceTests { mTestLooper.dispatchAll(); } private void switchUser(GameManagerService gameManagerService, int from, int to) { UserInfo userInfoFrom = new UserInfo(from, "name", 0); UserInfo userInfoTo = new UserInfo(to, "name", 0); gameManagerService.onUserSwitching(/* from */ new SystemService.TargetUser(userInfoFrom), /* to */ new SystemService.TargetUser(userInfoTo)); mTestLooper.dispatchAll(); } private void mockManageUsersGranted() { mMockContext.setPermission(Manifest.permission.MANAGE_USERS, PackageManager.PERMISSION_GRANTED); } private void mockModifyGameModeGranted() { mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE, PackageManager.PERMISSION_GRANTED); Loading Loading @@ -819,6 +847,7 @@ public class GameManagerServiceTests { GameManagerService gameManagerService = new GameManagerService( mMockContext, mTestLooper.getLooper()); startUser(gameManagerService, USER_ID_1); gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, Loading Loading @@ -1246,4 +1275,133 @@ public class GameManagerServiceTests { public void testSetGameStateNotLoading() { setGameState(false); } private List<String> readGameModeInterventionList() throws Exception { final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(), "system/game_mode_intervention.list"); assertNotNull(interventionFile); List<String> output = Files.readAllLines(interventionFile.toPath()); return output; } private void mockInterventionListForMultipleUsers() { final String[] packageNames = new String[] {"com.android.app0", "com.android.app1", "com.android.app2"}; final ApplicationInfo[] applicationInfos = new ApplicationInfo[3]; final PackageInfo[] pis = new PackageInfo[3]; for (int i = 0; i < 3; ++i) { applicationInfos[i] = new ApplicationInfo(); applicationInfos[i].category = ApplicationInfo.CATEGORY_GAME; applicationInfos[i].packageName = packageNames[i]; pis[i] = new PackageInfo(); pis[i].packageName = packageNames[i]; pis[i].applicationInfo = applicationInfos[i]; } final List<PackageInfo> userOnePackages = new ArrayList<>(); final List<PackageInfo> userTwoPackages = new ArrayList<>(); userOnePackages.add(pis[1]); userTwoPackages.add(pis[0]); userTwoPackages.add(pis[2]); final List<UserInfo> userInfos = new ArrayList<>(2); userInfos.add(new UserInfo()); userInfos.add(new UserInfo()); userInfos.get(0).id = USER_ID_1; userInfos.get(1).id = USER_ID_2; when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_1))) .thenReturn(userOnePackages); when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_2))) .thenReturn(userTwoPackages); when(mMockUserManager.getUsers()).thenReturn(userInfos); } @Test public void testVerifyInterventionList() throws Exception { mockDeviceConfigAll(); mockInterventionListForMultipleUsers(); mockManageUsersGranted(); mockModifyGameModeGranted(); final Context context = InstrumentationRegistry.getContext(); GameManagerService gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper(), context.getFilesDir()); startUser(gameManagerService, USER_ID_1); startUser(gameManagerService, USER_ID_2); gameManagerService.setGameModeConfigOverride("com.android.app0", USER_ID_2, GameManager.GAME_MODE_PERFORMANCE, "120", "0.6"); gameManagerService.setGameModeConfigOverride("com.android.app2", USER_ID_2, GameManager.GAME_MODE_BATTERY, "60", "0.5"); mTestLooper.dispatchAll(); /* Expected fileOutput (order may vary) com.android.app2 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.5,fps=60 com.android.app1 <UID> 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30 com.android.app0 <UID> 0 2 angle=0,scaling=0.6,fps=120 3 angle=0,scaling=0.7,fps=30 The current game mode would only be set to non-zero if the current user have that game installed. */ List<String> fileOutput = readGameModeInterventionList(); assertEquals(fileOutput.size(), 3); String[] splitLine = fileOutput.get(0).split("\\s+"); assertEquals(splitLine[0], "com.android.app2"); assertEquals(splitLine[2], "3"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60"); splitLine = fileOutput.get(1).split("\\s+"); assertEquals(splitLine[0], "com.android.app1"); assertEquals(splitLine[2], "0"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); splitLine = fileOutput.get(2).split("\\s+"); assertEquals(splitLine[0], "com.android.app0"); assertEquals(splitLine[2], "2"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); switchUser(gameManagerService, USER_ID_2, USER_ID_1); gameManagerService.setGameMode("com.android.app1", GameManager.GAME_MODE_BATTERY, USER_ID_1); mTestLooper.dispatchAll(); fileOutput = readGameModeInterventionList(); assertEquals(fileOutput.size(), 3); splitLine = fileOutput.get(0).split("\\s+"); assertEquals(splitLine[0], "com.android.app2"); assertEquals(splitLine[2], "0"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60"); splitLine = fileOutput.get(1).split("\\s+"); assertEquals(splitLine[0], "com.android.app1"); assertEquals(splitLine[2], "3"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); splitLine = fileOutput.get(2).split("\\s+"); assertEquals(splitLine[0], "com.android.app0"); assertEquals(splitLine[2], "0"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); } } Loading
services/core/java/com/android/server/app/GameManagerService.java +182 −2 Original line number Diff line number Diff line Loading @@ -60,6 +60,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; Loading @@ -68,6 +69,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; Loading @@ -77,9 +79,12 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.text.TextUtils; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.AttributeSet; import android.util.KeyValueListParser; import android.util.Slog; Loading @@ -90,6 +95,7 @@ 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; import com.android.server.LocalServices; import com.android.server.ServiceThread; Loading @@ -99,9 +105,17 @@ import com.android.server.SystemService.TargetUser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedWriter; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; /** Loading @@ -122,6 +136,7 @@ public final class GameManagerService extends IGameManagerService.Stub { static final int POPULATE_GAME_MODE_SETTINGS = 3; static final int SET_GAME_STATE = 4; static final int CANCEL_GAME_LOADING_MODE = 5; static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6; static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds static final int LOADING_BOOST_MAX_DURATION = 5 * 1000; // 5 seconds Loading @@ -131,6 +146,8 @@ public final class GameManagerService extends IGameManagerService.Stub { .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 = "game_mode_intervention.list"; private final Context mContext; private final Object mLock = new Object(); Loading @@ -138,8 +155,12 @@ public final class GameManagerService extends IGameManagerService.Stub { private final Object mOverrideConfigLock = new Object(); 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 final AtomicFile mGameModeInterventionListFile; private DeviceConfigListener mDeviceConfigListener; @GuardedBy("mLock") private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>(); Loading @@ -158,9 +179,53 @@ public final class GameManagerService extends IGameManagerService.Stub { mContext = context; 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(); FileUtils.setPermissions(mSystemDir.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1); mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir, GAME_MODE_INTERVENTION_LIST_FILE_NAME)); FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(), FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP, -1, -1); if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) { mGameServiceController = new GameServiceController( context, BackgroundThread.getExecutor(), new GameServiceProviderSelectorImpl( context.getResources(), context.getPackageManager()), new GameServiceProviderInstanceFactoryImpl(context)); } else { mGameServiceController = null; } } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) GameManagerService(Context context, Looper looper, File dataDir) { mContext = context; 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(); FileUtils.setPermissions(mSystemDir.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1); mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir, GAME_MODE_INTERVENTION_LIST_FILE_NAME)); FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(), FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP, -1, -1); if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) { mGameServiceController = new GameServiceController( context, BackgroundThread.getExecutor(), Loading Loading @@ -306,6 +371,22 @@ public final class GameManagerService extends IGameManagerService.Stub { mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false); break; } case WRITE_GAME_MODE_INTERVENTION_LIST_FILE: { final int userId = (int) msg.obj; if (userId < 0) { Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId); synchronized (mLock) { removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null); } break; } Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null); writeGameModeInterventionsToFile(userId); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); break; } } } } Loading Loading @@ -955,6 +1036,11 @@ public final class GameManagerService extends IGameManagerService.Stub { } } updateInterventions(packageName, gameMode, userId); final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE); msg.obj = userId; if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) { mHandler.sendMessage(msg); } } /** Loading Loading @@ -1520,15 +1606,98 @@ public final class GameManagerService extends IGameManagerService.Stub { final int newGameMode = getNewGameMode(gameMode, config); if (newGameMode != gameMode) { setGameMode(packageName, newGameMode, userId); } } else { // Make sure we handle the case when the interventions are changed while // the game mode remains the same. We call only updateInterventions() here. updateInterventions(packageName, gameMode, userId); } } } catch (Exception e) { Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e); } final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE); msg.obj = userId; if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) { mHandler.sendMessage(msg); } } /* Write the interventions and mode of each game to file /system/data/game_mode_intervention.list Each line will contain the information of each game, separated by tab. The format of the output is: <package name> <UID> <current mode> <game mode 1> <interventions> <game mode 2> <interventions> For example: com.android.app1 1425 1 2 angle=0,scaling=1.0,fps=60 3 angle=1,scaling=0.5,fps=30 */ private void writeGameModeInterventionsToFile(@UserIdInt int userId) { FileOutputStream fileOutputStream = null; BufferedWriter bufferedWriter; try { fileOutputStream = mGameModeInterventionListFile.startWrite(); bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream, Charset.defaultCharset())); final StringBuilder sb = new StringBuilder(); final List<String> installedGamesList = getInstalledGamePackageNamesByAllUsers(userId); for (final String packageName : installedGamesList) { GamePackageConfiguration packageConfig = getConfig(packageName); if (packageConfig == null) { continue; } sb.append(packageName); sb.append("\t"); sb.append(mPackageManager.getPackageUidAsUser(packageName, userId)); sb.append("\t"); sb.append(getGameMode(packageName, userId)); sb.append("\t"); final int[] modes = packageConfig.getAvailableGameModes(); for (int mode : modes) { final GamePackageConfiguration.GameModeConfiguration gameModeConfiguration = packageConfig.getGameModeConfiguration(mode); if (gameModeConfiguration == null) { continue; } sb.append(mode); sb.append("\t"); final int useAngle = gameModeConfiguration.getUseAngle() ? 1 : 0; sb.append(TextUtils.formatSimple("angle=%d", useAngle)); sb.append(","); final String scaling = gameModeConfiguration.getScaling(); sb.append("scaling="); sb.append(scaling); sb.append(","); final int fps = gameModeConfiguration.getFps(); sb.append(TextUtils.formatSimple("fps=%d", fps)); sb.append("\t"); } sb.append("\n"); } bufferedWriter.append(sb); bufferedWriter.flush(); FileUtils.sync(fileOutputStream); mGameModeInterventionListFile.finishWrite(fileOutputStream); } catch (Exception e) { mGameModeInterventionListFile.failWrite(fileOutputStream); Slog.wtf(TAG, "Failed to write game_mode_intervention.list, exception " + e); } return; } private String[] getInstalledGamePackageNames(int userId) { private int[] getAllUserIds(@UserIdInt int currentUserId) { final List<UserInfo> users = mUserManager.getUsers(); int[] userIds = new int[users.size()]; for (int i = 0; i < userIds.length; ++i) { userIds[i] = users.get(i).id; } if (currentUserId != -1) { userIds = ArrayUtils.appendInt(userIds, currentUserId); } return userIds; } private String[] getInstalledGamePackageNames(@UserIdInt int userId) { final List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(0, userId); return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category Loading @@ -1537,6 +1706,17 @@ public final class GameManagerService extends IGameManagerService.Stub { .toArray(String[]::new); } private List<String> getInstalledGamePackageNamesByAllUsers(@UserIdInt int currentUserId) { HashSet<String> packageSet = new HashSet<>(); final int[] userIds = getAllUserIds(currentUserId); for (int userId : userIds) { packageSet.addAll(Arrays.asList(getInstalledGamePackageNames(userId))); } return new ArrayList<>(packageSet); } /** * @hide */ Loading
services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +159 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ 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.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; Loading Loading @@ -49,6 +50,7 @@ import android.content.res.XmlResourceParser; import android.hardware.power.Mode; import android.os.Bundle; import android.os.PowerManagerInternal; import android.os.UserManager; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; Loading @@ -69,6 +71,8 @@ import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import java.io.File; import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; Loading @@ -79,7 +83,7 @@ import java.util.function.Supplier; @Presubmit public class GameManagerServiceTests { @Mock MockContext mMockContext; private static final String TAG = "GameServiceTests"; private static final String TAG = "GameManagerServiceTests"; 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; Loading @@ -91,6 +95,8 @@ public class GameManagerServiceTests { private PackageManager mMockPackageManager; @Mock private PowerManagerInternal mMockPowerManager; @Mock private UserManager mMockUserManager; // Stolen from ConnectivityServiceTest.MockContext class MockContext extends ContextWrapper { Loading Loading @@ -150,6 +156,15 @@ public class GameManagerServiceTests { public PackageManager getPackageManager() { return mMockPackageManager; } @Override public Object getSystemService(String name) { switch (name) { case Context.USER_SERVICE: return mMockUserManager; } throw new UnsupportedOperationException("Couldn't find system service: " + name); } } @Before Loading Loading @@ -198,6 +213,19 @@ public class GameManagerServiceTests { mTestLooper.dispatchAll(); } private void switchUser(GameManagerService gameManagerService, int from, int to) { UserInfo userInfoFrom = new UserInfo(from, "name", 0); UserInfo userInfoTo = new UserInfo(to, "name", 0); gameManagerService.onUserSwitching(/* from */ new SystemService.TargetUser(userInfoFrom), /* to */ new SystemService.TargetUser(userInfoTo)); mTestLooper.dispatchAll(); } private void mockManageUsersGranted() { mMockContext.setPermission(Manifest.permission.MANAGE_USERS, PackageManager.PERMISSION_GRANTED); } private void mockModifyGameModeGranted() { mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE, PackageManager.PERMISSION_GRANTED); Loading Loading @@ -819,6 +847,7 @@ public class GameManagerServiceTests { GameManagerService gameManagerService = new GameManagerService( mMockContext, mTestLooper.getLooper()); startUser(gameManagerService, USER_ID_1); gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, Loading Loading @@ -1246,4 +1275,133 @@ public class GameManagerServiceTests { public void testSetGameStateNotLoading() { setGameState(false); } private List<String> readGameModeInterventionList() throws Exception { final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(), "system/game_mode_intervention.list"); assertNotNull(interventionFile); List<String> output = Files.readAllLines(interventionFile.toPath()); return output; } private void mockInterventionListForMultipleUsers() { final String[] packageNames = new String[] {"com.android.app0", "com.android.app1", "com.android.app2"}; final ApplicationInfo[] applicationInfos = new ApplicationInfo[3]; final PackageInfo[] pis = new PackageInfo[3]; for (int i = 0; i < 3; ++i) { applicationInfos[i] = new ApplicationInfo(); applicationInfos[i].category = ApplicationInfo.CATEGORY_GAME; applicationInfos[i].packageName = packageNames[i]; pis[i] = new PackageInfo(); pis[i].packageName = packageNames[i]; pis[i].applicationInfo = applicationInfos[i]; } final List<PackageInfo> userOnePackages = new ArrayList<>(); final List<PackageInfo> userTwoPackages = new ArrayList<>(); userOnePackages.add(pis[1]); userTwoPackages.add(pis[0]); userTwoPackages.add(pis[2]); final List<UserInfo> userInfos = new ArrayList<>(2); userInfos.add(new UserInfo()); userInfos.add(new UserInfo()); userInfos.get(0).id = USER_ID_1; userInfos.get(1).id = USER_ID_2; when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_1))) .thenReturn(userOnePackages); when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_2))) .thenReturn(userTwoPackages); when(mMockUserManager.getUsers()).thenReturn(userInfos); } @Test public void testVerifyInterventionList() throws Exception { mockDeviceConfigAll(); mockInterventionListForMultipleUsers(); mockManageUsersGranted(); mockModifyGameModeGranted(); final Context context = InstrumentationRegistry.getContext(); GameManagerService gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper(), context.getFilesDir()); startUser(gameManagerService, USER_ID_1); startUser(gameManagerService, USER_ID_2); gameManagerService.setGameModeConfigOverride("com.android.app0", USER_ID_2, GameManager.GAME_MODE_PERFORMANCE, "120", "0.6"); gameManagerService.setGameModeConfigOverride("com.android.app2", USER_ID_2, GameManager.GAME_MODE_BATTERY, "60", "0.5"); mTestLooper.dispatchAll(); /* Expected fileOutput (order may vary) com.android.app2 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.5,fps=60 com.android.app1 <UID> 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30 com.android.app0 <UID> 0 2 angle=0,scaling=0.6,fps=120 3 angle=0,scaling=0.7,fps=30 The current game mode would only be set to non-zero if the current user have that game installed. */ List<String> fileOutput = readGameModeInterventionList(); assertEquals(fileOutput.size(), 3); String[] splitLine = fileOutput.get(0).split("\\s+"); assertEquals(splitLine[0], "com.android.app2"); assertEquals(splitLine[2], "3"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60"); splitLine = fileOutput.get(1).split("\\s+"); assertEquals(splitLine[0], "com.android.app1"); assertEquals(splitLine[2], "0"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); splitLine = fileOutput.get(2).split("\\s+"); assertEquals(splitLine[0], "com.android.app0"); assertEquals(splitLine[2], "2"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); switchUser(gameManagerService, USER_ID_2, USER_ID_1); gameManagerService.setGameMode("com.android.app1", GameManager.GAME_MODE_BATTERY, USER_ID_1); mTestLooper.dispatchAll(); fileOutput = readGameModeInterventionList(); assertEquals(fileOutput.size(), 3); splitLine = fileOutput.get(0).split("\\s+"); assertEquals(splitLine[0], "com.android.app2"); assertEquals(splitLine[2], "0"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60"); splitLine = fileOutput.get(1).split("\\s+"); assertEquals(splitLine[0], "com.android.app1"); assertEquals(splitLine[2], "3"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); splitLine = fileOutput.get(2).split("\\s+"); assertEquals(splitLine[0], "com.android.app0"); assertEquals(splitLine[2], "0"); assertEquals(splitLine[3], "2"); assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120"); assertEquals(splitLine[5], "3"); assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30"); } }