| /* |
| * 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 android.app.ActivityManager; |
| import android.app.GameManager; |
| import android.app.IGameManagerService; |
| import android.content.Context; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.ServiceManager.ServiceNotFoundException; |
| import android.os.ShellCommand; |
| |
| import java.io.PrintWriter; |
| import java.util.Locale; |
| import java.util.StringJoiner; |
| |
| /** |
| * ShellCommands for GameManagerService. |
| * |
| * Use with {@code adb shell cmd game ...}. |
| */ |
| public class GameManagerShellCommand extends ShellCommand { |
| private static final String STANDARD_MODE_STR = "standard"; |
| private static final String STANDARD_MODE_NUM = "1"; |
| private static final String PERFORMANCE_MODE_STR = "performance"; |
| private static final String PERFORMANCE_MODE_NUM = "2"; |
| private static final String BATTERY_MODE_STR = "battery"; |
| private static final String BATTERY_MODE_NUM = "3"; |
| private static final String CUSTOM_MODE_STR = "custom"; |
| private static final String CUSTOM_MODE_NUM = "4"; |
| private static final String UNSUPPORTED_MODE_STR = "unsupported"; |
| private static final String UNSUPPORTED_MODE_NUM = String.valueOf( |
| GameManager.GAME_MODE_UNSUPPORTED); |
| |
| public GameManagerShellCommand() { |
| } |
| |
| @Override |
| public int onCommand(String cmd) { |
| if (cmd == null) { |
| return handleDefaultCommands(cmd); |
| } |
| final PrintWriter pw = getOutPrintWriter(); |
| try { |
| switch (cmd) { |
| case "set": { |
| return runSetGameModeConfig(pw); |
| } |
| case "reset": { |
| return runResetGameModeConfig(pw); |
| } |
| case "mode": { |
| /** The "mode" command allows setting a package's current game mode outside of |
| * the game dashboard UI. This command requires that a mode already be supported |
| * by the package. Devs can forcibly support game modes via the manifest |
| * metadata flags: com.android.app.gamemode.performance.enabled, |
| * com.android.app.gamemode.battery.enabled |
| * OR by `adb shell device_config put game_overlay \ |
| * <PACKAGE_NAME> <CONFIG_STRING>` |
| * see: {@link GameManagerServiceTests#mockDeviceConfigAll()} |
| */ |
| return runSetGameMode(pw); |
| } |
| case "list-modes": { |
| return runListGameModes(pw); |
| } |
| case "list-configs": { |
| return runListGameModeConfigs(pw); |
| } |
| default: |
| return handleDefaultCommands(cmd); |
| } |
| } catch (Exception e) { |
| pw.println("Error: " + e); |
| } |
| return -1; |
| } |
| |
| private int runListGameModes(PrintWriter pw) throws ServiceNotFoundException, RemoteException { |
| final String packageName = getNextArgRequired(); |
| final int userId = ActivityManager.getCurrentUser(); |
| final GameManagerService gameManagerService = (GameManagerService) |
| ServiceManager.getService(Context.GAME_SERVICE); |
| final String currentMode = gameModeIntToString( |
| gameManagerService.getGameMode(packageName, userId)); |
| final StringJoiner sj = new StringJoiner(","); |
| for (int mode : gameManagerService.getAvailableGameModes(packageName, userId)) { |
| sj.add(gameModeIntToString(mode)); |
| } |
| pw.println(packageName + " current mode: " + currentMode + ", available game modes: [" + sj |
| + "]"); |
| return 0; |
| } |
| |
| private int runListGameModeConfigs(PrintWriter pw) |
| throws ServiceNotFoundException, RemoteException { |
| final String packageName = getNextArgRequired(); |
| |
| final GameManagerService gameManagerService = (GameManagerService) |
| ServiceManager.getService(Context.GAME_SERVICE); |
| |
| final String listStr = gameManagerService.getInterventionList(packageName, |
| ActivityManager.getCurrentUser()); |
| |
| if (listStr == null) { |
| pw.println("No interventions found for " + packageName); |
| } else { |
| pw.println(packageName + " interventions: " + listStr); |
| } |
| return 0; |
| } |
| |
| private int runSetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { |
| final String option = getNextOption(); |
| String userIdStr = null; |
| if (option != null && option.equals("--user")) { |
| userIdStr = getNextArgRequired(); |
| } |
| |
| final String gameMode = getNextArgRequired(); |
| final String packageName = getNextArgRequired(); |
| final IGameManagerService service = IGameManagerService.Stub.asInterface( |
| ServiceManager.getServiceOrThrow(Context.GAME_SERVICE)); |
| boolean batteryModeSupported = false; |
| boolean perfModeSupported = false; |
| int userId = userIdStr != null ? Integer.parseInt(userIdStr) |
| : ActivityManager.getCurrentUser(); |
| int[] modes = service.getAvailableGameModes(packageName, userId); |
| for (int mode : modes) { |
| if (mode == GameManager.GAME_MODE_PERFORMANCE) { |
| perfModeSupported = true; |
| } else if (mode == GameManager.GAME_MODE_BATTERY) { |
| batteryModeSupported = true; |
| } |
| } |
| switch (gameMode.toLowerCase()) { |
| case STANDARD_MODE_NUM: |
| case STANDARD_MODE_STR: |
| // Standard mode can be used to specify loading ANGLE as the default OpenGL ES |
| // driver, so it should always be available. |
| service.setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId); |
| pw.println("Set game mode to `STANDARD` for user `" + userId + "` in game `" |
| + packageName + "`"); |
| break; |
| case PERFORMANCE_MODE_NUM: |
| case PERFORMANCE_MODE_STR: |
| if (perfModeSupported) { |
| service.setGameMode(packageName, GameManager.GAME_MODE_PERFORMANCE, |
| userId); |
| pw.println("Set game mode to `PERFORMANCE` for user `" + userId + "` in game `" |
| + packageName + "`"); |
| } else { |
| pw.println("Game mode: " + gameMode + " not supported by " |
| + packageName); |
| return -1; |
| } |
| break; |
| case BATTERY_MODE_NUM: |
| case BATTERY_MODE_STR: |
| if (batteryModeSupported) { |
| service.setGameMode(packageName, GameManager.GAME_MODE_BATTERY, |
| userId); |
| pw.println("Set game mode to `BATTERY` for user `" + userId + "` in game `" |
| + packageName + "`"); |
| } else { |
| pw.println("Game mode: " + gameMode + " not supported by " |
| + packageName); |
| return -1; |
| } |
| break; |
| case CUSTOM_MODE_NUM: |
| case CUSTOM_MODE_STR: |
| service.setGameMode(packageName, GameManager.GAME_MODE_CUSTOM, userId); |
| pw.println("Set game mode to `CUSTOM` for user `" + userId + "` in game `" |
| + packageName + "`"); |
| break; |
| default: |
| pw.println("Invalid game mode: " + gameMode); |
| return -1; |
| } |
| return 0; |
| } |
| |
| private int runSetGameModeConfig(PrintWriter pw) |
| throws ServiceNotFoundException, RemoteException { |
| String option; |
| /** |
| * handling optional input |
| * "--user", "--downscale" and "--fps" can come in any order |
| */ |
| String userIdStr = null; |
| String fpsStr = null; |
| String downscaleRatio = null; |
| int gameMode = GameManager.GAME_MODE_CUSTOM; |
| while ((option = getNextOption()) != null) { |
| switch (option) { |
| case "--mode": |
| gameMode = Integer.parseInt(getNextArgRequired()); |
| break; |
| case "--user": |
| if (userIdStr == null) { |
| userIdStr = getNextArgRequired(); |
| } else { |
| pw.println("Duplicate option '" + option + "'"); |
| return -1; |
| } |
| break; |
| case "--downscale": |
| if (downscaleRatio == null) { |
| downscaleRatio = getNextArgRequired(); |
| if ("disable".equals(downscaleRatio)) { |
| downscaleRatio = "-1"; |
| } else { |
| try { |
| Float.parseFloat(downscaleRatio); |
| } catch (NumberFormatException e) { |
| pw.println("Invalid scaling ratio '" + downscaleRatio + "'"); |
| return -1; |
| } |
| } |
| } else { |
| pw.println("Duplicate option '" + option + "'"); |
| return -1; |
| } |
| break; |
| case "--fps": |
| if (fpsStr == null) { |
| fpsStr = getNextArgRequired(); |
| try { |
| Integer.parseInt(fpsStr); |
| } catch (NumberFormatException e) { |
| pw.println("Invalid frame rate: '" + fpsStr + "'"); |
| return -1; |
| } |
| } else { |
| pw.println("Duplicate option '" + option + "'"); |
| return -1; |
| } |
| break; |
| default: |
| pw.println("Invalid option '" + option + "'"); |
| return -1; |
| } |
| } |
| |
| final String packageName = getNextArgRequired(); |
| |
| int userId = userIdStr != null ? Integer.parseInt(userIdStr) |
| : ActivityManager.getCurrentUser(); |
| |
| final GameManagerService gameManagerService = (GameManagerService) |
| ServiceManager.getService(Context.GAME_SERVICE); |
| if (gameManagerService == null) { |
| pw.println("Failed to find GameManagerService on device"); |
| return -1; |
| } |
| gameManagerService.setGameModeConfigOverride(packageName, userId, gameMode, |
| fpsStr, downscaleRatio); |
| pw.println("Set custom mode intervention config for user `" + userId + "` in game `" |
| + packageName + "` as: `" |
| + "downscaling-ratio: " + downscaleRatio + ";" |
| + "fps-override: " + fpsStr + "`"); |
| return 0; |
| } |
| |
| private int runResetGameModeConfig(PrintWriter pw) |
| throws ServiceNotFoundException, RemoteException { |
| String option = null; |
| String gameMode = null; |
| String userIdStr = null; |
| while ((option = getNextOption()) != null) { |
| switch (option) { |
| case "--user": |
| if (userIdStr == null) { |
| userIdStr = getNextArgRequired(); |
| } else { |
| pw.println("Duplicate option '" + option + "'"); |
| return -1; |
| } |
| break; |
| case "--mode": |
| if (gameMode == null) { |
| gameMode = getNextArgRequired(); |
| } else { |
| pw.println("Duplicate option '" + option + "'"); |
| return -1; |
| } |
| break; |
| default: |
| pw.println("Invalid option '" + option + "'"); |
| return -1; |
| } |
| } |
| |
| final String packageName = getNextArgRequired(); |
| |
| final GameManagerService gameManagerService = (GameManagerService) |
| ServiceManager.getService(Context.GAME_SERVICE); |
| |
| int userId = userIdStr != null ? Integer.parseInt(userIdStr) |
| : ActivityManager.getCurrentUser(); |
| |
| if (gameMode == null) { |
| gameManagerService.resetGameModeConfigOverride(packageName, userId, -1); |
| return 0; |
| } |
| |
| switch (gameMode.toLowerCase(Locale.getDefault())) { |
| case PERFORMANCE_MODE_NUM: |
| case PERFORMANCE_MODE_STR: |
| gameManagerService.resetGameModeConfigOverride(packageName, userId, |
| GameManager.GAME_MODE_PERFORMANCE); |
| break; |
| case BATTERY_MODE_NUM: |
| case BATTERY_MODE_STR: |
| gameManagerService.resetGameModeConfigOverride(packageName, userId, |
| GameManager.GAME_MODE_BATTERY); |
| break; |
| default: |
| pw.println("Invalid game mode: " + gameMode); |
| return -1; |
| } |
| return 0; |
| } |
| |
| private static String gameModeIntToString(@GameManager.GameMode int gameMode) { |
| switch (gameMode) { |
| case GameManager.GAME_MODE_BATTERY: |
| return BATTERY_MODE_STR; |
| case GameManager.GAME_MODE_PERFORMANCE: |
| return PERFORMANCE_MODE_STR; |
| case GameManager.GAME_MODE_CUSTOM: |
| return CUSTOM_MODE_STR; |
| case GameManager.GAME_MODE_STANDARD: |
| return STANDARD_MODE_STR; |
| case GameManager.GAME_MODE_UNSUPPORTED: |
| return UNSUPPORTED_MODE_STR; |
| } |
| return ""; |
| } |
| |
| @Override |
| public void onHelp() { |
| PrintWriter pw = getOutPrintWriter(); |
| pw.println("Game manager (game) commands:"); |
| pw.println(" help"); |
| pw.println(" Print this help text."); |
| pw.println(" downscale"); |
| pw.println(" Deprecated. Please use `custom` command."); |
| pw.println(" list-configs <PACKAGE_NAME>"); |
| pw.println(" Lists the current intervention configs of an app."); |
| pw.println(" list-modes <PACKAGE_NAME>"); |
| pw.println(" Lists the current selected and available game modes of an app."); |
| pw.println(" mode [--user <USER_ID>] [1|2|3|4|standard|performance|battery|custom] " |
| + "<PACKAGE_NAME>"); |
| pw.println(" Set app to run in the specified game mode, if supported."); |
| pw.println(" --user <USER_ID>: apply for the given user,"); |
| pw.println(" the current user is used when unspecified."); |
| pw.println(" set [intervention configs] <PACKAGE_NAME>"); |
| pw.println(" Set app to run at custom mode using provided intervention configs"); |
| pw.println(" Intervention configs consists of:"); |
| pw.println(" --downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65"); |
| pw.println(" |0.7|0.75|0.8|0.85|0.9|disable]: Set app to run at the"); |
| pw.println(" specified scaling ratio."); |
| pw.println(" --fps: Integer value to set app to run at the specified fps,"); |
| pw.println(" if supported. 0 to disable."); |
| pw.println(" reset [--mode [2|3|performance|battery] --user <USER_ID>] <PACKAGE_NAME>"); |
| pw.println(" Resets the game mode of the app to device configuration."); |
| pw.println(" This should only be used to reset any override to non custom game mode"); |
| pw.println(" applied using the deprecated `set` command"); |
| pw.println(" --mode [2|3|performance|battery]: apply for the given mode,"); |
| pw.println(" resets all modes when unspecified."); |
| pw.println(" --user <USER_ID>: apply for the given user,"); |
| pw.println(" the current user is used when unspecified."); |
| } |
| } |