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

Commit c28fc68b authored by Xiang Wang's avatar Xiang Wang
Browse files

Support dynamic game resolution scaling factor

CompatModePackages will read scaling factor from local game manager
service directly and treat any positive value returned as compat scaling
override (with highest priority).

Bug: b/240335717
Test: atest GameManagerServiceTests WmTests:CompatModePackagesTests
Test: adb shell cmd game set --mode 3 --downscale 0.39 $PKG
Change-Id: I95e0ac4af6a2540ba53bfb449b61b1648f3790ad
parent f823057f
Loading
Loading
Loading
Loading
+30 −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;

/**
 * Game manager local system service interface.
 *
 * @hide Only for use within the system server.
 */

public abstract class GameManagerInternal {
    /**
     * Used by the CompatModePackages to read game's compat scaling override.
     */
    public abstract float getResolutionScalingFactor(String packageName, int userId);
}
+18 −165
Original line number Diff line number Diff line
@@ -25,20 +25,6 @@ import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscali
import static com.android.internal.R.styleable.GameModeConfig_allowGameFpsOverride;
import static com.android.internal.R.styleable.GameModeConfig_supportsBatteryGameMode;
import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanceGameMode;
import static com.android.server.wm.CompatModePackages.DOWNSCALED;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_30;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_35;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_40;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_45;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_50;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_55;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_60;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_65;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_70;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_75;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_80;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_85;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_90;

import android.Manifest;
import android.annotation.NonNull;
@@ -48,10 +34,10 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.GameManager;
import android.app.GameManager.GameMode;
import android.app.GameManagerInternal;
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameManagerService;
import android.app.compat.PackageOverride;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -75,9 +61,7 @@ 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.ServiceManager;
import android.os.ShellCallback;
import android.os.UserManager;
import android.provider.DeviceConfig;
@@ -92,8 +76,6 @@ import android.util.Xml;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.CompatibilityOverrideConfig;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -140,10 +122,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
    static final int WRITE_SETTINGS_DELAY = 10 * 1000;  // 10 seconds
    static final int LOADING_BOOST_MAX_DURATION = 5 * 1000;  // 5 seconds

    static final PackageOverride COMPAT_ENABLED = new PackageOverride.Builder().setEnabled(true)
            .build();
    static final PackageOverride COMPAT_DISABLED = new PackageOverride.Builder().setEnabled(false)
            .build();
    private static final String PACKAGE_NAME_MSG_KEY = "packageName";
    private static final String USER_ID_MSG_KEY = "userId";
    private static final String GAME_MODE_INTERVENTION_LIST_FILE_NAME =
@@ -156,7 +134,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
    private final Handler mHandler;
    private final PackageManager mPackageManager;
    private final UserManager mUserManager;
    private final IPlatformCompat mPlatformCompat;
    private final PowerManagerInternal mPowerManagerInternal;
    private final File mSystemDir;
    @VisibleForTesting
@@ -180,8 +157,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
        mHandler = new SettingsHandler(looper);
        mPackageManager = mContext.getPackageManager();
        mUserManager = mContext.getSystemService(UserManager.class);
        mPlatformCompat = IPlatformCompat.Stub.asInterface(
                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
        mSystemDir = new File(Environment.getDataDirectory(), "system");
        mSystemDir.mkdirs();
@@ -212,8 +187,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
        mHandler = new SettingsHandler(looper);
        mPackageManager = mContext.getPackageManager();
        mUserManager = mContext.getSystemService(UserManager.class);
        mPlatformCompat = IPlatformCompat.Stub.asInterface(
                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
        mSystemDir = new File(dataDir, "system");
        mSystemDir.mkdirs();
@@ -326,10 +299,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
                    break;
                }
                case POPULATE_GAME_MODE_SETTINGS: {
                    // Scan all game packages and re-enforce the configured compat mode overrides
                    // as the DeviceConfig may have be wiped/since last reboot and we can't risk
                    // having overrides configured for packages that no longer have any DeviceConfig
                    // and thus any way to escape compat mode.
                    removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
                    final int userId = (int) msg.obj;
                    final String[] packageNames = getInstalledGamePackageNames(userId);
@@ -412,39 +381,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
        }
    }

    // Turn the raw string to the corresponding CompatChange id.
    static long getCompatChangeId(String raw) {
        switch (raw) {
            case "0.3":
                return DOWNSCALE_30;
            case "0.35":
                return DOWNSCALE_35;
            case "0.4":
                return DOWNSCALE_40;
            case "0.45":
                return DOWNSCALE_45;
            case "0.5":
                return DOWNSCALE_50;
            case "0.55":
                return DOWNSCALE_55;
            case "0.6":
                return DOWNSCALE_60;
            case "0.65":
                return DOWNSCALE_65;
            case "0.7":
                return DOWNSCALE_70;
            case "0.75":
                return DOWNSCALE_75;
            case "0.8":
                return DOWNSCALE_80;
            case "0.85":
                return DOWNSCALE_85;
            case "0.9":
                return DOWNSCALE_90;
        }
        return 0;
    }

    public enum FrameRate {
        FPS_DEFAULT(0),
        FPS_30(30),
@@ -752,13 +688,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
                        + mUseAngle + ",Fps:" + mFps + ",Loading Boost Duration:"
                        + mLoadingBoostDuration + "]";
            }

            /**
             * Get the corresponding compat change id for the current scaling string.
             */
            public long getCompatChangeId() {
                return GameManagerService.getCompatChangeId(String.valueOf(mScaling));
            }
        }

        public String getPackageName() {
@@ -856,6 +785,14 @@ 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);
            return getResolutionScalingFactorInternal(packageName, gameMode, userId);
        }
    }

    /**
     * SystemService lifecycle for GameService.
     *
@@ -866,13 +803,13 @@ public final class GameManagerService extends IGameManagerService.Stub {

        public Lifecycle(Context context) {
            super(context);
            mService = new GameManagerService(context);
        }

        @Override
        public void onStart() {
            final Context context = getContext();
            mService = new GameManagerService(context);
            publishBinderService(Context.GAME_SERVICE, mService);
            mService.publishLocalService();
            mService.registerDeviceConfigListener();
            mService.registerPackageReceiver();
        }
@@ -1185,7 +1122,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
     * Updates the resolution scaling factor for the package's target game mode and activates it.
     *
     * @param scalingFactor enable scaling override over any other compat scaling if positive,
     *                      disable otherwise
     *                      or disable the override otherwise
     * @throws SecurityException        if caller doesn't have
     *                                  {@link android.Manifest.permission#MANAGE_GAME_MODE}
     *                                  permission.
@@ -1315,28 +1252,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
        }
    }

    /**
     * @hide
     */
    @VisibleForTesting
    public void disableCompatScale(String packageName) {
        final long uid = Binder.clearCallingIdentity();
        try {
            Slog.i(TAG, "Disabling downscale for " + packageName);
            final ArrayMap<Long, PackageOverride> overrides = new ArrayMap<>();
            overrides.put(DOWNSCALED, COMPAT_DISABLED);
            final CompatibilityOverrideConfig changeConfig = new CompatibilityOverrideConfig(
                    overrides);
            try {
                mPlatformCompat.putOverridesOnReleaseBuilds(changeConfig, packageName);
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e);
            }
        } finally {
            Binder.restoreCallingIdentity(uid);
        }
    }

    /**
     * Remove frame rate override due to mode switch
     */
@@ -1350,61 +1265,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
        }
    }

    private void enableCompatScale(String packageName, long scaleId) {
        final long uid = Binder.clearCallingIdentity();
        try {
            Slog.i(TAG, "Enabling downscale: " + scaleId + " for " + packageName);
            final ArrayMap<Long, PackageOverride> overrides = new ArrayMap<>();
            overrides.put(DOWNSCALED, COMPAT_ENABLED);
            overrides.put(DOWNSCALE_30, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_35, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_40, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_45, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_50, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_55, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_60, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_65, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_70, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_75, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_80, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_85, COMPAT_DISABLED);
            overrides.put(DOWNSCALE_90, COMPAT_DISABLED);
            overrides.put(scaleId, COMPAT_ENABLED);
            final CompatibilityOverrideConfig changeConfig = new CompatibilityOverrideConfig(
                    overrides);
            try {
                mPlatformCompat.putOverridesOnReleaseBuilds(changeConfig, packageName);
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e);
            }
        } finally {
            Binder.restoreCallingIdentity(uid);
        }
    }

    private void updateCompatModeDownscale(GamePackageConfiguration packageConfig,
            String packageName, @GameMode int gameMode) {

        if (DEBUG) {
            Slog.v(TAG, dumpDeviceConfigs());
        }
        final GamePackageConfiguration.GameModeConfiguration modeConfig =
                packageConfig.getGameModeConfiguration(gameMode);
        if (modeConfig == null) {
            Slog.i(TAG, "Game mode " + gameMode + " not found for " + packageName);
            return;
        }
        long scaleId = modeConfig.getCompatChangeId();
        if (scaleId == 0) {
            disableCompatScale(packageName);
            Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for "
                    + packageName);
            return;
        }

        enableCompatScale(packageName, scaleId);
    }

    private int modeToBitmask(@GameMode int gameMode) {
        return (1 << gameMode);
    }
@@ -1442,20 +1302,17 @@ public final class GameManagerService extends IGameManagerService.Stub {
            @GameMode int gameMode, @UserIdInt int userId) {
        if (gameMode == GameManager.GAME_MODE_STANDARD
                || gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
            disableCompatScale(packageName);
            resetFps(packageName, userId);
            return;
        }
        final GamePackageConfiguration packageConfig = getConfig(packageName);
        if (packageConfig == null) {
            disableCompatScale(packageName);
            Slog.v(TAG, "Package configuration not found for " + packageName);
            return;
        }
        if (packageConfig.willGamePerformOptimizations(gameMode)) {
            return;
        }
        updateCompatModeDownscale(packageConfig, packageName, gameMode);
        updateFps(packageConfig, packageName, gameMode, userId);
        updateUseAngle(packageName, gameMode);
    }
@@ -1670,7 +1527,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
                }
            }
        } catch (Exception e) {
            Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e);
            Slog.e(TAG, "Failed to update configs for user " + userId + ": " + e);
        }

        final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
@@ -1821,14 +1678,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
                            updateConfigsForUser(userId, true /*checkGamePackage*/, packageName);
                            break;
                        case ACTION_PACKAGE_REMOVED:
                            disableCompatScale(packageName);
                            // If EXTRA_REPLACING is true, it means there will be an
                            // ACTION_PACKAGE_ADDED triggered after this because this
                            // is an updated package that gets installed. Hence, disable
                            // resolution downscaling effort but avoid removing the server
                            // or commandline overriding configurations because those will
                            // not change but the package game mode configurations may change
                            // which may opt in and/or opt out some game mode configurations.
                            if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
                                synchronized (mOverrideConfigLock) {
                                    mOverrideConfigs.remove(packageName);
@@ -1860,6 +1709,10 @@ public final class GameManagerService extends IGameManagerService.Stub {
        mDeviceConfigListener = new DeviceConfigListener();
    }

    private void publishLocalService() {
        LocalServices.addService(GameManagerInternal.class, new LocalService());
    }

    private String dumpDeviceConfigs() {
        StringBuilder out = new StringBuilder();
        for (String key : mConfigs.keySet()) {
+15 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;

import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.GameManagerInternal;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
@@ -48,6 +49,7 @@ import android.util.TypedXmlSerializer;
import android.util.Xml;

import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -64,6 +66,7 @@ public final class CompatModePackages {
    private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;

    private final ActivityTaskManagerService mService;
    private GameManagerInternal mGameManager;
    private final AtomicFile mFile;

    // Compatibility state: no longer ask user to select the mode.
@@ -375,6 +378,18 @@ public final class CompatModePackages {

    float getCompatScale(String packageName, int uid) {
        final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
        if (mGameManager == null) {
            mGameManager = LocalServices.getService(GameManagerInternal.class);
        }
        if (mGameManager != null) {
            final int userId = userHandle.getIdentifier();
            final float scalingFactor = mGameManager.getResolutionScalingFactor(packageName,
                    userId);
            if (scalingFactor > 0) {
                return 1f / scalingFactor;
            }
        }

        if (CompatChanges.isChangeEnabled(DOWNSCALED, packageName, userHandle)) {
            if (CompatChanges.isChangeEnabled(DOWNSCALE_90, packageName, userHandle)) {
                return 1f / 0.9f;
+0 −1
Original line number Diff line number Diff line
@@ -201,7 +201,6 @@ public class GameManagerServiceTests {
    public void tearDown() throws Exception {
        LocalServices.removeServiceForTest(PowerManagerInternal.class);
        GameManagerService gameManagerService = new GameManagerService(mMockContext);
        gameManagerService.disableCompatScale(mPackageName);
        if (mMockingSession != null) {
            mMockingSession.finishMocking();
        }
+95 −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 com.android.server.wm;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;

import android.app.GameManagerInternal;
import android.platform.test.annotations.Presubmit;

import androidx.test.filters.SmallTest;

import com.android.server.LocalServices;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Tests for the {@link CompatModePackages} class.
 *
 * Build/Install/Run:
 * atest WmTests:CompatModePackagesTests
 */
@SmallTest
@Presubmit
public class CompatModePackagesTests extends SystemServiceTestsBase {
    ActivityTaskManagerService mAtm;
    GameManagerInternal mGm;
    static final String TEST_PACKAGE = "compat.mode.packages";
    static final int TEST_USER_ID = 1;

    @Before
    public void setUp() {
        mAtm = mSystemServicesTestRule.getActivityTaskManagerService();
        mGm = mock(GameManagerInternal.class);
    }

    @After
    public void tearDown() {
        LocalServices.removeServiceForTest(GameManagerInternal.class);
    }

    @Test
    public void testGetCompatScale_gameManagerReturnsPositive() {
        LocalServices.addService(GameManagerInternal.class, mGm);
        float scale = 0.25f;
        doReturn(scale).when(mGm).getResolutionScalingFactor(anyString(), anyInt());
        assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1 / scale,
                0.01f);
    }

    @Test
    public void testGetCompatScale_gameManagerReturnsZero() {
        LocalServices.addService(GameManagerInternal.class, mGm);
        float scale = 0f;
        doReturn(scale).when(mGm).getResolutionScalingFactor(anyString(), anyInt());
        assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1f,
                0.01f);
    }

    @Test
    public void testGetCompatScale_gameManagerReturnsNegative() {
        LocalServices.addService(GameManagerInternal.class, mGm);
        float scale = -1f;
        doReturn(scale).when(mGm).getResolutionScalingFactor(anyString(), anyInt());
        assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1f,
                0.01f);
    }

    @Test
    public void testGetCompatScale_noGameManager() {
        assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1f,
                0.01f);
    }

}