Loading core/java/android/app/GameManager.java +4 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,9 @@ package android.app; import android.Manifest; import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; import android.content.Context; Loading Loading @@ -73,8 +75,8 @@ public final class GameManager { /** * Returns the game mode for the given package. */ // TODO(b/178111358): Add @RequiresPermission. @UserHandleAware @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @GameMode int getGameMode(String packageName) { try { return mService.getGameMode(packageName, mContext.getUserId()); Loading @@ -86,8 +88,8 @@ public final class GameManager { /** * Sets the game mode for the given package. */ // TODO(b/178111358): Add @RequiresPermission. @UserHandleAware @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(String packageName, @GameMode int gameMode) { try { mService.setGameMode(packageName, gameMode, mContext.getUserId()); Loading core/res/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -5447,6 +5447,11 @@ <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" android:protectionLevel="signature" /> <!-- Allows managing the Game Mode @hide Used internally. --> <permission android:name="android.permission.MANAGE_GAME_MODE" android:protectionLevel="signature" /> <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> Loading services/core/java/com/android/server/app/GameManagerService.java +48 −2 Original line number Diff line number Diff line Loading @@ -16,17 +16,23 @@ package com.android.server.app; import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.GameManager; import android.app.GameManager.GameMode; import android.app.IGameManagerService; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -81,6 +87,14 @@ public final class GameManagerService extends IGameManagerService.Stub { switch (msg.what) { case WRITE_SETTINGS: { final int userId = (int) msg.obj; if (userId < 0) { Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId); synchronized (mLock) { removeMessages(WRITE_SETTINGS, msg.obj); } break; } Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mLock) { removeMessages(WRITE_SETTINGS, msg.obj); Loading @@ -94,6 +108,15 @@ public final class GameManagerService extends IGameManagerService.Stub { } case REMOVE_SETTINGS: { final int userId = (int) msg.obj; if (userId < 0) { Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId); synchronized (mLock) { removeMessages(WRITE_SETTINGS, msg.obj); removeMessages(REMOVE_SETTINGS, msg.obj); } break; } synchronized (mLock) { // Since the user was removed, ignore previous write message // and do write here. Loading Loading @@ -146,9 +169,23 @@ public final class GameManagerService extends IGameManagerService.Stub { } } //TODO(b/178111358) Add proper permission check and multi-user handling private boolean hasPermission(String permission) { return mContext.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; } @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; } 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; Loading @@ -158,9 +195,18 @@ public final class GameManagerService extends IGameManagerService.Stub { } } //TODO(b/178111358) Add proper permission check and multi-user handling @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; } userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "setGameMode", "com.android.server.app.GameManagerService"); synchronized (mLock) { if (!mSettings.containsKey(userId)) { return; Loading services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java +134 −6 Original line number Diff line number Diff line Loading @@ -18,14 +18,23 @@ package com.android.server.app; import static org.junit.Assert.assertEquals; import android.Manifest; import android.app.GameManager; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import java.util.HashMap; import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @SmallTest Loading @@ -37,15 +46,88 @@ public class GameManagerServiceTests { private static final int USER_ID_1 = 1001; private static final int USER_ID_2 = 1002; // Stolen from ConnectivityServiceTest.MockContext class MockContext extends ContextWrapper { private static final String TAG = "MockContext"; // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); MockContext(Context base) { super(base); } /** * Mock checks for the specified permission, and have them behave as per {@code granted}. * * <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}. */ public void setPermission(String permission, Integer granted) { mMockedPermissions.put(permission, granted); } private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) { final Integer granted = mMockedPermissions.get(permission); return granted != null ? granted : ifAbsent.get(); } @Override public int checkPermission(String permission, int pid, int uid) { return checkMockedPermission( permission, () -> super.checkPermission(permission, pid, uid)); } @Override public int checkCallingOrSelfPermission(String permission) { return checkMockedPermission( permission, () -> super.checkCallingOrSelfPermission(permission)); } @Override public void enforceCallingOrSelfPermission(String permission, String message) { final Integer granted = mMockedPermissions.get(permission); if (granted == null) { super.enforceCallingOrSelfPermission(permission, message); return; } if (!granted.equals(PackageManager.PERMISSION_GRANTED)) { throw new SecurityException("[Test] permission denied: " + permission); } } } @Mock private MockContext mMockContext; @Before public void setUp() throws Exception { mMockContext = new MockContext(InstrumentationRegistry.getContext()); } private void mockModifyGameModeGranted() { mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE, PackageManager.PERMISSION_GRANTED); } private void mockModifyGameModeDenied() { mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE, PackageManager.PERMISSION_DENIED); } /** * By default game mode is not supported. */ @Test public void testGameModeDefaultValue() { GameManagerService gameManagerService = new GameManagerService(InstrumentationRegistry.getContext()); GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); mockModifyGameModeGranted(); assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_0, USER_ID_1)); } Loading @@ -55,10 +137,11 @@ public class GameManagerServiceTests { */ @Test public void testDefaultValueForNonexistentUser() { GameManagerService gameManagerService = new GameManagerService(InstrumentationRegistry.getContext()); GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); mockModifyGameModeGranted(); gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_2); assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_2)); Loading @@ -69,10 +152,11 @@ public class GameManagerServiceTests { */ @Test public void testGameMode() { GameManagerService gameManagerService = new GameManagerService(InstrumentationRegistry.getContext()); 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); Loading @@ -83,4 +167,48 @@ public class GameManagerServiceTests { assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); } /** * Test permission.MANAGE_GAME_MODE is checked */ @Test public void testGetGameModePermissionDenied() { 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)); // 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)); } /** * Test permission.MANAGE_GAME_MODE is checked */ @Test public void testSetGameModePermissionDenied() { 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)); // 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); mockModifyGameModeGranted(); assertEquals(GameManager.GAME_MODE_STANDARD, gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); } } Loading
core/java/android/app/GameManager.java +4 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,9 @@ package android.app; import android.Manifest; import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; import android.content.Context; Loading Loading @@ -73,8 +75,8 @@ public final class GameManager { /** * Returns the game mode for the given package. */ // TODO(b/178111358): Add @RequiresPermission. @UserHandleAware @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @GameMode int getGameMode(String packageName) { try { return mService.getGameMode(packageName, mContext.getUserId()); Loading @@ -86,8 +88,8 @@ public final class GameManager { /** * Sets the game mode for the given package. */ // TODO(b/178111358): Add @RequiresPermission. @UserHandleAware @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(String packageName, @GameMode int gameMode) { try { mService.setGameMode(packageName, gameMode, mContext.getUserId()); Loading
core/res/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -5447,6 +5447,11 @@ <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" android:protectionLevel="signature" /> <!-- Allows managing the Game Mode @hide Used internally. --> <permission android:name="android.permission.MANAGE_GAME_MODE" android:protectionLevel="signature" /> <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> Loading
services/core/java/com/android/server/app/GameManagerService.java +48 −2 Original line number Diff line number Diff line Loading @@ -16,17 +16,23 @@ package com.android.server.app; import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.GameManager; import android.app.GameManager.GameMode; import android.app.IGameManagerService; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -81,6 +87,14 @@ public final class GameManagerService extends IGameManagerService.Stub { switch (msg.what) { case WRITE_SETTINGS: { final int userId = (int) msg.obj; if (userId < 0) { Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId); synchronized (mLock) { removeMessages(WRITE_SETTINGS, msg.obj); } break; } Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mLock) { removeMessages(WRITE_SETTINGS, msg.obj); Loading @@ -94,6 +108,15 @@ public final class GameManagerService extends IGameManagerService.Stub { } case REMOVE_SETTINGS: { final int userId = (int) msg.obj; if (userId < 0) { Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId); synchronized (mLock) { removeMessages(WRITE_SETTINGS, msg.obj); removeMessages(REMOVE_SETTINGS, msg.obj); } break; } synchronized (mLock) { // Since the user was removed, ignore previous write message // and do write here. Loading Loading @@ -146,9 +169,23 @@ public final class GameManagerService extends IGameManagerService.Stub { } } //TODO(b/178111358) Add proper permission check and multi-user handling private boolean hasPermission(String permission) { return mContext.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; } @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; } 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; Loading @@ -158,9 +195,18 @@ public final class GameManagerService extends IGameManagerService.Stub { } } //TODO(b/178111358) Add proper permission check and multi-user handling @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; } userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "setGameMode", "com.android.server.app.GameManagerService"); synchronized (mLock) { if (!mSettings.containsKey(userId)) { return; Loading
services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java +134 −6 Original line number Diff line number Diff line Loading @@ -18,14 +18,23 @@ package com.android.server.app; import static org.junit.Assert.assertEquals; import android.Manifest; import android.app.GameManager; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import java.util.HashMap; import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @SmallTest Loading @@ -37,15 +46,88 @@ public class GameManagerServiceTests { private static final int USER_ID_1 = 1001; private static final int USER_ID_2 = 1002; // Stolen from ConnectivityServiceTest.MockContext class MockContext extends ContextWrapper { private static final String TAG = "MockContext"; // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); MockContext(Context base) { super(base); } /** * Mock checks for the specified permission, and have them behave as per {@code granted}. * * <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}. */ public void setPermission(String permission, Integer granted) { mMockedPermissions.put(permission, granted); } private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) { final Integer granted = mMockedPermissions.get(permission); return granted != null ? granted : ifAbsent.get(); } @Override public int checkPermission(String permission, int pid, int uid) { return checkMockedPermission( permission, () -> super.checkPermission(permission, pid, uid)); } @Override public int checkCallingOrSelfPermission(String permission) { return checkMockedPermission( permission, () -> super.checkCallingOrSelfPermission(permission)); } @Override public void enforceCallingOrSelfPermission(String permission, String message) { final Integer granted = mMockedPermissions.get(permission); if (granted == null) { super.enforceCallingOrSelfPermission(permission, message); return; } if (!granted.equals(PackageManager.PERMISSION_GRANTED)) { throw new SecurityException("[Test] permission denied: " + permission); } } } @Mock private MockContext mMockContext; @Before public void setUp() throws Exception { mMockContext = new MockContext(InstrumentationRegistry.getContext()); } private void mockModifyGameModeGranted() { mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE, PackageManager.PERMISSION_GRANTED); } private void mockModifyGameModeDenied() { mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE, PackageManager.PERMISSION_DENIED); } /** * By default game mode is not supported. */ @Test public void testGameModeDefaultValue() { GameManagerService gameManagerService = new GameManagerService(InstrumentationRegistry.getContext()); GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); mockModifyGameModeGranted(); assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_0, USER_ID_1)); } Loading @@ -55,10 +137,11 @@ public class GameManagerServiceTests { */ @Test public void testDefaultValueForNonexistentUser() { GameManagerService gameManagerService = new GameManagerService(InstrumentationRegistry.getContext()); GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); mockModifyGameModeGranted(); gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_2); assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_2)); Loading @@ -69,10 +152,11 @@ public class GameManagerServiceTests { */ @Test public void testGameMode() { GameManagerService gameManagerService = new GameManagerService(InstrumentationRegistry.getContext()); 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); Loading @@ -83,4 +167,48 @@ public class GameManagerServiceTests { assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); } /** * Test permission.MANAGE_GAME_MODE is checked */ @Test public void testGetGameModePermissionDenied() { 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)); // 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)); } /** * Test permission.MANAGE_GAME_MODE is checked */ @Test public void testSetGameModePermissionDenied() { 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)); // 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); mockModifyGameModeGranted(); assertEquals(GameManager.GAME_MODE_STANDARD, gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); } }