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

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

Merge changes Ia3905cd1,Ibcedd393,Id145fe3a

* changes:
  Send writing file messages on shutdown with no delay
  Persist game mode configs in settings file
  Support per user game config override
parents ab0857dd 117debcb
Loading
Loading
Loading
Loading
+165 −136

File changed.

Preview size limit exceeded, changes collapsed.

+152 −22
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.util.Xml;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.server.app.GameManagerService.GamePackageConfiguration;
import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -39,10 +41,11 @@ import java.util.Map;

/**
 * Persists all GameService related settings.
 *
 * @hide
 */
public class GameManagerSettings {

    public static final String TAG = "GameManagerService_GameManagerSettings";
    // The XML file follows the below format:
    // <?xml>
    // <packages>
@@ -53,8 +56,14 @@ public class GameManagerSettings {

    private static final String TAG_PACKAGE = "package";
    private static final String TAG_PACKAGES = "packages";
    private static final String TAG_GAME_MODE_CONFIG = "gameModeConfig";

    private static final String ATTR_NAME = "name";
    private static final String ATTR_GAME_MODE = "gameMode";
    private static final String ATTR_SCALING = "scaling";
    private static final String ATTR_FPS = "fps";
    private static final String ATTR_USE_ANGLE = "useAngle";
    private static final String ATTR_LOADING_BOOST_DURATION = "loadingBoost";

    private final File mSystemDir;
    @VisibleForTesting
@@ -62,6 +71,8 @@ public class GameManagerSettings {

    // PackageName -> GameMode
    private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
    // PackageName -> GamePackageConfiguration
    private final ArrayMap<String, GamePackageConfiguration> mConfigOverrides = new ArrayMap<>();

    GameManagerSettings(File dataDir) {
        mSystemDir = new File(dataDir, "system");
@@ -74,7 +85,7 @@ public class GameManagerSettings {
    }

    /**
     * Return the game mode of a given package.
     * Returns the game mode of a given package.
     * This operation must be synced with an external lock.
     */
    int getGameModeLocked(String packageName) {
@@ -85,7 +96,7 @@ public class GameManagerSettings {
    }

    /**
     * Set the game mode of a given package.
     * Sets the game mode of a given package.
     * This operation must be synced with an external lock.
     */
    void setGameModeLocked(String packageName, int gameMode) {
@@ -93,15 +104,40 @@ public class GameManagerSettings {
    }

    /**
     * Remove the game mode of a given package.
     * Removes all game settings of a given package.
     * This operation must be synced with an external lock.
     */
    void removeGame(String packageName) {
        mGameModes.remove(packageName);
        mConfigOverrides.remove(packageName);
    }

    /**
     * Returns the game config override of a given package or null if absent.
     * This operation must be synced with an external lock.
     */
    GamePackageConfiguration getConfigOverride(String packageName) {
        return mConfigOverrides.get(packageName);
    }

    /**
     * Write all current game service settings into disk.
     * Sets the game config override of a given package.
     * This operation must be synced with an external lock.
     */
    void setConfigOverride(String packageName, GamePackageConfiguration configOverride) {
        mConfigOverrides.put(packageName, configOverride);
    }

    /**
     * Removes the game mode config override of a given package.
     * This operation must be synced with an external lock.
     */
    void removeConfigOverride(String packageName) {
        mConfigOverrides.remove(packageName);
    }

    /**
     * Writes all current game service settings into disk.
     * This operation must be synced with an external lock.
     */
    void writePersistentDataLocked() {
@@ -115,9 +151,11 @@ public class GameManagerSettings {

            serializer.startTag(null, TAG_PACKAGES);
            for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) {
                String packageName = entry.getKey();
                serializer.startTag(null, TAG_PACKAGE);
                serializer.attribute(null, ATTR_NAME, entry.getKey());
                serializer.attribute(null, ATTR_NAME, packageName);
                serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue());
                writeGameModeConfigTags(serializer, mConfigOverrides.get(packageName));
                serializer.endTag(null, TAG_PACKAGE);
            }
            serializer.endTag(null, TAG_PACKAGES);
@@ -133,20 +171,41 @@ public class GameManagerSettings {
            return;
        } catch (java.io.IOException e) {
            mSettingsFile.failWrite(fstr);
            Slog.wtf(GameManagerService.TAG, "Unable to write game manager service settings, "
            Slog.wtf(TAG, "Unable to write game manager service settings, "
                    + "current changes will be lost at reboot", e);
        }
    }

    private void writeGameModeConfigTags(TypedXmlSerializer serializer,
            GamePackageConfiguration config) throws IOException {
        if (config == null) {
            return;
        }
        final int[] gameModes = config.getAvailableGameModes();
        for (final int mode : gameModes) {
            final GameModeConfiguration modeConfig = config.getGameModeConfiguration(mode);
            if (modeConfig != null) {
                serializer.startTag(null, TAG_GAME_MODE_CONFIG);
                serializer.attributeInt(null, ATTR_GAME_MODE, mode);
                serializer.attributeBoolean(null, ATTR_USE_ANGLE, modeConfig.getUseAngle());
                serializer.attribute(null, ATTR_FPS, modeConfig.getFpsStr());
                serializer.attributeFloat(null, ATTR_SCALING, modeConfig.getScaling());
                serializer.attributeInt(null, ATTR_LOADING_BOOST_DURATION,
                        modeConfig.getLoadingBoostDuration());
                serializer.endTag(null, TAG_GAME_MODE_CONFIG);
            }
        }
    }

    /**
     * Read game service settings from the disk.
     * Reads game service settings from the disk.
     * This operation must be synced with an external lock.
     */
    boolean readPersistentDataLocked() {
        mGameModes.clear();

        if (!mSettingsFile.exists()) {
            Slog.v(GameManagerService.TAG, "Settings file doesn't exists, skip reading");
            Slog.v(TAG, "Settings file doesn't exist, skip reading");
            return false;
        }

@@ -160,8 +219,7 @@ public class GameManagerSettings {
                // Do nothing
            }
            if (type != XmlPullParser.START_TAG) {
                Slog.wtf(GameManagerService.TAG,
                        "No start tag found in package manager settings");
                Slog.wtf(TAG, "No start tag found in package manager settings");
                return false;
            }

@@ -173,35 +231,107 @@ public class GameManagerSettings {
                }

                String tagName = parser.getName();
                if (tagName.equals(TAG_PACKAGE)) {
                if (type == XmlPullParser.START_TAG && TAG_PACKAGE.equals(tagName)) {
                    readPackage(parser);
                } else {
                    Slog.w(GameManagerService.TAG, "Unknown element: " + parser.getName());
                    XmlUtils.skipCurrentTag(parser);
                    Slog.w(TAG, "Unknown element under packages tag: " + tagName + " with type: "
                            + type);
                }
            }
        } catch (XmlPullParserException | java.io.IOException e) {
            Slog.wtf(GameManagerService.TAG, "Error reading package manager settings", e);
            Slog.wtf(TAG, "Error reading package manager settings", e);
            return false;
        }

        return true;
    }

    // this must be called on tag of type START_TAG.
    private void readPackage(TypedXmlPullParser parser) throws XmlPullParserException,
            IOException {
        String name = null;
        final String name = parser.getAttributeValue(null, ATTR_NAME);
        if (name == null) {
            Slog.wtf(TAG, "No package name found in package tag");
            XmlUtils.skipCurrentTag(parser);
            return;
        }
        int gameMode = GameManager.GAME_MODE_UNSUPPORTED;
        try {
            name = parser.getAttributeValue(null, ATTR_NAME);
            gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
        } catch (XmlPullParserException e) {
            Slog.wtf(GameManagerService.TAG, "Error reading game mode", e);
            Slog.wtf(TAG, "Invalid game mode in package tag: "
                    + parser.getAttributeValue(null, ATTR_GAME_MODE), e);
            return;
        }
        if (name != null) {
        mGameModes.put(name, gameMode);
        final int packageTagDepth = parser.getDepth();
        int type;
        final GamePackageConfiguration config = new GamePackageConfiguration(name);
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG
                || parser.getDepth() > packageTagDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }
            final String tagName = parser.getName();
            if (type == XmlPullParser.START_TAG && TAG_GAME_MODE_CONFIG.equals(tagName)) {
                readGameModeConfig(parser, config);
            } else {
                XmlUtils.skipCurrentTag(parser);
                Slog.w(TAG, "Unknown element under package tag: " + tagName + " with type: "
                        + type);
            }
        }
        if (config.getAvailableGameModes().length > 1) {
            mConfigOverrides.put(name, config);
        }
    }

    // this must be called on tag of type START_TAG.
    private void readGameModeConfig(TypedXmlPullParser parser, GamePackageConfiguration config) {
        final int gameMode;
        try {
            gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
        } catch (XmlPullParserException e) {
            Slog.wtf(TAG, "Invalid game mode value in config tag: " + parser.getAttributeValue(null,
                    ATTR_GAME_MODE), e);
            return;
        }

        final GameModeConfiguration modeConfig = config.getOrAddDefaultGameModeConfiguration(
                gameMode);
        try {
            final float scaling = parser.getAttributeFloat(null, ATTR_SCALING);
            modeConfig.setScaling(scaling);
        } catch (XmlPullParserException e) {
            final String rawScaling = parser.getAttributeValue(null, ATTR_SCALING);
            if (rawScaling != null) {
                Slog.wtf(TAG, "Invalid scaling value in config tag: " + rawScaling, e);
            }
        }

        final String fps = parser.getAttributeValue(null, ATTR_FPS);
        modeConfig.setFpsStr(fps != null ? fps : GameModeConfiguration.DEFAULT_FPS);

        try {
            final boolean useAngle = parser.getAttributeBoolean(null, ATTR_USE_ANGLE);
            modeConfig.setUseAngle(useAngle);
        } catch (XmlPullParserException e) {
            final String rawUseAngle = parser.getAttributeValue(null, ATTR_USE_ANGLE);
            if (rawUseAngle != null) {
                Slog.wtf(TAG, "Invalid useAngle value in config tag: " + rawUseAngle, e);
            }
        }
        try {
            final int loadingBoostDuration = parser.getAttributeInt(null,
                    ATTR_LOADING_BOOST_DURATION);
            modeConfig.setLoadingBoostDuration(loadingBoostDuration);
        } catch (XmlPullParserException e) {
            final String rawLoadingBoost = parser.getAttributeValue(null,
                    ATTR_LOADING_BOOST_DURATION);
            if (rawLoadingBoost != null) {
                Slog.wtf(TAG, "Invalid loading boost in config tag: " + rawLoadingBoost, e);
            }
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -81,7 +81,8 @@ public class GameManagerShellCommand extends ShellCommand {
        final GameManagerService gameManagerService = (GameManagerService)
                ServiceManager.getService(Context.GAME_SERVICE);

        final String listStr = gameManagerService.getInterventionList(packageName);
        final String listStr = gameManagerService.getInterventionList(packageName,
                ActivityManager.getCurrentUser());

        if (listStr == null) {
            pw.println("No interventions found for " + packageName);
+73 −14
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
package com.android.server.app;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.server.app.GameManagerService.WRITE_SETTINGS;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -35,11 +37,15 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.Manifest;
import android.annotation.Nullable;
import android.app.GameManager;
import android.app.GameModeInfo;
import android.app.GameState;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -97,6 +103,7 @@ public class GameManagerServiceTests {
    private PowerManagerInternal mMockPowerManager;
    @Mock
    private UserManager mMockUserManager;
    private BroadcastReceiver mShutDownActionReceiver;

    // Stolen from ConnectivityServiceTest.MockContext
    class MockContext extends ContextWrapper {
@@ -165,6 +172,12 @@ public class GameManagerServiceTests {
            }
            throw new UnsupportedOperationException("Couldn't find system service: " + name);
        }

        @Override
        public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
            mShutDownActionReceiver =  receiver;
            return null;
        }
    }

    @Before
@@ -200,15 +213,16 @@ public class GameManagerServiceTests {
    @After
    public void tearDown() throws Exception {
        LocalServices.removeServiceForTest(PowerManagerInternal.class);
        GameManagerService gameManagerService = new GameManagerService(mMockContext);
        if (mMockingSession != null) {
            mMockingSession.finishMocking();
        }
        deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
    }

    private void startUser(GameManagerService gameManagerService, int userId) {
        UserInfo userInfo = new UserInfo(userId, "name", 0);
        gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo));
        gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo),
                InstrumentationRegistry.getContext().getFilesDir());
        mTestLooper.dispatchAll();
    }

@@ -584,7 +598,7 @@ public class GameManagerServiceTests {
            gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        }
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName);
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(scaling, config.getGameModeConfiguration(gameMode).getScaling(), 0.01f);
    }

@@ -594,7 +608,7 @@ public class GameManagerServiceTests {

        // Validate GamePackageConfiguration returns the correct value.
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName);
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);

        // Validate GameManagerService.isAngleEnabled() returns the correct value.
@@ -607,7 +621,7 @@ public class GameManagerServiceTests {

        // Validate GamePackageConfiguration returns the correct value.
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName);
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(
                loadingBoost, config.getGameModeConfiguration(gameMode).getLoadingBoostDuration());

@@ -623,7 +637,7 @@ public class GameManagerServiceTests {
            gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        }
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName);
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
    }

@@ -1049,7 +1063,7 @@ public class GameManagerServiceTests {
                mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName);
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(90,
                config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
        assertEquals(30, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
@@ -1064,7 +1078,7 @@ public class GameManagerServiceTests {
                mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName);
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertEquals(0,
                config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
        assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
@@ -1092,7 +1106,7 @@ public class GameManagerServiceTests {
        startUser(gameManagerService, USER_ID_1);
        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
        GameManagerService.GamePackageConfiguration config =
                gameManagerService.getConfig(mPackageName);
                gameManagerService.getConfig(mPackageName, USER_ID_1);
        assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
    }

@@ -1339,10 +1353,15 @@ public class GameManagerServiceTests {
        mTestLooper.dispatchAll();

        /* Expected fileOutput (order may vary)
         # user 1001:
         com.android.app2 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.5,fps=60
         com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
         com.android.app0 <UID>   0   2   angle=0,scaling=0.6,fps=120 3   angle=0,scaling=0.7,fps=30

         # user 1002:
         com.android.app2 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
         com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
         com.android.app0 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
         The current game mode would only be set to non-zero if the current user have that game
         installed.
        */
@@ -1386,7 +1405,7 @@ public class GameManagerServiceTests {
        assertEquals(splitLine[3], "2");
        assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
        assertEquals(splitLine[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60");
        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
        splitLine = fileOutput.get(1).split("\\s+");
        assertEquals(splitLine[0], "com.android.app1");
        assertEquals(splitLine[2], "3");
@@ -1398,7 +1417,7 @@ public class GameManagerServiceTests {
        assertEquals(splitLine[0], "com.android.app0");
        assertEquals(splitLine[2], "0");
        assertEquals(splitLine[3], "2");
        assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120");
        assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
        assertEquals(splitLine[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");

@@ -1493,12 +1512,52 @@ public class GameManagerServiceTests {

    @Test
    public void testGetResolutionScalingFactor_noUserId() {
        mockModifyGameModeDenied();
        mockModifyGameModeGranted();
        mockDeviceConfigAll();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_2);
        assertEquals(-1f, gameManagerService.getResolutionScalingFactor(mPackageName,
                GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f);
        assertThrows(IllegalArgumentException.class, () -> {
            gameManagerService.getResolutionScalingFactor(mPackageName,
                    GameManager.GAME_MODE_BATTERY, USER_ID_1);
        });
    }

    @Test
    public void testWritingSettingFile_onShutdown() throws InterruptedException {
        mockModifyGameModeGranted();
        mockDeviceConfigAll();
        GameManagerService gameManagerService = new GameManagerService(mMockContext);
        gameManagerService.onBootCompleted();
        startUser(gameManagerService, USER_ID_1);
        Thread.sleep(500);
        gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1,
                GameManager.GAME_MODE_BATTERY, "60", "0.5");
        gameManagerService.setGameMode("com.android.app1", USER_ID_1,
                GameManager.GAME_MODE_PERFORMANCE);
        GameManagerSettings settings = new GameManagerSettings(
                InstrumentationRegistry.getContext().getFilesDir());
        Thread.sleep(500);
        // no data written as delayed messages are queued
        assertFalse(settings.readPersistentDataLocked());
        assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
        Intent shutdown = new Intent();
        shutdown.setAction(Intent.ACTION_SHUTDOWN);
        mShutDownActionReceiver.onReceive(mMockContext, shutdown);
        Thread.sleep(500);
        // data is written on processing new message with no delay on shutdown,
        // and all queued messages should be removed
        assertTrue(settings.readPersistentDataLocked());
        assertFalse(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
    }

    private static void deleteFolder(File folder) {
        File[] files = folder.listFiles();
        if (files != null) {
            for (File file : files) {
                deleteFolder(file);
            }
        }
        folder.delete();
    }
}
+148 −19

File changed.

Preview size limit exceeded, changes collapsed.