Merge "Add writing function to intervention list" into tm-qpr-dev

This commit is contained in:
Andy Yu
2022-06-13 21:14:58 +00:00
committed by Android (Google) Code Review
2 changed files with 341 additions and 3 deletions

View File

@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
/**
@@ -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
@@ -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();
@@ -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<>();
@@ -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(),
@@ -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;
}
}
}
}
@@ -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);
}
}
/**
@@ -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);
}
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);
}
}
private String[] getInstalledGamePackageNames(int userId) {
/*
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 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
@@ -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
*/

View File

@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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 {
@@ -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
@@ -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);
@@ -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,
@@ -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");
}
}