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

Commit f0a11f45 authored by Xiang Wang's avatar Xiang Wang Committed by Android (Google) Code Review
Browse files

Merge changes from topics "game-mode-custom", "no-game-mode-supported"

* changes:
  Add IGameModeListener and methods to add/remove in GameManagerService
  Disallow setting UNSUPPORTED mode to avoid ambiguity
  Always return STANDARD & CUSTOM game modes as available modes
parents 4c47125d 8b9be354
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -187,7 +187,9 @@ public final class GameManager {
     * Sets the game mode for the given package.
     * <p>
     * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
     *
     * <p>
     * Setting the game mode on a non-game application or setting a game to
     * {@link #GAME_MODE_UNSUPPORTED} will have no effect.
     * @hide
     */
    @SystemApi
+14 −0
Original line number Diff line number Diff line
@@ -19,20 +19,34 @@ package android.app;
import android.app.GameModeConfiguration;
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameModeListener;

/**
 * @hide
 */
interface IGameManagerService {
    int getGameMode(String packageName, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void setGameMode(String packageName, int gameMode, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    int[] getAvailableGameModes(String packageName);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    boolean isAngleEnabled(String packageName, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void notifyGraphicsEnvironmentSetup(String packageName, int userId);
    void setGameState(String packageName, in GameState gameState, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    GameModeInfo getGameModeInfo(String packageName, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_GAME_SERVICE)")
    void setGameServiceProvider(String packageName);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void updateResolutionScalingFactor(String packageName, int gameMode, float scalingFactor, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    float getResolutionScalingFactor(String packageName, int gameMode, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void updateCustomGameModeConfiguration(String packageName, in GameModeConfiguration gameModeConfig, int userId);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void addGameModeListener(IGameModeListener gameModeListener);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
    void removeGameModeListener(IGameModeListener gameModeListener);
}
+25 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 */
interface IGameModeListener {
    /**
     * Called when the game mode of the user has changed.
     */
    void onGameModeChanged(String packageName, int gameModeFrom, int gameModeTo, int userId);
}
+1 −9
Original line number Diff line number Diff line
@@ -50,15 +50,11 @@ public final class GameManagerTests {
        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(mPackageName, GameManager.GAME_MODE_STANDARD);
    }

    @Test
    public void testPublicApiGameModeGetterSetter() {
        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
                mGameManager.getGameMode());

        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                mGameManager.getGameMode());

@@ -73,10 +69,6 @@ public final class GameManagerTests {

    @Test
    public void testPrivilegedGameModeGetterSetter() {
        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
                mGameManager.getGameMode(mPackageName));

        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
        assertEquals(GameManager.GAME_MODE_STANDARD,
                mGameManager.getGameMode(mPackageName));

+107 −41
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.app.GameModeConfiguration;
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameManagerService;
import android.app.IGameModeListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -58,10 +59,12 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserManager;
@@ -132,6 +135,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
    private final Context mContext;
    private final Object mLock = new Object();
    private final Object mDeviceConfigLock = new Object();
    private final Object mGameModeListenerLock = new Object();
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    final Handler mHandler;
    private final PackageManager mPackageManager;
@@ -145,6 +149,9 @@ public final class GameManagerService extends IGameManagerService.Stub {
    private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
    @GuardedBy("mDeviceConfigLock")
    private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
    // listener to caller uid map
    @GuardedBy("mGameModeListenerLock")
    private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
    @Nullable
    private final GameServiceController mGameServiceController;

@@ -598,6 +605,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
            }
        }

        // used to check if the override package config has any game mode config, if not, it's
        // considered empty and safe to delete from settings
        boolean hasActiveGameModeConfig() {
            synchronized (mModeConfigLock) {
                return !mModeConfigs.isEmpty();
            }
        }

        /**
         * GameModeConfiguration contains all the values for all the interventions associated with
         * a game mode.
@@ -693,7 +708,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
            public boolean isActive() {
                return (mGameMode == GameManager.GAME_MODE_STANDARD
                        || mGameMode == GameManager.GAME_MODE_PERFORMANCE
                        || mGameMode == GameManager.GAME_MODE_BATTERY)
                        || mGameMode == GameManager.GAME_MODE_BATTERY
                        || mGameMode == GameManager.GAME_MODE_CUSTOM)
                        && !willGamePerformOptimizations(mGameMode);
            }

@@ -741,7 +757,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
        }

        private int getAvailableGameModesBitfield() {
            int field = 0;
            int field = modeToBitmask(GameManager.GAME_MODE_CUSTOM)
                    | modeToBitmask(GameManager.GAME_MODE_STANDARD);
            synchronized (mModeConfigLock) {
                for (final int mode : mModeConfigs.keySet()) {
                    field |= modeToBitmask(mode);
@@ -753,13 +770,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
            if (mPerfModeOptedIn) {
                field |= modeToBitmask(GameManager.GAME_MODE_PERFORMANCE);
            }
            // The lowest bit is reserved for UNSUPPORTED, STANDARD is supported if we support any
            // other mode.
            if (field > 1) {
                field |= modeToBitmask(GameManager.GAME_MODE_STANDARD);
            } else {
                field |= modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
            }
            return field;
        }

@@ -881,7 +891,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
    private final class LocalService extends GameManagerInternal {
        @Override
        public float getResolutionScalingFactor(String packageName, int userId) {
            final int gameMode = getGameModeFromSettings(packageName, userId);
            final int gameMode = getGameModeFromSettingsUnchecked(packageName, userId);
            return getResolutionScalingFactorInternal(packageName, gameMode, userId);
        }
    }
@@ -960,7 +970,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
            config = mConfigs.get(packageName);
        }
        if (config == null) {
            return new int[]{};
            return new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM};
        }
        return config.getAvailableGameModes();
    }
@@ -986,12 +996,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
        return getAvailableGameModesUnchecked(packageName);
    }

    private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) {
    private @GameMode int getGameModeFromSettingsUnchecked(String packageName,
            @UserIdInt int userId) {
        synchronized (mLock) {
            if (!mSettings.containsKey(userId)) {
                Slog.d(TAG, "User ID '" + userId + "' does not have a Game Mode"
                        + " selected for package: '" + packageName + "'");
                return GameManager.GAME_MODE_UNSUPPORTED;
                return GameManager.GAME_MODE_STANDARD;
            }

            return mSettings.get(userId).getGameModeLocked(packageName);
@@ -1024,12 +1035,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
        // 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.
        if (isValidPackageName(packageName, userId)) {
            return getGameModeFromSettings(packageName, userId);
            return getGameModeFromSettingsUnchecked(packageName, userId);
        }

        // Since the package name doesn't match, check the caller has the necessary permission.
        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
        return getGameModeFromSettings(packageName, userId);
        return getGameModeFromSettingsUnchecked(packageName, userId);
    }

    /**
@@ -1054,7 +1065,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
            return null;
        }

        final @GameMode int activeGameMode = getGameModeFromSettings(packageName, userId);
        final @GameMode int activeGameMode = getGameModeFromSettingsUnchecked(packageName, userId);
        final GamePackageConfiguration config = getConfig(packageName, userId);
        if (config != null) {
            final @GameMode int[] optedInGameModes = config.getOptedInGameModes();
@@ -1077,7 +1088,10 @@ public final class GameManagerService extends IGameManagerService.Stub {
            }
            return gameModeInfoBuilder.build();
        } else {
            return new GameModeInfo.Builder().setActiveGameMode(activeGameMode).build();
            return new GameModeInfo.Builder()
                    .setActiveGameMode(activeGameMode)
                    .setAvailableGameModes(getAvailableGameModesUnchecked(packageName))
                    .build();
        }
    }

@@ -1091,11 +1105,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
            throws SecurityException {
        checkPermission(Manifest.permission.MANAGE_GAME_MODE);

        if (!isPackageGame(packageName, userId)) {
            // Restrict to games only.
        if (!isPackageGame(packageName, userId) || gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
            // Restrict to games and valid game modes only.
            return;
        }

        int fromGameMode;
        synchronized (mLock) {
            userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                    Binder.getCallingUid(), userId, false, true, "setGameMode",
@@ -1107,9 +1121,21 @@ public final class GameManagerService extends IGameManagerService.Stub {
                return;
            }
            GameManagerSettings userSettings = mSettings.get(userId);
            fromGameMode = userSettings.getGameModeLocked(packageName);
            userSettings.setGameModeLocked(packageName, gameMode);
        }
        updateInterventions(packageName, gameMode, userId);
        synchronized (mGameModeListenerLock) {
            for (IGameModeListener listener : mGameModeListeners.keySet()) {
                Binder.allowBlocking(listener.asBinder());
                try {
                    listener.onGameModeChanged(packageName, fromGameMode, gameMode, userId);
                } catch (RemoteException ex) {
                    Slog.w(TAG, "Cannot notify game mode change for listener added by "
                            + mGameModeListeners.get(listener));
                }
            }
        }
        sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
        sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
                "SET_GAME_MODE", 0 /*delayMillis*/);
@@ -1334,6 +1360,57 @@ public final class GameManagerService extends IGameManagerService.Stub {
                + internalConfig.getScaling());
    }

    /**
     * Adds a game mode listener.
     *
     * @throws SecurityException if caller doesn't have
     *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
     *                           permission.
     */
    @Override
    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
    public void addGameModeListener(@NonNull IGameModeListener listener) {
        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
        try {
            final IBinder listenerBinder = listener.asBinder();
            listenerBinder.linkToDeath(new DeathRecipient() {
                @Override public void binderDied() {
                    // TODO(b/258851194): add traces on binder death based listener removal
                    removeGameModeListenerUnchecked(listener);
                    listenerBinder.unlinkToDeath(this, 0 /*flags*/);
                }
            }, 0 /*flags*/);
            synchronized (mGameModeListenerLock) {
                mGameModeListeners.put(listener, Binder.getCallingUid());
            }
        } catch (RemoteException ex) {
            Slog.e(TAG,
                    "Failed to link death recipient for IGameModeListener from caller "
                            + Binder.getCallingUid() + ", abandoned its listener registration", ex);
        }
    }

    /**
     * Removes a game mode listener.
     *
     * @throws SecurityException if caller doesn't have
     *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
     *                           permission.
     */
    @Override
    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
    public void removeGameModeListener(@NonNull IGameModeListener listener) {
        // TODO(b/258851194): add traces on manual listener removal
        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
        removeGameModeListenerUnchecked(listener);
    }

    private void removeGameModeListenerUnchecked(IGameModeListener listener) {
        synchronized (mGameModeListenerLock) {
            mGameModeListeners.remove(listener);
        }
    }

    /**
     * Notified when boot is completed.
     */
@@ -1474,7 +1551,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
        final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
        if (gameMode == GameManager.GAME_MODE_STANDARD
                || gameMode == GameManager.GAME_MODE_UNSUPPORTED || packageConfig == null
                || packageConfig.willGamePerformOptimizations(gameMode)) {
                || packageConfig.willGamePerformOptimizations(gameMode)
                || packageConfig.getGameModeConfiguration(gameMode) == null) {
            resetFps(packageName, userId);
            // resolution scaling does not need to be reset as it's now read dynamically on game
            // restart, see #getResolutionScalingFactor and CompatModePackages#getCompatScale.
@@ -1562,13 +1640,9 @@ public final class GameManagerService extends IGameManagerService.Stub {
                if (!bitFieldContainsModeBitmask(modesBitfield, gameModeToReset)) {
                    return;
                }
                // if the game mode to reset is the only mode other than standard mode or there
                // is device config, the entire package config override is removed.
                if (Integer.bitCount(modesBitfield) <= 2 || deviceConfig == null) {
                    settings.removeConfigOverride(packageName);
                } else {
                    // otherwise we reset the mode by removing the game mode config override
                configOverride.removeModeConfig(gameModeToReset);
                if (!configOverride.hasActiveGameModeConfig()) {
                    settings.removeConfigOverride(packageName);
                }
            } else {
                settings.removeConfigOverride(packageName);
@@ -1596,20 +1670,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
            // want to check if we support selectable game modes
            modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
            if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) {
                if (bitFieldContainsModeBitmask(modesBitfield,
                        GameManager.GAME_MODE_STANDARD)) {
                    // If the current set mode isn't supported,
                    // but we support STANDARD, then set the mode to STANDARD.
                // always default to STANDARD if there is no mode config
                newGameMode = GameManager.GAME_MODE_STANDARD;
                } else {
                    // If we don't support any game modes, then set to UNSUPPORTED
                    newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
            }
            }
        } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) {
            // If we have no config for the package, but the configured mode is not
            // UNSUPPORTED, then set to UNSUPPORTED
            newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
        } else {
            // always default to STANDARD if there is no package config
            newGameMode = GameManager.GAME_MODE_STANDARD;
        }
        return newGameMode;
    }
Loading