All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.app.GameManagerService Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2021 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.app;

import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;

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;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.GameManager;
import android.app.GameManager.GameMode;
import android.app.IGameManagerService;
import android.app.compat.PackageOverride;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
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.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.util.ArrayMap;
import android.util.KeyValueListParser;
import android.util.Slog;

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.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;

import java.io.FileDescriptor;
import java.util.List;

/**
 * Service to manage game related features.
 *
 * 

Game service is a core service that monitors, coordinates game related features, * as well as collect metrics.

* * @hide */ public final class GameManagerService extends IGameManagerService.Stub { public static final String TAG = "GameManagerService"; private static final boolean DEBUG = false; static final int WRITE_SETTINGS = 1; static final int REMOVE_SETTINGS = 2; static final int POPULATE_GAME_MODE_SETTINGS = 3; static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds static final PackageOverride COMPAT_ENABLED = new PackageOverride.Builder().setEnabled(true) .build(); static final PackageOverride COMPAT_DISABLED = new PackageOverride.Builder().setEnabled(false) .build(); private final Context mContext; private final Object mLock = new Object(); private final Object mDeviceConfigLock = new Object(); private final Handler mHandler; private final PackageManager mPackageManager; private final IPlatformCompat mPlatformCompat; private DeviceConfigListener mDeviceConfigListener; @GuardedBy("mLock") private final ArrayMap mSettings = new ArrayMap<>(); @GuardedBy("mDeviceConfigLock") private final ArrayMap mConfigs = new ArrayMap<>(); public GameManagerService(Context context) { this(context, createServiceThread().getLooper()); } GameManagerService(Context context, Looper looper) { mContext = context; mHandler = new SettingsHandler(looper); mPackageManager = mContext.getPackageManager(); mPlatformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); } @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result) { new GameManagerShellCommand().exec(this, in, out, err, args, callback, result); } class SettingsHandler extends Handler { SettingsHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { doHandleMessage(msg); } void doHandleMessage(Message msg) { 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); if (mSettings.containsKey(userId)) { GameManagerSettings userSettings = mSettings.get(userId); userSettings.writePersistentDataLocked(); } } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); break; } 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. removeMessages(WRITE_SETTINGS, msg.obj); removeMessages(REMOVE_SETTINGS, msg.obj); if (mSettings.containsKey(userId)) { final GameManagerSettings userSettings = mSettings.get(userId); mSettings.remove(userId); userSettings.writePersistentDataLocked(); } } 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); updateConfigsForUser(userId, packageNames); break; } } } } private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { DeviceConfigListener() { super(); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_GAME_OVERLAY, mContext.getMainExecutor(), this); } @Override public void onPropertiesChanged(Properties properties) { final String[] packageNames = properties.getKeyset().toArray(new String[0]); updateConfigsForUser(ActivityManager.getCurrentUser(), packageNames); } @Override public void finalize() { DeviceConfig.removeOnPropertiesChangedListener(this); } } // 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; } /** * GamePackageConfiguration manages all game mode config details for its associated package. */ @VisibleForTesting public class GamePackageConfiguration { public static final String TAG = "GameManagerService_GamePackageConfiguration"; /** * Metadata that can be included in the app manifest to allow/disallow any window manager * downscaling interventions. Default value is TRUE. */ public static final String METADATA_WM_ALLOW_DOWNSCALE = "com.android.graphics.intervention.wm.allowDownscale"; /** * Metadata that needs to be included in the app manifest to OPT-IN to PERFORMANCE mode. * This means the app will assume full responsibility for the experience provided by this * mode and the system will enable no window manager downscaling. * Default value is FALSE */ public static final String METADATA_PERFORMANCE_MODE_ENABLE = "com.android.app.gamemode.performance.enabled"; /** * Metadata that needs to be included in the app manifest to OPT-IN to BATTERY mode. * This means the app will assume full responsibility for the experience provided by this * mode and the system will enable no window manager downscaling. * Default value is FALSE */ public static final String METADATA_BATTERY_MODE_ENABLE = "com.android.app.gamemode.battery.enabled"; private final String mPackageName; private final ArrayMap mModeConfigs; private boolean mPerfModeOptedIn; private boolean mBatteryModeOptedIn; private boolean mAllowDownscale; GamePackageConfiguration(String packageName, int userId) { mPackageName = packageName; mModeConfigs = new ArrayMap<>(); try { final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName, PackageManager.GET_META_DATA, userId); if (ai.metaData != null) { mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE); mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE); mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true); } else { mPerfModeOptedIn = false; mBatteryModeOptedIn = false; mAllowDownscale = true; } } catch (PackageManager.NameNotFoundException e) { // Not all packages are installed, hence ignore those that are not installed yet. Slog.v(TAG, "Failed to get package metadata"); } final String configString = DeviceConfig.getProperty( DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName); if (configString != null) { final String[] gameModeConfigStrings = configString.split(":"); for (String gameModeConfigString : gameModeConfigStrings) { try { final KeyValueListParser parser = new KeyValueListParser(','); parser.setString(gameModeConfigString); addModeConfig(new GameModeConfiguration(parser)); } catch (IllegalArgumentException e) { Slog.e(TAG, "Invalid config string"); } } } } /** * GameModeConfiguration contains all the values for all the interventions associated with * a game mode. */ @VisibleForTesting public class GameModeConfiguration { public static final String TAG = "GameManagerService_GameModeConfiguration"; public static final String MODE_KEY = "mode"; public static final String SCALING_KEY = "downscaleFactor"; public static final String DEFAULT_SCALING = "1.0"; private final @GameMode int mGameMode; private final String mScaling; GameModeConfiguration(KeyValueListParser parser) { mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED); mScaling = !mAllowDownscale || isGameModeOptedIn(mGameMode) ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING); } public int getGameMode() { return mGameMode; } public String getScaling() { return mScaling; } public boolean isValid() { return (mGameMode == GameManager.GAME_MODE_PERFORMANCE || mGameMode == GameManager.GAME_MODE_BATTERY) && (!mAllowDownscale || getCompatChangeId() != 0); } /** * @hide */ public String toString() { return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]"; } /** * Get the corresponding compat change id for the current scaling string. */ public long getCompatChangeId() { return GameManagerService.getCompatChangeId(mScaling); } } public String getPackageName() { return mPackageName; } /** * Gets whether a package has opted into a game mode via its manifest. * * @return True if the app package has specified in its metadata either: * "com.android.app.gamemode.performance.enabled" or * "com.android.app.gamemode.battery.enabled" with a value of "true" */ public boolean isGameModeOptedIn(@GameMode int gameMode) { return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY) || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE); } private int getAvailableGameModesBitfield() { int field = 0; for (final int mode : mModeConfigs.keySet()) { field |= modeToBitmask(mode); } if (mBatteryModeOptedIn) { field |= modeToBitmask(GameManager.GAME_MODE_BATTERY); } 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; } /** * Get an array of a package's available game modes. */ public @GameMode int[] getAvailableGameModes() { final int modesBitfield = getAvailableGameModesBitfield(); int[] modes = new int[Integer.bitCount(modesBitfield)]; int i = 0; final int gameModeInHighestBit = Integer.numberOfTrailingZeros(Integer.highestOneBit(modesBitfield)); for (int mode = 0; mode <= gameModeInHighestBit; ++mode) { if (((modesBitfield >> mode) & 1) != 0) { modes[i++] = mode; } } return modes; } /** * Get a GameModeConfiguration for a given game mode. * * @return The package's GameModeConfiguration for the provided mode or null if absent */ public GameModeConfiguration getGameModeConfiguration(@GameMode int gameMode) { return mModeConfigs.get(gameMode); } /** * Insert a new GameModeConfiguration */ public void addModeConfig(GameModeConfiguration config) { if (config.isValid()) { mModeConfigs.put(config.getGameMode(), config); } else { Slog.w(TAG, "Invalid game mode config for " + mPackageName + ":" + config.toString()); } } public boolean isValid() { return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn; } public String toString() { return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]"; } } /** * SystemService lifecycle for GameService. * * @hide */ public static class Lifecycle extends SystemService { private GameManagerService mService; public Lifecycle(Context context) { super(context); } @Override public void onStart() { mService = new GameManagerService(getContext()); publishBinderService(Context.GAME_SERVICE, mService); mService.registerDeviceConfigListener(); mService.registerPackageReceiver(); } @Override public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { mService.onBootCompleted(); } } @Override public void onUserStarting(@NonNull TargetUser user) { mService.onUserStarting(user.getUserIdentifier()); } @Override public void onUserStopping(@NonNull TargetUser user) { mService.onUserStopping(user.getUserIdentifier()); } @Override public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { mService.onUserSwitching(from, to.getUserIdentifier()); } } private boolean isValidPackageName(String packageName, int userId) { try { return mPackageManager.getPackageUidAsUser(packageName, userId) == Binder.getCallingUid(); } catch (PackageManager.NameNotFoundException e) { return false; } } private void checkPermission(String permission) throws SecurityException { if (mContext.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Access denied to process: " + Binder.getCallingPid() + ", must have permission " + permission); } } /** * Get an array of game modes available for a given package. * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}. */ @Override @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException { checkPermission(Manifest.permission.MANAGE_GAME_MODE); synchronized (mDeviceConfigLock) { final GamePackageConfiguration config = mConfigs.get(packageName); if (config == null) { return new int[]{GameManager.GAME_MODE_UNSUPPORTED}; } return config.getAvailableGameModes(); } } private @GameMode int getGameModeFromSettings(String packageName, int userId) { synchronized (mLock) { if (!mSettings.containsKey(userId)) { Slog.w(TAG, "User ID '" + userId + "' does not have a Game Mode" + " selected for package: '" + packageName + "'"); return GameManager.GAME_MODE_UNSUPPORTED; } return mSettings.get(userId).getGameModeLocked(packageName); } } /** * Get the Game Mode for the package name. * Verifies that the calling process is for the matching package UID or has * {@link android.Manifest.permission#MANAGE_GAME_MODE}. */ @Override public @GameMode int getGameMode(String packageName, int userId) throws SecurityException { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "getGameMode", "com.android.server.app.GameManagerService"); // Restrict to games only. try { final ApplicationInfo applicationInfo = mPackageManager .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { // The game mode for applications that are not identified as game is always // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)} return GameManager.GAME_MODE_UNSUPPORTED; } } catch (PackageManager.NameNotFoundException e) { return GameManager.GAME_MODE_UNSUPPORTED; } // This function handles two types of queries: // 1.) A normal, non-privileged app querying its own Game Mode. // 2.) A privileged system service querying the Game Mode of another package. // The least privileged case is a normal app performing a query, so check that first and // 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); } // Since the package name doesn't match, check the caller has the necessary permission. checkPermission(Manifest.permission.MANAGE_GAME_MODE); return getGameModeFromSettings(packageName, userId); } /** * Sets the Game Mode for the package name. * Verifies that the calling process has {@link android.Manifest.permission#MANAGE_GAME_MODE}. */ @Override @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(String packageName, @GameMode int gameMode, int userId) throws SecurityException { checkPermission(Manifest.permission.MANAGE_GAME_MODE); // Restrict to games only. try { final ApplicationInfo applicationInfo = mPackageManager .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { // Ignore attempt to set the game mode for applications that are not identified // as game. See {@link PackageManager#setApplicationCategoryHint(String, int)} return; } } catch (PackageManager.NameNotFoundException e) { return; } synchronized (mLock) { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "setGameMode", "com.android.server.app.GameManagerService"); if (!mSettings.containsKey(userId)) { return; } GameManagerSettings userSettings = mSettings.get(userId); userSettings.setGameModeLocked(packageName, gameMode); final Message msg = mHandler.obtainMessage(WRITE_SETTINGS); msg.obj = userId; if (!mHandler.hasEqualMessages(WRITE_SETTINGS, userId)) { mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY); } } updateCompatModeDownscale(packageName, gameMode); } /** * Notified when boot is completed. */ @VisibleForTesting void onBootCompleted() { Slog.d(TAG, "onBootCompleted"); } void onUserStarting(int userId) { synchronized (mLock) { if (!mSettings.containsKey(userId)) { GameManagerSettings userSettings = new GameManagerSettings(Environment.getDataSystemDeDirectory(userId)); mSettings.put(userId, userSettings); userSettings.readPersistentDataLocked(); } } final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS); msg.obj = userId; mHandler.sendMessage(msg); } void onUserStopping(int userId) { synchronized (mLock) { if (!mSettings.containsKey(userId)) { return; } final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS); msg.obj = userId; mHandler.sendMessage(msg); } } void onUserSwitching(TargetUser from, int toUserId) { if (from != null) { synchronized (mLock) { final int fromUserId = from.getUserIdentifier(); if (mSettings.containsKey(fromUserId)) { final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS); msg.obj = fromUserId; mHandler.sendMessage(msg); } } } final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS); msg.obj = toUserId; mHandler.sendMessage(msg); } /** * @hide */ @VisibleForTesting public void disableCompatScale(String packageName) { final long uid = Binder.clearCallingIdentity(); try { Slog.i(TAG, "Disabling downscale for " + packageName); final ArrayMap 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); } } private void enableCompatScale(String packageName, long scaleId) { final long uid = Binder.clearCallingIdentity(); try { Slog.i(TAG, "Enabling downscale: " + scaleId + " for " + packageName); final ArrayMap 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(String packageName, @GameMode int gameMode) { synchronized (mDeviceConfigLock) { if (gameMode == GameManager.GAME_MODE_STANDARD || gameMode == GameManager.GAME_MODE_UNSUPPORTED) { disableCompatScale(packageName); return; } final GamePackageConfiguration packageConfig = mConfigs.get(packageName); if (packageConfig == null) { disableCompatScale(packageName); Slog.v(TAG, "Package configuration not found for " + packageName); return; } if (DEBUG) { Slog.v(TAG, dumpDeviceConfigs()); } if (packageConfig.isGameModeOptedIn(gameMode)) { disableCompatScale(packageName); return; } 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) { Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for " + packageName); return; } enableCompatScale(packageName, scaleId); } } private int modeToBitmask(@GameMode int gameMode) { return (1 << gameMode); } private boolean bitFieldContainsModeBitmask(int bitField, @GameMode int gameMode) { return (bitField & modeToBitmask(gameMode)) != 0; } /** * @hide */ @VisibleForTesting public void updateConfigsForUser(int userId, String ...packageNames) { try { synchronized (mDeviceConfigLock) { for (final String packageName : packageNames) { final GamePackageConfiguration config = new GamePackageConfiguration(packageName, userId); if (config.isValid()) { if (DEBUG) { Slog.i(TAG, "Adding config: " + config.toString()); } mConfigs.put(packageName, config); } else { Slog.w(TAG, "Invalid package config for " + config.getPackageName() + ":" + config.toString()); mConfigs.remove(packageName); } } } for (final String packageName : packageNames) { if (mSettings.containsKey(userId)) { int gameMode = getGameMode(packageName, userId); int newGameMode = gameMode; // Make sure the user settings and package configs don't conflict. I.e. the // user setting is set to a mode that no longer available due to config/manifest // changes. Most of the time we won't have to change anything. GamePackageConfiguration config; synchronized (mDeviceConfigLock) { config = mConfigs.get(packageName); } if (config != null) { int modesBitfield = config.getAvailableGameModesBitfield(); // Remove UNSUPPORTED to simplify the logic here, since we really just // 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. 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; } if (newGameMode != gameMode) { setGameMode(packageName, newGameMode, userId); } updateCompatModeDownscale(packageName, gameMode); } } } catch (Exception e) { Slog.e(TAG, "Failed to update compat modes for user: " + userId); } } private String[] getInstalledGamePackageNames(int userId) { final List packages = mPackageManager.getInstalledPackagesAsUser(0, userId); return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) .map(e -> e.packageName) .toArray(String[]::new); } /** * @hide */ @VisibleForTesting public GamePackageConfiguration getConfig(String packageName) { return mConfigs.get(packageName); } private void registerPackageReceiver() { final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(ACTION_PACKAGE_ADDED); packageFilter.addAction(ACTION_PACKAGE_CHANGED); packageFilter.addAction(ACTION_PACKAGE_REMOVED); packageFilter.addDataScheme("package"); final BroadcastReceiver packageReceiver = new BroadcastReceiver() { @Override public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { final Uri data = intent.getData(); try { final int userId = getSendingUserId(); if (userId != ActivityManager.getCurrentUser()) { return; } final String packageName = data.getSchemeSpecificPart(); try { final ApplicationInfo applicationInfo = mPackageManager .getApplicationInfoAsUser( packageName, PackageManager.MATCH_ALL, userId); if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { return; } } catch (PackageManager.NameNotFoundException e) { // Ignore the exception. } switch (intent.getAction()) { case ACTION_PACKAGE_ADDED: case ACTION_PACKAGE_CHANGED: updateConfigsForUser(userId, packageName); break; case ACTION_PACKAGE_REMOVED: disableCompatScale(packageName); synchronized (mDeviceConfigLock) { mConfigs.remove(packageName); } break; default: // do nothing break; } } catch (NullPointerException e) { Slog.e(TAG, "Failed to get package name for new package"); } } }; mContext.registerReceiverForAllUsers(packageReceiver, packageFilter, /* broadcastPermission= */ null, /* scheduler= */ null); } private void registerDeviceConfigListener() { mDeviceConfigListener = new DeviceConfigListener(); } private String dumpDeviceConfigs() { StringBuilder out = new StringBuilder(); for (String key : mConfigs.keySet()) { out.append("[\nName: ").append(key) .append("\nConfig: ").append(mConfigs.get(key).toString()).append("\n]"); } return out.toString(); } private static ServiceThread createServiceThread() { ServiceThread handlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); handlerThread.start(); return handlerThread; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy