diff --git a/core/api/current.txt b/core/api/current.txt index 310e95990b85d..6070cc7f6acc6 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b41f9702c7c13..a57d824125377 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -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); diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java index ac1fa1ec68378..47de04078b457 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -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. + *

+ * 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. + *

+ * An application can use android:isGame="true" or + * android:appCategory="game" to indicate that the application is a game. If an + * application is not a game, always return {@link #GAME_MODE_UNSUPPORTED}. + *

+ * 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. + *

+ * 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. + *

+ * 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) { diff --git a/core/java/android/app/IGameManager.aidl b/core/java/android/app/IGameManager.aidl new file mode 100644 index 0000000000000..3730de82f94a8 --- /dev/null +++ b/core/java/android/app/IGameManager.aidl @@ -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(); +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 48754cccfe0d7..30b3d43ab23e2 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5523,8 +5523,6 @@ public abstract class Context { * {@link GameManager}. * * @see #getSystemService(String) - * - * @hide */ public static final String GAME_SERVICE = "game"; diff --git a/core/tests/GameManagerTests/Android.bp b/core/tests/GameManagerTests/Android.bp index 2789e9f316d1d..8c5d6d5a76a1f 100644 --- a/core/tests/GameManagerTests/Android.bp +++ b/core/tests/GameManagerTests/Android.bp @@ -29,6 +29,7 @@ android_test { "androidx.test.rules", "frameworks-base-testutils", "junit", + "platform-test-annotations", "truth-prebuilt", ], libs: ["android.test.runner"], diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java index 8f50051725cd0..0c964114a76ea 100644 --- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java +++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java @@ -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, 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); - } - } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 1393116a814e1..0687191c5461a 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -418,6 +418,9 @@ + + + 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)); } }