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

Commit 24c4706c authored by Andy Yu's avatar Andy Yu Committed by Automerger Merge Worker
Browse files

Merge "Add writing function to intervention list" into tm-qpr-dev am: eac10acb

parents e719ccce eac10acb
Loading
Loading
Loading
Loading
+182 −2
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -68,6 +69,7 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -77,9 +79,12 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.AttributeSet;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -90,6 +95,7 @@ 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;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -99,9 +105,17 @@ import com.android.server.SystemService.TargetUser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

/**
@@ -122,6 +136,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
    static final int POPULATE_GAME_MODE_SETTINGS = 3;
    static final int SET_GAME_STATE = 4;
    static final int CANCEL_GAME_LOADING_MODE = 5;
    static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6;
    static final int WRITE_SETTINGS_DELAY = 10 * 1000;  // 10 seconds
    static final int LOADING_BOOST_MAX_DURATION = 5 * 1000;  // 5 seconds

@@ -131,6 +146,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
            .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 =
            "game_mode_intervention.list";

    private final Context mContext;
    private final Object mLock = new Object();
@@ -138,8 +155,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
    private final Object mOverrideConfigLock = new Object();
    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
    final AtomicFile mGameModeInterventionListFile;
    private DeviceConfigListener mDeviceConfigListener;
    @GuardedBy("mLock")
    private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
@@ -158,9 +179,53 @@ public final class GameManagerService extends IGameManagerService.Stub {
        mContext = context;
        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();
        FileUtils.setPermissions(mSystemDir.toString(),
                FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
                -1, -1);
        mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
                                                     GAME_MODE_INTERVENTION_LIST_FILE_NAME));
        FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
                FileUtils.S_IRUSR | FileUtils.S_IWUSR
                        | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
                -1, -1);
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
            mGameServiceController = new GameServiceController(
                    context, BackgroundThread.getExecutor(),
                    new GameServiceProviderSelectorImpl(
                            context.getResources(),
                            context.getPackageManager()),
                    new GameServiceProviderInstanceFactoryImpl(context));
        } else {
            mGameServiceController = null;
        }
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    GameManagerService(Context context, Looper looper, File dataDir) {
        mContext = context;
        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();
        FileUtils.setPermissions(mSystemDir.toString(),
                FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
                -1, -1);
        mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
                GAME_MODE_INTERVENTION_LIST_FILE_NAME));
        FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
                FileUtils.S_IRUSR | FileUtils.S_IWUSR
                        | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
                -1, -1);
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
            mGameServiceController = new GameServiceController(
                    context, BackgroundThread.getExecutor(),
@@ -306,6 +371,22 @@ public final class GameManagerService extends IGameManagerService.Stub {
                    mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
                    break;
                }
                case WRITE_GAME_MODE_INTERVENTION_LIST_FILE: {
                    final int userId = (int) msg.obj;
                    if (userId < 0) {
                        Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId);
                        synchronized (mLock) {
                            removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
                        }
                        break;
                    }

                    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                    removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
                    writeGameModeInterventionsToFile(userId);
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    break;
                }
            }
        }
    }
@@ -955,6 +1036,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
            }
        }
        updateInterventions(packageName, gameMode, userId);
        final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
        msg.obj = userId;
        if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
            mHandler.sendMessage(msg);
        }
    }

    /**
@@ -1520,15 +1606,98 @@ public final class GameManagerService extends IGameManagerService.Stub {
                final int newGameMode = getNewGameMode(gameMode, config);
                if (newGameMode != gameMode) {
                    setGameMode(packageName, newGameMode, userId);
                }
                } else {
                    // Make sure we handle the case when the interventions are changed while
                    // the game mode remains the same. We call only updateInterventions() here.
                    updateInterventions(packageName, gameMode, userId);
                }
            }
        } catch (Exception e) {
            Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e);
        }

        final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
        msg.obj = userId;
        if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
            mHandler.sendMessage(msg);
        }
    }

    /*
     Write the interventions and mode of each game to file /system/data/game_mode_intervention.list
     Each line will contain the information of each game, separated by tab.
     The format of the output is:
     <package name> <UID> <current mode> <game mode 1> <interventions> <game mode 2> <interventions>
     For example:
     com.android.app1   1425    1   2   angle=0,scaling=1.0,fps=60  3   angle=1,scaling=0.5,fps=30
     */
    private void writeGameModeInterventionsToFile(@UserIdInt int userId) {
        FileOutputStream fileOutputStream = null;
        BufferedWriter bufferedWriter;
        try {
            fileOutputStream = mGameModeInterventionListFile.startWrite();
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream,
                    Charset.defaultCharset()));

            final StringBuilder sb = new StringBuilder();
            final List<String> installedGamesList = getInstalledGamePackageNamesByAllUsers(userId);
            for (final String packageName : installedGamesList) {
                GamePackageConfiguration packageConfig = getConfig(packageName);
                if (packageConfig == null) {
                    continue;
                }
                sb.append(packageName);
                sb.append("\t");
                sb.append(mPackageManager.getPackageUidAsUser(packageName, userId));
                sb.append("\t");
                sb.append(getGameMode(packageName, userId));
                sb.append("\t");
                final int[] modes = packageConfig.getAvailableGameModes();
                for (int mode : modes) {
                    final GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
                            packageConfig.getGameModeConfiguration(mode);
                    if (gameModeConfiguration == null) {
                        continue;
                    }
                    sb.append(mode);
                    sb.append("\t");
                    final int useAngle = gameModeConfiguration.getUseAngle() ? 1 : 0;
                    sb.append(TextUtils.formatSimple("angle=%d", useAngle));
                    sb.append(",");
                    final String scaling = gameModeConfiguration.getScaling();
                    sb.append("scaling=");
                    sb.append(scaling);
                    sb.append(",");
                    final int fps = gameModeConfiguration.getFps();
                    sb.append(TextUtils.formatSimple("fps=%d", fps));
                    sb.append("\t");
                }
                sb.append("\n");
            }
            bufferedWriter.append(sb);
            bufferedWriter.flush();
            FileUtils.sync(fileOutputStream);
            mGameModeInterventionListFile.finishWrite(fileOutputStream);
        } catch (Exception e) {
            mGameModeInterventionListFile.failWrite(fileOutputStream);
            Slog.wtf(TAG, "Failed to write game_mode_intervention.list, exception " + e);
        }
        return;
    }

    private String[] getInstalledGamePackageNames(int userId) {
    private int[] getAllUserIds(@UserIdInt int currentUserId) {
        final List<UserInfo> users = mUserManager.getUsers();
        int[] userIds = new int[users.size()];
        for (int i = 0; i < userIds.length; ++i) {
            userIds[i] = users.get(i).id;
        }
        if (currentUserId != -1) {
            userIds = ArrayUtils.appendInt(userIds, currentUserId);
        }
        return userIds;
    }

    private String[] getInstalledGamePackageNames(@UserIdInt int userId) {
        final List<PackageInfo> packages =
                mPackageManager.getInstalledPackagesAsUser(0, userId);
        return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category
@@ -1537,6 +1706,17 @@ public final class GameManagerService extends IGameManagerService.Stub {
                .toArray(String[]::new);
    }

    private List<String> getInstalledGamePackageNamesByAllUsers(@UserIdInt int currentUserId) {
        HashSet<String> packageSet = new HashSet<>();

        final int[] userIds = getAllUserIds(currentUserId);
        for (int userId : userIds) {
            packageSet.addAll(Arrays.asList(getInstalledGamePackageNames(userId)));
        }

        return new ArrayList<>(packageSet);
    }

    /**
     * @hide
     */
+159 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.app;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -49,6 +50,7 @@ import android.content.res.XmlResourceParser;
import android.hardware.power.Mode;
import android.os.Bundle;
import android.os.PowerManagerInternal;
import android.os.UserManager;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -69,6 +71,8 @@ import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;

import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -79,7 +83,7 @@ import java.util.function.Supplier;
@Presubmit
public class GameManagerServiceTests {
    @Mock MockContext mMockContext;
    private static final String TAG = "GameServiceTests";
    private static final String TAG = "GameManagerServiceTests";
    private static final String PACKAGE_NAME_INVALID = "com.android.app";
    private static final int USER_ID_1 = 1001;
    private static final int USER_ID_2 = 1002;
@@ -91,6 +95,8 @@ public class GameManagerServiceTests {
    private PackageManager mMockPackageManager;
    @Mock
    private PowerManagerInternal mMockPowerManager;
    @Mock
    private UserManager mMockUserManager;

    // Stolen from ConnectivityServiceTest.MockContext
    class MockContext extends ContextWrapper {
@@ -150,6 +156,15 @@ public class GameManagerServiceTests {
        public PackageManager getPackageManager() {
            return mMockPackageManager;
        }

        @Override
        public Object getSystemService(String name) {
            switch (name) {
                case Context.USER_SERVICE:
                    return mMockUserManager;
            }
            throw new UnsupportedOperationException("Couldn't find system service: " + name);
        }
    }

    @Before
@@ -198,6 +213,19 @@ public class GameManagerServiceTests {
        mTestLooper.dispatchAll();
    }

    private void switchUser(GameManagerService gameManagerService, int from, int to) {
        UserInfo userInfoFrom = new UserInfo(from, "name", 0);
        UserInfo userInfoTo = new UserInfo(to, "name", 0);
        gameManagerService.onUserSwitching(/* from */ new SystemService.TargetUser(userInfoFrom),
                /* to */ new SystemService.TargetUser(userInfoTo));
        mTestLooper.dispatchAll();
    }

    private void mockManageUsersGranted() {
        mMockContext.setPermission(Manifest.permission.MANAGE_USERS,
                PackageManager.PERMISSION_GRANTED);
    }

    private void mockModifyGameModeGranted() {
        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
                PackageManager.PERMISSION_GRANTED);
@@ -819,6 +847,7 @@ public class GameManagerServiceTests {
        GameManagerService gameManagerService = new GameManagerService(
                mMockContext, mTestLooper.getLooper());
        startUser(gameManagerService, USER_ID_1);

        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                GameManager.GAME_MODE_PERFORMANCE, "120", "0.3");
        gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
@@ -1246,4 +1275,133 @@ public class GameManagerServiceTests {
    public void testSetGameStateNotLoading() {
        setGameState(false);
    }

    private List<String> readGameModeInterventionList() throws Exception {
        final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(),
                "system/game_mode_intervention.list");
        assertNotNull(interventionFile);
        List<String> output = Files.readAllLines(interventionFile.toPath());
        return output;
    }

    private void mockInterventionListForMultipleUsers() {
        final String[] packageNames = new String[] {"com.android.app0",
                "com.android.app1", "com.android.app2"};

        final ApplicationInfo[] applicationInfos = new ApplicationInfo[3];
        final PackageInfo[] pis = new PackageInfo[3];
        for (int i = 0; i < 3; ++i) {
            applicationInfos[i] = new ApplicationInfo();
            applicationInfos[i].category = ApplicationInfo.CATEGORY_GAME;
            applicationInfos[i].packageName = packageNames[i];

            pis[i] = new PackageInfo();
            pis[i].packageName = packageNames[i];
            pis[i].applicationInfo = applicationInfos[i];
        }

        final List<PackageInfo> userOnePackages = new ArrayList<>();
        final List<PackageInfo> userTwoPackages = new ArrayList<>();
        userOnePackages.add(pis[1]);
        userTwoPackages.add(pis[0]);
        userTwoPackages.add(pis[2]);

        final List<UserInfo> userInfos = new ArrayList<>(2);
        userInfos.add(new UserInfo());
        userInfos.add(new UserInfo());
        userInfos.get(0).id = USER_ID_1;
        userInfos.get(1).id = USER_ID_2;

        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_1)))
                .thenReturn(userOnePackages);
        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_2)))
                .thenReturn(userTwoPackages);
        when(mMockUserManager.getUsers()).thenReturn(userInfos);
    }

    @Test
    public void testVerifyInterventionList() throws Exception {
        mockDeviceConfigAll();
        mockInterventionListForMultipleUsers();
        mockManageUsersGranted();
        mockModifyGameModeGranted();
        final Context context = InstrumentationRegistry.getContext();
        GameManagerService gameManagerService =
                new GameManagerService(mMockContext,
                                       mTestLooper.getLooper(),
                                       context.getFilesDir());
        startUser(gameManagerService, USER_ID_1);
        startUser(gameManagerService, USER_ID_2);

        gameManagerService.setGameModeConfigOverride("com.android.app0", USER_ID_2,
                GameManager.GAME_MODE_PERFORMANCE, "120", "0.6");
        gameManagerService.setGameModeConfigOverride("com.android.app2", USER_ID_2,
                GameManager.GAME_MODE_BATTERY, "60", "0.5");
        mTestLooper.dispatchAll();

        /* Expected fileOutput (order may vary)
         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

         The current game mode would only be set to non-zero if the current user have that game
         installed.
        */

        List<String> fileOutput = readGameModeInterventionList();
        assertEquals(fileOutput.size(), 3);

        String[] splitLine = fileOutput.get(0).split("\\s+");
        assertEquals(splitLine[0], "com.android.app2");
        assertEquals(splitLine[2], "3");
        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");
        splitLine = fileOutput.get(1).split("\\s+");
        assertEquals(splitLine[0], "com.android.app1");
        assertEquals(splitLine[2], "0");
        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.7,fps=30");
        splitLine = fileOutput.get(2).split("\\s+");
        assertEquals(splitLine[0], "com.android.app0");
        assertEquals(splitLine[2], "2");
        assertEquals(splitLine[3], "2");
        assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120");
        assertEquals(splitLine[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");

        switchUser(gameManagerService, USER_ID_2, USER_ID_1);
        gameManagerService.setGameMode("com.android.app1",
                GameManager.GAME_MODE_BATTERY, USER_ID_1);
        mTestLooper.dispatchAll();

        fileOutput = readGameModeInterventionList();
        assertEquals(fileOutput.size(), 3);

        splitLine = fileOutput.get(0).split("\\s+");
        assertEquals(splitLine[0], "com.android.app2");
        assertEquals(splitLine[2], "0");
        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");
        splitLine = fileOutput.get(1).split("\\s+");
        assertEquals(splitLine[0], "com.android.app1");
        assertEquals(splitLine[2], "3");
        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.7,fps=30");
        splitLine = fileOutput.get(2).split("\\s+");
        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[5], "3");
        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");

    }
}