Merge "GameManager: Add Public API" into sc-dev am: 3c4b8d977b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13559229

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ib82e54bff7654088cdc5f5b6207a7bf5884dfd6a
This commit is contained in:
Tim Van Patten
2021-03-07 04:19:53 +00:00
committed by Automerger Merge Worker
11 changed files with 225 additions and 93 deletions

View File

@@ -5312,6 +5312,14 @@ package android.app {
field @Deprecated public static final int TRANSIT_UNSET = -1; // 0xffffffff
}
public final class GameManager {
method public int getGameMode();
field public static final int GAME_MODE_BATTERY = 3; // 0x3
field public static final int GAME_MODE_PERFORMANCE = 2; // 0x2
field public static final int GAME_MODE_STANDARD = 1; // 0x1
field public static final int GAME_MODE_UNSUPPORTED = 0; // 0x0
}
public class Instrumentation {
ctor public Instrumentation();
method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
@@ -10487,6 +10495,7 @@ package android.content {
field public static final String EUICC_SERVICE = "euicc";
field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
field public static final String FINGERPRINT_SERVICE = "fingerprint";
field public static final String GAME_SERVICE = "game";
field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final String INPUT_METHOD_SERVICE = "input_method";
field public static final String INPUT_SERVICE = "input";

View File

@@ -259,6 +259,10 @@ package android.app {
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream();
}
public final class GameManager {
method @RequiresPermission("android.permission.MANAGE_GAME_MODE") public void setGameMode(@NonNull String, int);
}
public abstract class HomeVisibilityListener {
ctor public HomeVisibilityListener();
method public abstract void onHomeVisibilityChanged(boolean);

View File

@@ -18,8 +18,11 @@ package android.app;
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UserHandleAware;
import android.content.Context;
import android.os.Handler;
@@ -27,57 +30,87 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* The GameManager allows system apps to modify and query the game mode of apps.
*
* @hide
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@SystemService(Context.GAME_SERVICE)
public final class GameManager {
private static final String TAG = "GameManager";
private final Context mContext;
private final @Nullable Context mContext;
private final IGameManagerService mService;
@IntDef(flag = false, prefix = { "GAME_MODE_" }, value = {
/** @hide */
@IntDef(flag = false, prefix = {"GAME_MODE_"}, value = {
GAME_MODE_UNSUPPORTED, // 0
GAME_MODE_STANDARD, // 1
GAME_MODE_PERFORMANCE, // 2
GAME_MODE_BATTERY, // 3
})
@Retention(RetentionPolicy.SOURCE)
public @interface GameMode {}
public @interface GameMode {
}
/**
* Game mode is not supported for this application.
*/
public static final int GAME_MODE_UNSUPPORTED = 0;
/**
* Standard game mode means the platform will use the game's default
* performance characteristics.
*/
public static final int GAME_MODE_STANDARD = 1;
/**
* Performance game mode maximizes the game's performance.
* <p>
* This game mode is highly likely to increase battery consumption.
*/
public static final int GAME_MODE_PERFORMANCE = 2;
/**
* Battery game mode will save battery and give longer game play time.
*/
public static final int GAME_MODE_BATTERY = 3;
public GameManager(Context context, Handler handler) throws ServiceNotFoundException {
GameManager(Context context, Handler handler) throws ServiceNotFoundException {
mContext = context;
mService = IGameManagerService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.GAME_SERVICE));
}
@VisibleForTesting
public GameManager(Context context, IGameManagerService gameManagerService) {
mContext = context;
mService = gameManagerService;
/**
* Return the user selected game mode for this application.
* <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 an
* application is not a game, always return {@link #GAME_MODE_UNSUPPORTED}.
* <p>
* Developers should call this API every time the application is resumed.
*/
public @GameMode int getGameMode() {
try {
return mService.getGameMode(mContext.getPackageName(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns the game mode for the given package.
* Gets the game mode for the given package.
* <p>
* The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
*
* @hide
*/
@UserHandleAware
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public @GameMode int getGameMode(String packageName) {
public @GameMode int getGameMode(@NonNull String packageName) {
try {
return mService.getGameMode(packageName, mContext.getUserId());
} catch (RemoteException e) {
@@ -87,10 +120,15 @@ public final class GameManager {
/**
* Sets the game mode for the given package.
* <p>
* The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
*
* @hide
*/
@TestApi
@UserHandleAware
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public void setGameMode(String packageName, @GameMode int gameMode) {
public void setGameMode(@NonNull String packageName, @GameMode int gameMode) {
try {
mService.setGameMode(packageName, gameMode, mContext.getUserId());
} catch (RemoteException e) {

View File

@@ -0,0 +1,28 @@
/*
* 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;
/**
* Interface used to control Game Manager modes.
* @hide
*/
interface IGameManager {
/**
* Return the current Game Mode for the calling package.
*/
int getGameMode();
}

View File

@@ -5523,8 +5523,6 @@ public abstract class Context {
* {@link GameManager}.
*
* @see #getSystemService(String)
*
* @hide
*/
public static final String GAME_SERVICE = "game";

View File

@@ -29,6 +29,7 @@ android_test {
"androidx.test.rules",
"frameworks-base-testutils",
"junit",
"platform-test-annotations",
"truth-prebuilt",
],
libs: ["android.test.runner"],

View File

@@ -16,13 +16,13 @@
package android.app;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static junit.framework.Assert.assertEquals;
import android.app.GameManager.GameMode;
import android.util.ArrayMap;
import android.util.Pair;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -35,22 +35,43 @@ import org.junit.runner.RunWith;
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public final class GameManagerTests {
private static final String PACKAGE_NAME_0 = "com.android.app0";
private static final String PACKAGE_NAME_1 = "com.android.app1";
private TestGameManagerService mService;
protected Context mContext;
private GameManager mGameManager;
private String mPackageName;
@Before
public void setUp() {
mService = new TestGameManagerService();
mGameManager = new GameManager(
InstrumentationRegistry.getContext(), mService);
mContext = getInstrumentation().getContext();
mGameManager = mContext.getSystemService(GameManager.class);
mPackageName = mContext.getPackageName();
// Reset the Game Mode for the test app, since it persists across invocations.
mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_UNSUPPORTED);
mGameManager.setGameMode(PACKAGE_NAME_0, GameManager.GAME_MODE_UNSUPPORTED);
mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_UNSUPPORTED);
}
@Test
public void testGameModeGetterSetter() {
public void testPublicApiGameModeGetterSetter() {
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
mGameManager.getGameMode());
mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
assertEquals(GameManager.GAME_MODE_STANDARD,
mGameManager.getGameMode());
mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE);
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
mGameManager.getGameMode());
}
@Test
public void testPrivilegedGameModeGetterSetter() {
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
mGameManager.getGameMode(PACKAGE_NAME_0));
@@ -62,22 +83,4 @@ public final class GameManagerTests {
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
mGameManager.getGameMode(PACKAGE_NAME_1));
}
private final class TestGameManagerService extends IGameManagerService.Stub {
private final ArrayMap<Pair<String, Integer>, Integer> mGameModes = new ArrayMap<>();
@Override
public @GameMode int getGameMode(String packageName, int userId) {
final Pair key = Pair.create(packageName, userId);
if (mGameModes.containsKey(key)) {
return mGameModes.get(key);
}
return GameManager.GAME_MODE_UNSUPPORTED;
}
@Override
public void setGameMode(String packageName, @GameMode int gameMode, int userId) {
mGameModes.put(Pair.create(packageName, userId), gameMode);
}
}
}

View File

@@ -418,6 +418,9 @@
<!-- Permission required for CTS test - PeopleManagerTest -->
<uses-permission android:name="android.permission.READ_PEOPLE_DATA" />
<!-- Permission required for CTS test - CtsGameManagerTestCases -->
<uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"

View File

@@ -136,6 +136,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
/**
* SystemService lifecycle for GameService.
*
* @hide
*/
public static class Lifecycle extends SystemService {
@@ -169,39 +170,69 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
}
private boolean hasPermission(String permission) {
return mContext.checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
private boolean isValidPackageName(String packageName) {
final PackageManager pm = mContext.getPackageManager();
try {
return pm.getPackageUid(packageName, 0) == Binder.getCallingUid();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return false;
}
}
@Override
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public @GameMode int getGameMode(String packageName, int userId) {
if (!hasPermission(Manifest.permission.MANAGE_GAME_MODE)) {
Log.w(TAG, String.format("Caller or self does not have permission.MANAGE_GAME_MODE"));
return GameManager.GAME_MODE_UNSUPPORTED;
private void checkPermission(String permission) throws SecurityException {
if (mContext.checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
+ ", must have permission " + permission);
}
}
private @GameMode int getGameModeFromSettings(String packageName, int userId) {
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
Log.w(TAG, "User ID '" + userId + "' does not have a Game Mode"
+ " selected for package: '" + packageName + "'");
return GameManager.GAME_MODE_UNSUPPORTED;
}
return mSettings.get(userId).getGameModeLocked(packageName);
}
}
/**
* Get the Game Mode for the package name.
* Verifies that the calling process is for the matching package UID or has
* {@link android.Manifest.permission#MANAGE_GAME_MODE}.
*/
@Override
public @GameMode int getGameMode(String packageName, int userId)
throws SecurityException {
// TODO(b/178860939): Restrict to games only.
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "getGameMode",
"com.android.server.app.GameManagerService");
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
return GameManager.GAME_MODE_UNSUPPORTED;
}
GameManagerSettings userSettings = mSettings.get(userId);
return userSettings.getGameModeLocked(packageName);
if (isValidPackageName(packageName)) {
return getGameModeFromSettings(packageName, userId);
}
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
return getGameModeFromSettings(packageName, userId);
}
/**
* Sets the Game Mode for the package name.
* Verifies that the calling process has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
*/
@Override
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public void setGameMode(String packageName, @GameMode int gameMode, int userId) {
if (!hasPermission(Manifest.permission.MANAGE_GAME_MODE)) {
Log.w(TAG, String.format("Caller or self does not have permission.MANAGE_GAME_MODE"));
return;
}
public void setGameMode(String packageName, @GameMode int gameMode, int userId)
throws SecurityException {
// TODO(b/178860939): Restrict to games only.
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "setGameMode",

View File

@@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
import android.util.AtomicFile;
import android.util.Log;
@@ -37,6 +38,7 @@ import java.io.IOException;
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class GameManagerServiceSettingsTests {
private static final String TAG = "GameServiceSettingsTests";

View File

@@ -17,12 +17,14 @@
package com.android.server.app;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.Manifest;
import android.app.GameManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -38,11 +40,11 @@ import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class GameManagerServiceTests {
private static final String TAG = "GameServiceTests";
private static final String PACKAGE_NAME_0 = "com.android.app0";
private static final String PACKAGE_NAME_1 = "com.android.app1";
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;
@@ -62,6 +64,7 @@ public class GameManagerServiceTests {
*
* <p>Passing null reverts to default behavior, which does a real permission check on the
* test package.
*
* @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
* {@link PackageManager#PERMISSION_DENIED}.
*/
@@ -103,9 +106,12 @@ public class GameManagerServiceTests {
@Mock
private MockContext mMockContext;
private String mPackageName;
@Before
public void setUp() throws Exception {
mMockContext = new MockContext(InstrumentationRegistry.getContext());
mPackageName = mMockContext.getPackageName();
}
private void mockModifyGameModeGranted() {
@@ -129,7 +135,7 @@ public class GameManagerServiceTests {
mockModifyGameModeGranted();
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(PACKAGE_NAME_0, USER_ID_1));
gameManagerService.getGameMode(mPackageName, USER_ID_1));
}
/**
@@ -142,9 +148,9 @@ public class GameManagerServiceTests {
mockModifyGameModeGranted();
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_2);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_2));
gameManagerService.getGameMode(mPackageName, USER_ID_2));
}
/**
@@ -152,40 +158,41 @@ public class GameManagerServiceTests {
*/
@Test
public void testGameMode() {
GameManagerService gameManagerService = new GameManagerService(mMockContext);
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onUserStarting(USER_ID_1);
mockModifyGameModeGranted();
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
gameManagerService.getGameMode(mPackageName, USER_ID_1));
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
USER_ID_1);
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
gameManagerService.getGameMode(mPackageName, USER_ID_1));
}
/**
* Test permission.MANAGE_GAME_MODE is checked
*/
@Test
public void testGetGameModePermissionDenied() {
GameManagerService gameManagerService = new GameManagerService(mMockContext);
public void testGetGameModeInvalidPackageName() {
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onUserStarting(USER_ID_1);
// Update the game mode so we can read back something valid.
mockModifyGameModeGranted();
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
try {
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
USER_ID_1));
// Deny permission.MANAGE_GAME_MODE and verify we get back GameManager.GAME_MODE_UNSUPPORTED
mockModifyGameModeDenied();
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
fail("GameManagerService failed to generate SecurityException when "
+ "permission.MANAGE_GAME_MODE is not granted.");
} catch (SecurityException ignored) {
}
// The test should throw an exception, so the test is passing if we get here.
}
/**
@@ -193,22 +200,30 @@ public class GameManagerServiceTests {
*/
@Test
public void testSetGameModePermissionDenied() {
GameManagerService gameManagerService = new GameManagerService(mMockContext);
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onUserStarting(USER_ID_1);
// Update the game mode so we can read back something valid.
mockModifyGameModeGranted();
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
gameManagerService.getGameMode(mPackageName, USER_ID_1));
// Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
mockModifyGameModeDenied();
gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE,
USER_ID_1);
try {
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
USER_ID_1);
fail("GameManagerService failed to generate SecurityException when "
+ "permission.MANAGE_GAME_MODE is denied.");
} catch (SecurityException ignored) {
}
// The test should throw an exception, so the test is passing if we get here.
mockModifyGameModeGranted();
// Verify that the Game Mode value wasn't updated.
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
gameManagerService.getGameMode(mPackageName, USER_ID_1));
}
}