Add Universal Game Mode Hint mode switching to GameManagerService.
Bug: 214405108 Test: atest com.android.server.app.GameManagerServiceTests Test: Manual with "adb logcat | grep GameManagerService", and opening/backgrounding a game on the attached device. Change-Id: I0883e6bae316199aeeab9f7dda8934ccd67e7243
This commit is contained in:
@@ -40,6 +40,7 @@ import android.app.GameModeInfo;
|
|||||||
import android.app.GameState;
|
import android.app.GameState;
|
||||||
import android.app.IGameManagerService;
|
import android.app.IGameManagerService;
|
||||||
import android.app.IGameModeListener;
|
import android.app.IGameModeListener;
|
||||||
|
import android.app.IUidObserver;
|
||||||
import android.app.StatsManager;
|
import android.app.StatsManager;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -165,38 +166,19 @@ public final class GameManagerService extends IGameManagerService.Stub {
|
|||||||
private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
|
private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = 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);
|
|
||||||
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)
|
||||||
@@ -227,6 +209,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
|
||||||
@@ -2152,4 +2142,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" />
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ 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.reset;
|
||||||
@@ -45,6 +46,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.app.ActivityManager;
|
||||||
import android.app.GameManager;
|
import android.app.GameManager;
|
||||||
import android.app.GameModeConfiguration;
|
import android.app.GameModeConfiguration;
|
||||||
import android.app.GameModeInfo;
|
import android.app.GameModeInfo;
|
||||||
@@ -2212,4 +2214,106 @@ public class GameManagerServiceTests {
|
|||||||
assertEquals(expectedInterventionListOutput,
|
assertEquals(expectedInterventionListOutput,
|
||||||
gameManagerService.getInterventionList(mPackageName, USER_ID_1));
|
gameManagerService.getInterventionList(mPackageName, USER_ID_1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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