Merge "Add Universal Game Mode Hint mode switching to GameManagerService." into tm-qpr-dev am: 0825f6b4d0
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21125149 Change-Id: Ia21647971ba194d4c556b329c064f7167e968cff Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -51,6 +51,7 @@ import android.app.GameManager.GameMode;
|
|||||||
import android.app.GameModeInfo;
|
import android.app.GameModeInfo;
|
||||||
import android.app.GameState;
|
import android.app.GameState;
|
||||||
import android.app.IGameManagerService;
|
import android.app.IGameManagerService;
|
||||||
|
import android.app.IUidObserver;
|
||||||
import android.app.compat.PackageOverride;
|
import android.app.compat.PackageOverride;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -118,6 +119,7 @@ import java.util.Arrays;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to manage game related features.
|
* Service to manage game related features.
|
||||||
@@ -171,40 +173,19 @@ public final class GameManagerService extends IGameManagerService.Stub {
|
|||||||
private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
|
private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
|
||||||
@Nullable
|
@Nullable
|
||||||
private final GameServiceController mGameServiceController;
|
private final GameServiceController mGameServiceController;
|
||||||
|
private final Object mUidObserverLock = new Object();
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
final UidObserver mUidObserver;
|
||||||
|
@GuardedBy("mUidObserverLock")
|
||||||
|
private final Set<Integer> mForegroundGameUids = new HashSet<>();
|
||||||
|
|
||||||
public GameManagerService(Context context) {
|
public GameManagerService(Context context) {
|
||||||
this(context, createServiceThread().getLooper());
|
this(context, createServiceThread().getLooper());
|
||||||
}
|
}
|
||||||
|
|
||||||
GameManagerService(Context context, Looper looper) {
|
GameManagerService(Context context, Looper looper) {
|
||||||
mContext = context;
|
this(context, looper, Environment.getDataDirectory());
|
||||||
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)
|
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
|
||||||
@@ -237,6 +218,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
|
|||||||
} else {
|
} else {
|
||||||
mGameServiceController = null;
|
mGameServiceController = null;
|
||||||
}
|
}
|
||||||
|
mUidObserver = new UidObserver();
|
||||||
|
try {
|
||||||
|
ActivityManager.getService().registerUidObserver(mUidObserver,
|
||||||
|
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
|
||||||
|
ActivityManager.PROCESS_STATE_UNKNOWN, null);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.w(TAG, "Could not register UidObserver");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1874,4 +1863,66 @@ public final class GameManagerService extends IGameManagerService.Stub {
|
|||||||
* load dynamic library for frame rate overriding JNI calls
|
* load dynamic library for frame rate overriding JNI calls
|
||||||
*/
|
*/
|
||||||
private static native void nativeSetOverrideFrameRate(int uid, float frameRate);
|
private static native void nativeSetOverrideFrameRate(int uid, float frameRate);
|
||||||
|
|
||||||
|
final class UidObserver extends IUidObserver.Stub {
|
||||||
|
@Override
|
||||||
|
public void onUidIdle(int uid, boolean disabled) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUidGone(int uid, boolean disabled) {
|
||||||
|
synchronized (mUidObserverLock) {
|
||||||
|
disableGameMode(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUidActive(int uid) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
|
||||||
|
synchronized (mUidObserverLock) {
|
||||||
|
if (ActivityManager.isProcStateBackground(procState)) {
|
||||||
|
disableGameMode(uid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
|
||||||
|
if (packages == null || packages.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int userId = mContext.getUserId();
|
||||||
|
if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mForegroundGameUids.isEmpty()) {
|
||||||
|
Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
|
||||||
|
mPowerManagerInternal.setPowerMode(Mode.GAME, true);
|
||||||
|
}
|
||||||
|
mForegroundGameUids.add(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableGameMode(int uid) {
|
||||||
|
synchronized (mUidObserverLock) {
|
||||||
|
if (!mForegroundGameUids.contains(uid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mForegroundGameUids.remove(uid);
|
||||||
|
if (!mForegroundGameUids.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Slog.v(TAG,
|
||||||
|
"Game power mode OFF (process remomved or state changed to background)");
|
||||||
|
mPowerManagerInternal.setPowerMode(Mode.GAME, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUidCachedChanged(int uid, boolean cached) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUidProcAdjChanged(int uid) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,9 @@
|
|||||||
<!-- needed by TrustManagerServiceTest to access LockSettings' secure storage -->
|
<!-- needed by TrustManagerServiceTest to access LockSettings' secure storage -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
|
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
|
||||||
|
|
||||||
|
<!-- needed by GameManagerServiceTest because GameManager creates a UidObserver -->
|
||||||
|
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
|
||||||
|
|
||||||
<application android:testOnly="true"
|
<application android:testOnly="true"
|
||||||
android:debuggable="true">
|
android:debuggable="true">
|
||||||
<uses-library android:name="android.test.runner" />
|
<uses-library android:name="android.test.runner" />
|
||||||
|
|||||||
@@ -29,13 +29,16 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
|
|||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.app.ActivityManager;
|
||||||
import android.app.GameManager;
|
import android.app.GameManager;
|
||||||
import android.app.GameModeInfo;
|
import android.app.GameModeInfo;
|
||||||
import android.app.GameState;
|
import android.app.GameState;
|
||||||
@@ -203,6 +206,23 @@ public class GameManagerServiceTests {
|
|||||||
LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
|
LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
|
||||||
|
throws Exception {
|
||||||
|
reset(mMockPackageManager);
|
||||||
|
final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
|
||||||
|
gameApplicationInfo.category = category;
|
||||||
|
gameApplicationInfo.packageName = packageName;
|
||||||
|
final PackageInfo pi = new PackageInfo();
|
||||||
|
pi.packageName = packageName;
|
||||||
|
pi.applicationInfo = gameApplicationInfo;
|
||||||
|
final List<PackageInfo> packages = new ArrayList<>();
|
||||||
|
packages.add(pi);
|
||||||
|
when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
|
||||||
|
.thenReturn(packages);
|
||||||
|
when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
|
||||||
|
.thenReturn(gameApplicationInfo);
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
LocalServices.removeServiceForTest(PowerManagerInternal.class);
|
LocalServices.removeServiceForTest(PowerManagerInternal.class);
|
||||||
@@ -1597,4 +1617,113 @@ public class GameManagerServiceTests {
|
|||||||
ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
|
ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
|
||||||
ArgumentMatchers.eq(0.0f));
|
ArgumentMatchers.eq(0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private GameManagerService createServiceAndStartUser(int userId) {
|
||||||
|
GameManagerService gameManagerService = new GameManagerService(mMockContext,
|
||||||
|
mTestLooper.getLooper());
|
||||||
|
startUser(gameManagerService, userId);
|
||||||
|
return gameManagerService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGamePowerMode_gamePackage() throws Exception {
|
||||||
|
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
|
||||||
|
String[] packages = {mPackageName};
|
||||||
|
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
|
||||||
|
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGamePowerMode_twoGames() throws Exception {
|
||||||
|
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
|
||||||
|
String[] packages1 = {mPackageName};
|
||||||
|
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
|
||||||
|
String someGamePkg = "some.game";
|
||||||
|
String[] packages2 = {someGamePkg};
|
||||||
|
int somePackageId = DEFAULT_PACKAGE_UID + 1;
|
||||||
|
when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
|
||||||
|
HashMap<Integer, Boolean> powerState = new HashMap<>();
|
||||||
|
doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
|
||||||
|
.when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
|
||||||
|
assertTrue(powerState.get(Mode.GAME));
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
|
||||||
|
assertTrue(powerState.get(Mode.GAME));
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
|
||||||
|
assertFalse(powerState.get(Mode.GAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGamePowerMode_twoGamesOverlap() throws Exception {
|
||||||
|
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
|
||||||
|
String[] packages1 = {mPackageName};
|
||||||
|
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
|
||||||
|
String someGamePkg = "some.game";
|
||||||
|
String[] packages2 = {someGamePkg};
|
||||||
|
int somePackageId = DEFAULT_PACKAGE_UID + 1;
|
||||||
|
when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
|
||||||
|
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
|
||||||
|
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGamePowerMode_released() throws Exception {
|
||||||
|
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
|
||||||
|
String[] packages = {mPackageName};
|
||||||
|
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
|
||||||
|
verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGamePowerMode_noPackage() throws Exception {
|
||||||
|
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
|
||||||
|
String[] packages = {};
|
||||||
|
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
|
||||||
|
verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGamePowerMode_notAGamePackage() throws Exception {
|
||||||
|
mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
|
||||||
|
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
|
||||||
|
String[] packages = {"someapp"};
|
||||||
|
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
|
||||||
|
verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGamePowerMode_notAGamePackageNotReleased() throws Exception {
|
||||||
|
mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
|
||||||
|
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
|
||||||
|
String[] packages = {"someapp"};
|
||||||
|
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
|
||||||
|
gameManagerService.mUidObserver.onUidStateChanged(
|
||||||
|
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
|
||||||
|
verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user