Merge "Implement getGameModeInfo API."

This commit is contained in:
Peiyong Lin
2022-01-23 05:47:25 +00:00
committed by Android (Google) Code Review
11 changed files with 314 additions and 41 deletions

View File

@@ -162,6 +162,7 @@ package android {
field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION";
field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION";
@@ -758,7 +759,17 @@ package android.app {
}
public final class GameManager {
method @RequiresPermission("android.permission.MANAGE_GAME_MODE") public void setGameMode(@NonNull String, int);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public android.app.GameModeInfo getGameModeInfo(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(@NonNull String, int);
}
public final class GameModeInfo implements android.os.Parcelable {
ctor public GameModeInfo(int, @NonNull int[]);
method public int describeContents();
method public int getActiveGameMode();
method @NonNull public int[] getAvailableGameModes();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeInfo> CREATOR;
}
public abstract class InstantAppResolverService extends android.app.Service {

View File

@@ -118,6 +118,31 @@ public final class GameManager {
}
}
/**
* Returns the {@link GameModeInfo} associated with the game associated with
* the given {@code packageName}. If the given package is not a game, {@code null} is
* always returned.
* <p>
* An application can use <code>android:isGame="true"</code> or
* <code>android:appCategory="game"</code> to indicate that the application is a game.
* If the manifest doesn't define a category, the category can also be
* provided by the installer via
* {@link android.content.pm.PackageManager#setApplicationCategoryHint(String, int)}.
* <p>
*
* @hide
*/
@SystemApi
@UserHandleAware
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public @Nullable GameModeInfo getGameModeInfo(@NonNull String packageName) {
try {
return mService.getGameModeInfo(packageName, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Sets the game mode for the given package.
* <p>

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
/**
* @hide
*/
parcelable GameModeInfo;

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
/**
* GameModeInfo returned from {@link GameManager#getGameModeInfo(String)}.
* @hide
*/
@SystemApi
public final class GameModeInfo implements Parcelable {
public static final @NonNull Creator<GameModeInfo> CREATOR = new Creator<GameModeInfo>() {
@Override
public GameModeInfo createFromParcel(Parcel in) {
return new GameModeInfo(in);
}
@Override
public GameModeInfo[] newArray(int size) {
return new GameModeInfo[size];
}
};
public GameModeInfo(@GameManager.GameMode int activeGameMode,
@NonNull @GameManager.GameMode int[] availableGameModes) {
mActiveGameMode = activeGameMode;
mAvailableGameModes = availableGameModes;
}
GameModeInfo(Parcel in) {
mActiveGameMode = in.readInt();
final int availableGameModesCount = in.readInt();
mAvailableGameModes = new int[availableGameModesCount];
in.readIntArray(mAvailableGameModes);
}
/**
* Returns the {@link GameManager.GameMode} the application is currently using.
* Developers can enable game modes by adding
* <code>
* <meta-data android:name="android.game_mode_intervention"
* android:resource="@xml/GAME_MODE_CONFIG_FILE" />
* </code>
* to the {@link <application> tag}, where the GAME_MODE_CONFIG_FILE is an XML file that
* specifies the game mode enablement and configuration:
* <code>
* <game-mode-config xmlns:android="http://schemas.android.com/apk/res/android"
* android:gameModePerformance="true"
* android:gameModeBattery="false"
* />
* </code>
*/
public @GameManager.GameMode int getActiveGameMode() {
return mActiveGameMode;
}
/**
* The collection of {@link GameManager.GameMode GameModes} that can be applied to the game.
*/
@NonNull
public @GameManager.GameMode int[] getAvailableGameModes() {
return mAvailableGameModes;
}
// Ideally there should be callback that the caller can register to know when the available
// GameMode and/or the active GameMode is changed, however, there's no concrete use case
// at the moment so there's no callback mechanism introduced .
private final @GameManager.GameMode int[] mAvailableGameModes;
private final @GameManager.GameMode int mActiveGameMode;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mActiveGameMode);
dest.writeInt(mAvailableGameModes.length);
dest.writeIntArray(mAvailableGameModes);
}
}

View File

@@ -16,6 +16,7 @@
package android.app;
import android.app.GameModeInfo;
import android.app.GameState;
/**
@@ -27,4 +28,5 @@ interface IGameManagerService {
int[] getAvailableGameModes(String packageName);
boolean getAngleEnabled(String packageName, int userId);
void setGameState(String packageName, in GameState gameState, int userId);
}
GameModeInfo getGameModeInfo(String packageName, int userId);
}

View File

@@ -6043,10 +6043,10 @@
<permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING"
android:protectionLevel="signature" />
<!-- Allows managing the Game Mode
@hide Used internally. -->
<!-- @SystemApi Allows managing the Game Mode
@hide -->
<permission android:name="android.permission.MANAGE_GAME_MODE"
android:protectionLevel="signature" />
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows accessing the frame rate per second of a given application
@hide -->

View File

@@ -30,6 +30,7 @@
<permission name="android.permission.MANAGE_DEBUGGING"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
<permission name="android.permission.MANAGE_FINGERPRINT"/>
<permission name="android.permission.MANAGE_GAME_MODE" />
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />

View File

@@ -30,6 +30,7 @@
<permission name="android.permission.GET_APP_OPS_STATS"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_DEBUGGING"/>
<permission name="android.permission.MANAGE_GAME_MODE" />
<permission name="android.permission.MANAGE_SENSOR_PRIVACY"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MANAGE_USERS"/>

View File

@@ -333,6 +333,7 @@ applications that come with the platform
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_ACCESSIBILITY"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
<permission name="android.permission.MANAGE_GAME_MODE"/>
<permission name="android.permission.MANAGE_ROLLBACKS"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>

View File

@@ -43,6 +43,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.GameManager;
import android.app.GameManager.GameMode;
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameManagerService;
import android.app.compat.PackageOverride;
@@ -694,6 +695,32 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
}
private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
GamePackageConfiguration config = null;
synchronized (mOverrideConfigLock) {
config = mOverrideConfigs.get(packageName);
}
if (config == null) {
synchronized (mDeviceConfigLock) {
config = mConfigs.get(packageName);
}
}
if (config == null) {
return new int[]{};
}
return config.getAvailableGameModes();
}
private boolean isPackageGame(String packageName, @UserIdInt int userId) {
try {
final ApplicationInfo applicationInfo = mPackageManager
.getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
return applicationInfo.category == ApplicationInfo.CATEGORY_GAME;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
/**
* Get an array of game modes available for a given package.
* Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
@@ -702,19 +729,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException {
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
GamePackageConfiguration config = null;
synchronized (mOverrideConfigLock) {
config = mOverrideConfigs.get(packageName);
}
if (config == null) {
synchronized (mDeviceConfigLock) {
config = mConfigs.get(packageName);
}
}
if (config == null) {
return new int[]{GameManager.GAME_MODE_UNSUPPORTED};
}
return config.getAvailableGameModes();
return getAvailableGameModesUnchecked(packageName);
}
private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) {
@@ -735,28 +750,22 @@ public final class GameManagerService extends IGameManagerService.Stub {
* {@link android.Manifest.permission#MANAGE_GAME_MODE}.
*/
@Override
public @GameMode int getGameMode(String packageName, int userId)
public @GameMode int getGameMode(@NonNull String packageName, @UserIdInt int userId)
throws SecurityException {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "getGameMode",
"com.android.server.app.GameManagerService");
// Restrict to games only.
try {
final ApplicationInfo applicationInfo = mPackageManager
.getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
// The game mode for applications that are not identified as game is always
// UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)}
return GameManager.GAME_MODE_UNSUPPORTED;
}
} catch (PackageManager.NameNotFoundException e) {
if (!isPackageGame(packageName, userId)) {
// The game mode for applications that are not identified as game is always
// UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)}
return GameManager.GAME_MODE_UNSUPPORTED;
}
// This function handles two types of queries:
// 1.) A normal, non-privileged app querying its own Game Mode.
// 2.) A privileged system service querying the Game Mode of another package.
// 1) A normal, non-privileged app querying its own Game Mode.
// 2) A privileged system service querying the Game Mode of another package.
// The least privileged case is a normal app performing a query, so check that first and
// return a value if the package name is valid. Next, check if the caller has the necessary
// permission and return a value. Do this check last, since it can throw an exception.
@@ -769,14 +778,32 @@ public final class GameManagerService extends IGameManagerService.Stub {
return getGameModeFromSettings(packageName, userId);
}
private boolean isPackageGame(String packageName, int userId) {
try {
final ApplicationInfo applicationInfo = mPackageManager
.getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
return applicationInfo.category == ApplicationInfo.CATEGORY_GAME;
} catch (PackageManager.NameNotFoundException e) {
return false;
/**
* Get the GameModeInfo for the package name.
* Verifies that the calling process is for the matching package UID or has
* {@link android.Manifest.permission#MANAGE_GAME_MODE}. If the package is not a game,
* null is always returned.
*/
@Override
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
@Nullable
public GameModeInfo getGameModeInfo(@NonNull String packageName, @UserIdInt int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "getGameModeInfo",
"com.android.server.app.GameManagerService");
// Check the caller has the necessary permission.
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
// Restrict to games only.
if (!isPackageGame(packageName, userId)) {
return null;
}
final @GameMode int activeGameMode = getGameModeFromSettings(packageName, userId);
final @GameMode int[] availableGameModes = getAvailableGameModesUnchecked(packageName);
return new GameModeInfo(activeGameMode, availableGameModes);
}
/**

View File

@@ -20,6 +20,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -28,6 +29,7 @@ import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.GameManager;
import android.app.GameModeInfo;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
@@ -515,7 +517,7 @@ public class GameManagerServiceTests {
public void testDeviceConfigDefault() {
mockDeviceConfigDefault();
mockModifyGameModeGranted();
checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED);
checkReportedModes(null);
}
/**
@@ -525,7 +527,7 @@ public class GameManagerServiceTests {
public void testDeviceConfigNone() {
mockDeviceConfigNone();
mockModifyGameModeGranted();
checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED);
checkReportedModes(null);
}
/**
@@ -566,7 +568,7 @@ public class GameManagerServiceTests {
public void testDeviceConfigInvalid() {
mockDeviceConfigInvalid();
mockModifyGameModeGranted();
checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED);
checkReportedModes(null);
}
/**
@@ -576,7 +578,7 @@ public class GameManagerServiceTests {
public void testDeviceConfigMalformed() {
mockDeviceConfigMalformed();
mockModifyGameModeGranted();
checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED);
checkReportedModes(null);
}
/**
@@ -966,4 +968,84 @@ public class GameManagerServiceTests {
static {
System.loadLibrary("mockingservicestestjni");
}
@Test
public void testGetGameModeInfoPermissionDenied() {
mockDeviceConfigAll();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
// Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
mockModifyGameModeDenied();
assertThrows(SecurityException.class,
() -> gameManagerService.getGameModeInfo(mPackageName, USER_ID_1));
}
@Test
public void testGetGameModeInfoWithAllGameModesDefault() {
mockDeviceConfigAll();
mockModifyGameModeGranted();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
assertEquals(3, gameModeInfo.getAvailableGameModes().length);
}
@Test
public void testGetGameModeInfoWithAllGameModes() {
mockDeviceConfigAll();
mockModifyGameModeGranted();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode());
assertEquals(3, gameModeInfo.getAvailableGameModes().length);
}
@Test
public void testGetGameModeInfoWithBatteryMode() {
mockDeviceConfigBattery();
mockModifyGameModeGranted();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertEquals(GameManager.GAME_MODE_BATTERY, gameModeInfo.getActiveGameMode());
assertEquals(2, gameModeInfo.getAvailableGameModes().length);
}
@Test
public void testGetGameModeInfoWithPerformanceMode() {
mockDeviceConfigPerformance();
mockModifyGameModeGranted();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode());
assertEquals(2, gameModeInfo.getAvailableGameModes().length);
}
@Test
public void testGetGameModeInfoWithUnsupportedGameMode() {
mockDeviceConfigNone();
mockModifyGameModeGranted();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode());
assertEquals(0, gameModeInfo.getAvailableGameModes().length);
}
}