Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b565adde authored by Tim Van Patten's avatar Tim Van Patten Committed by Automerger Merge Worker
Browse files

Merge "Add proper permission check and multi-user handling to...

Merge "Add proper permission check and multi-user handling to GameManagerService" into sc-dev am: d8850ff6

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

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I9e30908c1b1b491d4161c85081b117b92a9421ef
parents 7f093e1c d8850ff6
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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());
@@ -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());
+5 −0
Original line number Diff line number Diff line
@@ -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. -->
+48 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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);
@@ -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.
@@ -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;
@@ -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;
+134 −6
Original line number Diff line number Diff line
@@ -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
@@ -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));
    }
@@ -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));
@@ -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);
@@ -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));
    }
}