1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.app; 18 19 import android.app.ActivityManager; 20 import android.app.GameManager; 21 import android.app.IGameManagerService; 22 import android.content.Context; 23 import android.os.RemoteException; 24 import android.os.ServiceManager; 25 import android.os.ServiceManager.ServiceNotFoundException; 26 import android.os.ShellCommand; 27 28 import java.io.PrintWriter; 29 import java.util.Locale; 30 import java.util.StringJoiner; 31 32 /** 33 * ShellCommands for GameManagerService. 34 * 35 * Use with {@code adb shell cmd game ...}. 36 */ 37 public class GameManagerShellCommand extends ShellCommand { 38 private static final String STANDARD_MODE_STR = "standard"; 39 private static final String STANDARD_MODE_NUM = "1"; 40 private static final String PERFORMANCE_MODE_STR = "performance"; 41 private static final String PERFORMANCE_MODE_NUM = "2"; 42 private static final String BATTERY_MODE_STR = "battery"; 43 private static final String BATTERY_MODE_NUM = "3"; 44 private static final String CUSTOM_MODE_STR = "custom"; 45 private static final String CUSTOM_MODE_NUM = "4"; 46 private static final String UNSUPPORTED_MODE_STR = "unsupported"; 47 private static final String UNSUPPORTED_MODE_NUM = String.valueOf( 48 GameManager.GAME_MODE_UNSUPPORTED); 49 GameManagerShellCommand()50 public GameManagerShellCommand() { 51 } 52 53 @Override onCommand(String cmd)54 public int onCommand(String cmd) { 55 if (cmd == null) { 56 return handleDefaultCommands(cmd); 57 } 58 final PrintWriter pw = getOutPrintWriter(); 59 try { 60 switch (cmd) { 61 case "set": { 62 return runSetGameModeConfig(pw); 63 } 64 case "reset": { 65 return runResetGameModeConfig(pw); 66 } 67 case "mode": { 68 /** The "mode" command allows setting a package's current game mode outside of 69 * the game dashboard UI. This command requires that a mode already be supported 70 * by the package. Devs can forcibly support game modes via the manifest 71 * metadata flags: com.android.app.gamemode.performance.enabled, 72 * com.android.app.gamemode.battery.enabled 73 * OR by `adb shell device_config put game_overlay \ 74 * <PACKAGE_NAME> <CONFIG_STRING>` 75 * see: {@link GameManagerServiceTests#mockDeviceConfigAll()} 76 */ 77 return runSetGameMode(pw); 78 } 79 case "list-modes": { 80 return runListGameModes(pw); 81 } 82 case "list-configs": { 83 return runListGameModeConfigs(pw); 84 } 85 default: 86 return handleDefaultCommands(cmd); 87 } 88 } catch (Exception e) { 89 pw.println("Error: " + e); 90 } 91 return -1; 92 } 93 runListGameModes(PrintWriter pw)94 private int runListGameModes(PrintWriter pw) throws ServiceNotFoundException, RemoteException { 95 final String packageName = getNextArgRequired(); 96 final int userId = ActivityManager.getCurrentUser(); 97 final GameManagerService gameManagerService = (GameManagerService) 98 ServiceManager.getService(Context.GAME_SERVICE); 99 final String currentMode = gameModeIntToString( 100 gameManagerService.getGameMode(packageName, userId)); 101 final StringJoiner sj = new StringJoiner(","); 102 for (int mode : gameManagerService.getAvailableGameModes(packageName, userId)) { 103 sj.add(gameModeIntToString(mode)); 104 } 105 pw.println(packageName + " current mode: " + currentMode + ", available game modes: [" + sj 106 + "]"); 107 return 0; 108 } 109 runListGameModeConfigs(PrintWriter pw)110 private int runListGameModeConfigs(PrintWriter pw) 111 throws ServiceNotFoundException, RemoteException { 112 final String packageName = getNextArgRequired(); 113 114 final GameManagerService gameManagerService = (GameManagerService) 115 ServiceManager.getService(Context.GAME_SERVICE); 116 117 final String listStr = gameManagerService.getInterventionList(packageName, 118 ActivityManager.getCurrentUser()); 119 120 if (listStr == null) { 121 pw.println("No interventions found for " + packageName); 122 } else { 123 pw.println(packageName + " interventions: " + listStr); 124 } 125 return 0; 126 } 127 runSetGameMode(PrintWriter pw)128 private int runSetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { 129 final String option = getNextOption(); 130 String userIdStr = null; 131 if (option != null && option.equals("--user")) { 132 userIdStr = getNextArgRequired(); 133 } 134 135 final String gameMode = getNextArgRequired(); 136 final String packageName = getNextArgRequired(); 137 final IGameManagerService service = IGameManagerService.Stub.asInterface( 138 ServiceManager.getServiceOrThrow(Context.GAME_SERVICE)); 139 boolean batteryModeSupported = false; 140 boolean perfModeSupported = false; 141 int userId = userIdStr != null ? Integer.parseInt(userIdStr) 142 : ActivityManager.getCurrentUser(); 143 int[] modes = service.getAvailableGameModes(packageName, userId); 144 for (int mode : modes) { 145 if (mode == GameManager.GAME_MODE_PERFORMANCE) { 146 perfModeSupported = true; 147 } else if (mode == GameManager.GAME_MODE_BATTERY) { 148 batteryModeSupported = true; 149 } 150 } 151 switch (gameMode.toLowerCase()) { 152 case STANDARD_MODE_NUM: 153 case STANDARD_MODE_STR: 154 // Standard mode can be used to specify loading ANGLE as the default OpenGL ES 155 // driver, so it should always be available. 156 service.setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId); 157 pw.println("Set game mode to `STANDARD` for user `" + userId + "` in game `" 158 + packageName + "`"); 159 break; 160 case PERFORMANCE_MODE_NUM: 161 case PERFORMANCE_MODE_STR: 162 if (perfModeSupported) { 163 service.setGameMode(packageName, GameManager.GAME_MODE_PERFORMANCE, 164 userId); 165 pw.println("Set game mode to `PERFORMANCE` for user `" + userId + "` in game `" 166 + packageName + "`"); 167 } else { 168 pw.println("Game mode: " + gameMode + " not supported by " 169 + packageName); 170 return -1; 171 } 172 break; 173 case BATTERY_MODE_NUM: 174 case BATTERY_MODE_STR: 175 if (batteryModeSupported) { 176 service.setGameMode(packageName, GameManager.GAME_MODE_BATTERY, 177 userId); 178 pw.println("Set game mode to `BATTERY` for user `" + userId + "` in game `" 179 + packageName + "`"); 180 } else { 181 pw.println("Game mode: " + gameMode + " not supported by " 182 + packageName); 183 return -1; 184 } 185 break; 186 case CUSTOM_MODE_NUM: 187 case CUSTOM_MODE_STR: 188 service.setGameMode(packageName, GameManager.GAME_MODE_CUSTOM, userId); 189 pw.println("Set game mode to `CUSTOM` for user `" + userId + "` in game `" 190 + packageName + "`"); 191 break; 192 default: 193 pw.println("Invalid game mode: " + gameMode); 194 return -1; 195 } 196 return 0; 197 } 198 runSetGameModeConfig(PrintWriter pw)199 private int runSetGameModeConfig(PrintWriter pw) 200 throws ServiceNotFoundException, RemoteException { 201 String option; 202 /** 203 * handling optional input 204 * "--user", "--downscale" and "--fps" can come in any order 205 */ 206 String userIdStr = null; 207 String fpsStr = null; 208 String downscaleRatio = null; 209 int gameMode = GameManager.GAME_MODE_CUSTOM; 210 while ((option = getNextOption()) != null) { 211 switch (option) { 212 case "--mode": 213 gameMode = Integer.parseInt(getNextArgRequired()); 214 break; 215 case "--user": 216 if (userIdStr == null) { 217 userIdStr = getNextArgRequired(); 218 } else { 219 pw.println("Duplicate option '" + option + "'"); 220 return -1; 221 } 222 break; 223 case "--downscale": 224 if (downscaleRatio == null) { 225 downscaleRatio = getNextArgRequired(); 226 if ("disable".equals(downscaleRatio)) { 227 downscaleRatio = "-1"; 228 } else { 229 try { 230 Float.parseFloat(downscaleRatio); 231 } catch (NumberFormatException e) { 232 pw.println("Invalid scaling ratio '" + downscaleRatio + "'"); 233 return -1; 234 } 235 } 236 } else { 237 pw.println("Duplicate option '" + option + "'"); 238 return -1; 239 } 240 break; 241 case "--fps": 242 if (fpsStr == null) { 243 fpsStr = getNextArgRequired(); 244 try { 245 Integer.parseInt(fpsStr); 246 } catch (NumberFormatException e) { 247 pw.println("Invalid frame rate: '" + fpsStr + "'"); 248 return -1; 249 } 250 } else { 251 pw.println("Duplicate option '" + option + "'"); 252 return -1; 253 } 254 break; 255 default: 256 pw.println("Invalid option '" + option + "'"); 257 return -1; 258 } 259 } 260 261 final String packageName = getNextArgRequired(); 262 263 int userId = userIdStr != null ? Integer.parseInt(userIdStr) 264 : ActivityManager.getCurrentUser(); 265 266 final GameManagerService gameManagerService = (GameManagerService) 267 ServiceManager.getService(Context.GAME_SERVICE); 268 if (gameManagerService == null) { 269 pw.println("Failed to find GameManagerService on device"); 270 return -1; 271 } 272 gameManagerService.setGameModeConfigOverride(packageName, userId, gameMode, 273 fpsStr, downscaleRatio); 274 pw.println("Set custom mode intervention config for user `" + userId + "` in game `" 275 + packageName + "` as: `" 276 + "downscaling-ratio: " + downscaleRatio + ";" 277 + "fps-override: " + fpsStr + "`"); 278 return 0; 279 } 280 runResetGameModeConfig(PrintWriter pw)281 private int runResetGameModeConfig(PrintWriter pw) 282 throws ServiceNotFoundException, RemoteException { 283 String option = null; 284 String gameMode = null; 285 String userIdStr = null; 286 while ((option = getNextOption()) != null) { 287 switch (option) { 288 case "--user": 289 if (userIdStr == null) { 290 userIdStr = getNextArgRequired(); 291 } else { 292 pw.println("Duplicate option '" + option + "'"); 293 return -1; 294 } 295 break; 296 case "--mode": 297 if (gameMode == null) { 298 gameMode = getNextArgRequired(); 299 } else { 300 pw.println("Duplicate option '" + option + "'"); 301 return -1; 302 } 303 break; 304 default: 305 pw.println("Invalid option '" + option + "'"); 306 return -1; 307 } 308 } 309 310 final String packageName = getNextArgRequired(); 311 312 final GameManagerService gameManagerService = (GameManagerService) 313 ServiceManager.getService(Context.GAME_SERVICE); 314 315 int userId = userIdStr != null ? Integer.parseInt(userIdStr) 316 : ActivityManager.getCurrentUser(); 317 318 if (gameMode == null) { 319 gameManagerService.resetGameModeConfigOverride(packageName, userId, -1); 320 return 0; 321 } 322 323 switch (gameMode.toLowerCase(Locale.getDefault())) { 324 case PERFORMANCE_MODE_NUM: 325 case PERFORMANCE_MODE_STR: 326 gameManagerService.resetGameModeConfigOverride(packageName, userId, 327 GameManager.GAME_MODE_PERFORMANCE); 328 break; 329 case BATTERY_MODE_NUM: 330 case BATTERY_MODE_STR: 331 gameManagerService.resetGameModeConfigOverride(packageName, userId, 332 GameManager.GAME_MODE_BATTERY); 333 break; 334 default: 335 pw.println("Invalid game mode: " + gameMode); 336 return -1; 337 } 338 return 0; 339 } 340 gameModeIntToString(@ameManager.GameMode int gameMode)341 private static String gameModeIntToString(@GameManager.GameMode int gameMode) { 342 switch (gameMode) { 343 case GameManager.GAME_MODE_BATTERY: 344 return BATTERY_MODE_STR; 345 case GameManager.GAME_MODE_PERFORMANCE: 346 return PERFORMANCE_MODE_STR; 347 case GameManager.GAME_MODE_CUSTOM: 348 return CUSTOM_MODE_STR; 349 case GameManager.GAME_MODE_STANDARD: 350 return STANDARD_MODE_STR; 351 case GameManager.GAME_MODE_UNSUPPORTED: 352 return UNSUPPORTED_MODE_STR; 353 } 354 return ""; 355 } 356 357 @Override onHelp()358 public void onHelp() { 359 PrintWriter pw = getOutPrintWriter(); 360 pw.println("Game manager (game) commands:"); 361 pw.println(" help"); 362 pw.println(" Print this help text."); 363 pw.println(" downscale"); 364 pw.println(" Deprecated. Please use `custom` command."); 365 pw.println(" list-configs <PACKAGE_NAME>"); 366 pw.println(" Lists the current intervention configs of an app."); 367 pw.println(" list-modes <PACKAGE_NAME>"); 368 pw.println(" Lists the current selected and available game modes of an app."); 369 pw.println(" mode [--user <USER_ID>] [1|2|3|4|standard|performance|battery|custom] " 370 + "<PACKAGE_NAME>"); 371 pw.println(" Set app to run in the specified game mode, if supported."); 372 pw.println(" --user <USER_ID>: apply for the given user,"); 373 pw.println(" the current user is used when unspecified."); 374 pw.println(" set [intervention configs] <PACKAGE_NAME>"); 375 pw.println(" Set app to run at custom mode using provided intervention configs"); 376 pw.println(" Intervention configs consists of:"); 377 pw.println(" --downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65"); 378 pw.println(" |0.7|0.75|0.8|0.85|0.9|disable]: Set app to run at the"); 379 pw.println(" specified scaling ratio."); 380 pw.println(" --fps: Integer value to set app to run at the specified fps,"); 381 pw.println(" if supported. 0 to disable."); 382 pw.println(" reset [--mode [2|3|performance|battery] --user <USER_ID>] <PACKAGE_NAME>"); 383 pw.println(" Resets the game mode of the app to device configuration."); 384 pw.println(" This should only be used to reset any override to non custom game mode"); 385 pw.println(" applied using the deprecated `set` command"); 386 pw.println(" --mode [2|3|performance|battery]: apply for the given mode,"); 387 pw.println(" resets all modes when unspecified."); 388 pw.println(" --user <USER_ID>: apply for the given user,"); 389 pw.println(" the current user is used when unspecified."); 390 } 391 } 392