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 android.app;
18 
19 import android.Manifest;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SystemApi;
25 import android.annotation.SystemService;
26 import android.annotation.TestApi;
27 import android.annotation.UserHandleAware;
28 import android.content.Context;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.os.Build;
32 import android.os.Handler;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.os.ServiceManager.ServiceNotFoundException;
36 
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 
40 /**
41  * The GameManager allows system apps to modify and query the game mode of apps.
42  */
43 @SystemService(Context.GAME_SERVICE)
44 public final class GameManager {
45 
46     private static final String TAG = "GameManager";
47 
48     private final @Nullable Context mContext;
49     private final IGameManagerService mService;
50 
51     /** @hide */
52     @IntDef(flag = false, prefix = {"GAME_MODE_"}, value = {
53             GAME_MODE_UNSUPPORTED, // 0
54             GAME_MODE_STANDARD, // 1
55             GAME_MODE_PERFORMANCE, // 2
56             GAME_MODE_BATTERY, // 3
57             GAME_MODE_CUSTOM, // 4
58     })
59     @Retention(RetentionPolicy.SOURCE)
60     public @interface GameMode {
61     }
62 
63     /**
64      * Game mode is not supported for this application.
65      */
66     public static final int GAME_MODE_UNSUPPORTED = 0;
67 
68     /**
69      * Standard game mode means the platform will use the game's default
70      * performance characteristics.
71      */
72     public static final int GAME_MODE_STANDARD = 1;
73 
74     /**
75      * Performance game mode maximizes the game's performance.
76      * <p>
77      * This game mode is highly likely to increase battery consumption.
78      */
79     public static final int GAME_MODE_PERFORMANCE = 2;
80 
81     /**
82      * Battery game mode will save battery and give longer game play time.
83      */
84     public static final int GAME_MODE_BATTERY = 3;
85 
86     /**
87      * Custom game mode that has user-provided configuration overrides.
88      * <p>
89      * Custom game mode is expected to be handled only by the platform using users'
90      * preferred config. It is currently not allowed to opt in custom mode in game mode XML file nor
91      * expected to perform app-based optimizations when activated.
92      */
93     public static final int GAME_MODE_CUSTOM = 4;
94 
GameManager(Context context, Handler handler)95     GameManager(Context context, Handler handler) throws ServiceNotFoundException {
96         mContext = context;
97         mService = IGameManagerService.Stub.asInterface(
98                 ServiceManager.getServiceOrThrow(Context.GAME_SERVICE));
99     }
100 
101     /**
102      * Return the user selected game mode for this application.
103      * <p>
104      * An application can use <code>android:isGame="true"</code> or
105      * <code>android:appCategory="game"</code> to indicate that the application is a game. If an
106      * application is not a game, always return {@link #GAME_MODE_UNSUPPORTED}.
107      * <p>
108      * Developers should call this API every time the application is resumed.
109      * <p>
110      * If a game's <code>targetSdkVersion</code> is {@link android.os.Build.VERSION_CODES#TIRAMISU}
111      * or lower, and when the game mode is set to {@link #GAME_MODE_CUSTOM} which is available in
112      * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or newer, this API will return
113      * {@link #GAME_MODE_STANDARD} instead for backward compatibility.
114      */
getGameMode()115     public @GameMode int getGameMode() {
116         return getGameModeImpl(mContext.getPackageName(),
117                 mContext.getApplicationInfo().targetSdkVersion);
118     }
119 
120     /**
121      * Gets the game mode for the given package.
122      * <p>
123      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
124      * <p>
125      * Also see {@link #getGameMode()} on how it handles SDK version compatibility.
126      *
127      * @hide
128      */
129     @TestApi
130     @UserHandleAware
131     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
getGameMode(@onNull String packageName)132     public @GameMode int getGameMode(@NonNull String packageName) {
133         final int targetSdkVersion;
134         try {
135             final ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
136                     packageName, PackageManager.ApplicationInfoFlags.of(0));
137             targetSdkVersion = applicationInfo.targetSdkVersion;
138         } catch (PackageManager.NameNotFoundException ex) {
139             return GAME_MODE_UNSUPPORTED;
140         }
141         return getGameModeImpl(packageName, targetSdkVersion);
142     }
143 
144     // This target SDK version check can be performed against any game by a privileged app, and
145     // we don't want a binder call each time to check on behalf of an app using CompatChange.
146     @SuppressWarnings("AndroidFrameworkCompatChange")
getGameModeImpl(@onNull String packageName, int targetSdkVersion)147     private @GameMode int getGameModeImpl(@NonNull String packageName, int targetSdkVersion) {
148         final int gameMode;
149         try {
150             gameMode = mService.getGameMode(packageName,
151                     mContext.getUserId());
152         } catch (RemoteException e) {
153             throw e.rethrowFromSystemServer();
154         }
155         if (gameMode == GAME_MODE_CUSTOM && targetSdkVersion <= Build.VERSION_CODES.TIRAMISU) {
156             return GAME_MODE_STANDARD;
157         }
158         return gameMode;
159     }
160 
161     /**
162      * Returns the {@link GameModeInfo} associated with the game associated with
163      * the given {@code packageName}. If the given package is not a game, {@code null} is
164      * always returned.
165      * <p>
166      * An application can use <code>android:isGame="true"</code> or
167      * <code>android:appCategory="game"</code> to indicate that the application is a game.
168      * If the manifest doesn't define a category, the category can also be
169      * provided by the installer via
170      * {@link android.content.pm.PackageManager#setApplicationCategoryHint(String, int)}.
171      * <p>
172      *
173      * @hide
174      */
175     @SystemApi
176     @UserHandleAware
177     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
getGameModeInfo(@onNull String packageName)178     public @Nullable GameModeInfo getGameModeInfo(@NonNull String packageName) {
179         try {
180             return mService.getGameModeInfo(packageName, mContext.getUserId());
181         } catch (RemoteException e) {
182             throw e.rethrowFromSystemServer();
183         }
184     }
185 
186     /**
187      * Sets the game mode for the given package.
188      * <p>
189      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
190      * <p>
191      * Setting the game mode on a non-game application or setting a game to
192      * {@link #GAME_MODE_UNSUPPORTED} will have no effect.
193      * @hide
194      */
195     @SystemApi
196     @UserHandleAware
197     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
setGameMode(@onNull String packageName, @GameMode int gameMode)198     public void setGameMode(@NonNull String packageName, @GameMode int gameMode) {
199         try {
200             mService.setGameMode(packageName, gameMode, mContext.getUserId());
201         } catch (RemoteException e) {
202             throw e.rethrowFromSystemServer();
203         }
204     }
205 
206     /**
207      * Returns a list of supported game modes for a given package.
208      * <p>
209      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
210      *
211      * @hide
212      */
213     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
getAvailableGameModes(@onNull String packageName)214     public @GameMode int[] getAvailableGameModes(@NonNull String packageName) {
215         try {
216             return mService.getAvailableGameModes(packageName, mContext.getUserId());
217         } catch (RemoteException e) {
218             throw e.rethrowFromSystemServer();
219         }
220     }
221 
222     /**
223      * Returns if ANGLE is enabled for a given package and user ID.
224      * <p>
225      * ANGLE (Almost Native Graphics Layer Engine) can translate OpenGL ES commands to Vulkan
226      * commands. Enabling ANGLE may improve the performance and/or reduce the power consumption of
227      * applications.
228      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
229      *
230      * @hide
231      */
232     @TestApi
233     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
isAngleEnabled(@onNull String packageName)234     public boolean isAngleEnabled(@NonNull String packageName) {
235         try {
236             return mService.isAngleEnabled(packageName, mContext.getUserId());
237         } catch (RemoteException e) {
238             throw e.rethrowFromSystemServer();
239         }
240     }
241 
242     /**
243      * Set up the automatic power boost if appropriate.
244      *
245      * @hide
246      */
247     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
notifyGraphicsEnvironmentSetup()248     public void notifyGraphicsEnvironmentSetup() {
249         try {
250             mService.notifyGraphicsEnvironmentSetup(
251                     mContext.getPackageName(), mContext.getUserId());
252         } catch (RemoteException e) {
253             throw e.rethrowFromSystemServer();
254         }
255     }
256 
257     /**
258      * Called by games to communicate the current state to the platform.
259      * @param gameState An object set to the current state.
260      */
setGameState(@onNull GameState gameState)261     public void setGameState(@NonNull GameState gameState) {
262         try {
263             mService.setGameState(mContext.getPackageName(), gameState, mContext.getUserId());
264         } catch (RemoteException e) {
265             throw e.rethrowFromSystemServer();
266         }
267     }
268 
269 
270     /**
271      * Sets the game service provider to the given package name for test only.
272      *
273      * <p>Passing in {@code null} will clear a previously set value.
274      * @hide
275      */
276     @TestApi
setGameServiceProvider(@ullable String packageName)277     public void setGameServiceProvider(@Nullable String packageName) {
278         try {
279             mService.setGameServiceProvider(packageName);
280         } catch (RemoteException e) {
281             throw e.rethrowFromSystemServer();
282         }
283     }
284 
285     /**
286      * Updates the config for the game's {@link #GAME_MODE_CUSTOM} mode.
287      * <p>
288      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
289      *
290      * @param packageName The package name of the game to update
291      * @param gameModeConfig The configuration to use for game mode interventions
292      * @hide
293      */
294     @SystemApi
295     @UserHandleAware
296     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
updateCustomGameModeConfiguration(@onNull String packageName, @NonNull GameModeConfiguration gameModeConfig)297     public void updateCustomGameModeConfiguration(@NonNull String packageName,
298             @NonNull GameModeConfiguration gameModeConfig) {
299         try {
300             mService.updateCustomGameModeConfiguration(packageName, gameModeConfig,
301                     mContext.getUserId());
302         } catch (RemoteException e) {
303             throw e.rethrowFromSystemServer();
304         }
305     }
306 }
307