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