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 static android.content.Intent.ACTION_PACKAGE_ADDED;
20 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
21 import static android.content.Intent.EXTRA_REPLACING;
22 
23 import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver;
24 import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling;
25 import static com.android.internal.R.styleable.GameModeConfig_allowGameFpsOverride;
26 import static com.android.internal.R.styleable.GameModeConfig_supportsBatteryGameMode;
27 import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanceGameMode;
28 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
29 
30 import android.Manifest;
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.annotation.RequiresPermission;
34 import android.annotation.UserIdInt;
35 import android.app.ActivityManager;
36 import android.app.GameManager;
37 import android.app.GameManager.GameMode;
38 import android.app.GameManagerInternal;
39 import android.app.GameModeConfiguration;
40 import android.app.GameModeInfo;
41 import android.app.GameState;
42 import android.app.IGameManagerService;
43 import android.app.IGameModeListener;
44 import android.app.IGameStateListener;
45 import android.app.StatsManager;
46 import android.app.UidObserver;
47 import android.content.BroadcastReceiver;
48 import android.content.Context;
49 import android.content.Intent;
50 import android.content.IntentFilter;
51 import android.content.pm.ApplicationInfo;
52 import android.content.pm.PackageInfo;
53 import android.content.pm.PackageManager;
54 import android.content.pm.PackageManager.NameNotFoundException;
55 import android.content.pm.UserInfo;
56 import android.content.res.Resources;
57 import android.content.res.TypedArray;
58 import android.content.res.XmlResourceParser;
59 import android.hardware.power.Mode;
60 import android.net.Uri;
61 import android.os.Binder;
62 import android.os.Bundle;
63 import android.os.Environment;
64 import android.os.FileUtils;
65 import android.os.Handler;
66 import android.os.IBinder;
67 import android.os.Looper;
68 import android.os.Message;
69 import android.os.PowerManagerInternal;
70 import android.os.Process;
71 import android.os.RemoteException;
72 import android.os.ResultReceiver;
73 import android.os.ShellCallback;
74 import android.os.UserManager;
75 import android.provider.DeviceConfig;
76 import android.provider.DeviceConfig.Properties;
77 import android.text.TextUtils;
78 import android.util.ArrayMap;
79 import android.util.AtomicFile;
80 import android.util.AttributeSet;
81 import android.util.KeyValueListParser;
82 import android.util.Slog;
83 import android.util.StatsEvent;
84 import android.util.Xml;
85 
86 import com.android.internal.annotations.GuardedBy;
87 import com.android.internal.annotations.VisibleForTesting;
88 import com.android.internal.os.BackgroundThread;
89 import com.android.internal.util.ArrayUtils;
90 import com.android.internal.util.FrameworkStatsLog;
91 import com.android.server.LocalServices;
92 import com.android.server.ServiceThread;
93 import com.android.server.SystemService;
94 import com.android.server.SystemService.TargetUser;
95 
96 import org.xmlpull.v1.XmlPullParser;
97 import org.xmlpull.v1.XmlPullParserException;
98 
99 import java.io.BufferedWriter;
100 import java.io.File;
101 import java.io.FileDescriptor;
102 import java.io.FileOutputStream;
103 import java.io.IOException;
104 import java.io.OutputStreamWriter;
105 import java.io.PrintWriter;
106 import java.nio.charset.Charset;
107 import java.util.ArrayList;
108 import java.util.Arrays;
109 import java.util.HashSet;
110 import java.util.List;
111 import java.util.Map;
112 import java.util.Set;
113 
114 /**
115  * Service to manage game related features.
116  *
117  * <p>Game service is a core service that monitors, coordinates game related features,
118  * as well as collect metrics.</p>
119  *
120  * @hide
121  */
122 public final class GameManagerService extends IGameManagerService.Stub {
123     public static final String TAG = "GameManagerService";
124     // event strings used for logging
125     private static final String EVENT_SET_GAME_MODE = "SET_GAME_MODE";
126     private static final String EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG =
127             "UPDATE_CUSTOM_GAME_MODE_CONFIG";
128     private static final String EVENT_RECEIVE_SHUTDOWN_INDENT = "RECEIVE_SHUTDOWN_INDENT";
129     private static final String EVENT_ON_USER_STARTING = "ON_USER_STARTING";
130     private static final String EVENT_ON_USER_SWITCHING = "ON_USER_SWITCHING";
131     private static final String EVENT_ON_USER_STOPPING = "ON_USER_STOPPING";
132 
133     private static final boolean DEBUG = false;
134 
135     static final int WRITE_SETTINGS = 1;
136     static final int REMOVE_SETTINGS = 2;
137     static final int POPULATE_GAME_MODE_SETTINGS = 3;
138     static final int SET_GAME_STATE = 4;
139     static final int CANCEL_GAME_LOADING_MODE = 5;
140     static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6;
141     static final int WRITE_DELAY_MILLIS = 10 * 1000;  // 10 seconds
142     static final int LOADING_BOOST_MAX_DURATION = 5 * 1000;  // 5 seconds
143 
144     private static final String PACKAGE_NAME_MSG_KEY = "packageName";
145     private static final String USER_ID_MSG_KEY = "userId";
146     private static final String GAME_MODE_INTERVENTION_LIST_FILE_NAME =
147             "game_mode_intervention.list";
148 
149     private final Context mContext;
150     private final Object mLock = new Object();
151     private final Object mDeviceConfigLock = new Object();
152     private final Object mGameModeListenerLock = new Object();
153     private final Object mGameStateListenerLock = new Object();
154     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
155     final Handler mHandler;
156     private final PackageManager mPackageManager;
157     private final UserManager mUserManager;
158     private final PowerManagerInternal mPowerManagerInternal;
159     private final File mSystemDir;
160     @VisibleForTesting
161     final AtomicFile mGameModeInterventionListFile;
162     private DeviceConfigListener mDeviceConfigListener;
163     @GuardedBy("mLock")
164     private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
165     @GuardedBy("mDeviceConfigLock")
166     private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
167     // listener to caller uid map
168     @GuardedBy("mGameModeListenerLock")
169     private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
170     @GuardedBy("mGameStateListenerLock")
171     private final ArrayMap<IGameStateListener, Integer> mGameStateListeners = new ArrayMap<>();
172     @Nullable
173     private final GameServiceController mGameServiceController;
174     private final Object mUidObserverLock = new Object();
175     @VisibleForTesting
176     @Nullable
177     final MyUidObserver mUidObserver;
178     @GuardedBy("mUidObserverLock")
179     private final Set<Integer> mForegroundGameUids = new HashSet<>();
180 
GameManagerService(Context context)181     public GameManagerService(Context context) {
182         this(context, createServiceThread().getLooper());
183     }
184 
GameManagerService(Context context, Looper looper)185     GameManagerService(Context context, Looper looper) {
186         this(context, looper, Environment.getDataDirectory());
187     }
188 
189     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
GameManagerService(Context context, Looper looper, File dataDir)190     GameManagerService(Context context, Looper looper, File dataDir) {
191         mContext = context;
192         mHandler = new SettingsHandler(looper);
193         mPackageManager = mContext.getPackageManager();
194         mUserManager = mContext.getSystemService(UserManager.class);
195         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
196         mSystemDir = new File(dataDir, "system");
197         mSystemDir.mkdirs();
198         FileUtils.setPermissions(mSystemDir.toString(),
199                 FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
200                 -1, -1);
201         mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
202                 GAME_MODE_INTERVENTION_LIST_FILE_NAME));
203         FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
204                 FileUtils.S_IRUSR | FileUtils.S_IWUSR
205                         | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
206                 -1, -1);
207         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
208             mGameServiceController = new GameServiceController(
209                     context, BackgroundThread.getExecutor(),
210                     new GameServiceProviderSelectorImpl(
211                             context.getResources(),
212                             context.getPackageManager()),
213                     new GameServiceProviderInstanceFactoryImpl(context));
214         } else {
215             mGameServiceController = null;
216         }
217         mUidObserver = new MyUidObserver();
218         try {
219             ActivityManager.getService().registerUidObserver(mUidObserver,
220                     ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
221                     ActivityManager.PROCESS_STATE_UNKNOWN, null);
222         } catch (RemoteException e) {
223             Slog.w(TAG, "Could not register UidObserver");
224         }
225     }
226 
227     @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result)228     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
229             String[] args, ShellCallback callback, ResultReceiver result) {
230         new GameManagerShellCommand().exec(this, in, out, err, args, callback, result);
231     }
232 
233     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)234     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
235         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
236                 != PackageManager.PERMISSION_GRANTED) {
237             writer.println("Permission Denial: can't dump GameManagerService from from pid="
238                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
239                     + " without permission " + android.Manifest.permission.DUMP);
240             return;
241         }
242         if (args == null || args.length == 0) {
243             writer.println("*Dump GameManagerService*");
244             dumpAllGameConfigs(writer);
245         }
246     }
247 
dumpAllGameConfigs(PrintWriter pw)248     private void dumpAllGameConfigs(PrintWriter pw) {
249         final int userId = ActivityManager.getCurrentUser();
250         String[] packageList = getInstalledGamePackageNames(userId);
251         for (final String packageName : packageList) {
252             pw.println(getInterventionList(packageName, userId));
253         }
254     }
255 
256     class SettingsHandler extends Handler {
257 
SettingsHandler(Looper looper)258         SettingsHandler(Looper looper) {
259             super(looper);
260         }
261 
262         @Override
handleMessage(Message msg)263         public void handleMessage(Message msg) {
264             doHandleMessage(msg);
265         }
266 
doHandleMessage(Message msg)267         void doHandleMessage(Message msg) {
268             switch (msg.what) {
269                 case WRITE_SETTINGS: {
270                     final int userId = (int) msg.obj;
271                     if (userId < 0) {
272                         Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
273                         synchronized (mLock) {
274                             removeEqualMessages(WRITE_SETTINGS, msg.obj);
275                         }
276                         break;
277                     }
278                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
279                     synchronized (mLock) {
280                         removeEqualMessages(WRITE_SETTINGS, msg.obj);
281                         if (mSettings.containsKey(userId)) {
282                             GameManagerSettings userSettings = mSettings.get(userId);
283                             userSettings.writePersistentDataLocked();
284                         }
285                     }
286                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
287                     break;
288                 }
289                 case REMOVE_SETTINGS: {
290                     final int userId = (int) msg.obj;
291                     if (userId < 0) {
292                         Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
293                         synchronized (mLock) {
294                             removeEqualMessages(WRITE_SETTINGS, msg.obj);
295                             removeEqualMessages(REMOVE_SETTINGS, msg.obj);
296                         }
297                         break;
298                     }
299 
300                     synchronized (mLock) {
301                         // Since the user was removed, ignore previous write message
302                         // and do write here.
303                         removeEqualMessages(WRITE_SETTINGS, msg.obj);
304                         removeEqualMessages(REMOVE_SETTINGS, msg.obj);
305                         if (mSettings.containsKey(userId)) {
306                             final GameManagerSettings userSettings = mSettings.get(userId);
307                             mSettings.remove(userId);
308                             userSettings.writePersistentDataLocked();
309                         }
310                     }
311                     break;
312                 }
313                 case POPULATE_GAME_MODE_SETTINGS: {
314                     removeEqualMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
315                     final int userId = (int) msg.obj;
316                     final String[] packageNames = getInstalledGamePackageNames(userId);
317                     updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
318                     break;
319                 }
320                 case SET_GAME_STATE: {
321                     final GameState gameState = (GameState) msg.obj;
322                     final boolean isLoading = gameState.isLoading();
323                     final Bundle data = msg.getData();
324                     final String packageName = data.getString(PACKAGE_NAME_MSG_KEY);
325                     final int userId = data.getInt(USER_ID_MSG_KEY);
326 
327                     // Restrict to games only. Requires performance mode to be enabled.
328                     final boolean boostEnabled =
329                             getGameMode(packageName, userId) == GameManager.GAME_MODE_PERFORMANCE;
330                     int uid;
331                     try {
332                         uid = mPackageManager.getPackageUidAsUser(packageName, userId);
333                     } catch (NameNotFoundException e) {
334                         Slog.v(TAG, "Failed to get package metadata");
335                         uid = -1;
336                     }
337                     FrameworkStatsLog.write(FrameworkStatsLog.GAME_STATE_CHANGED, packageName, uid,
338                             boostEnabled, gameStateModeToStatsdGameState(gameState.getMode()),
339                             isLoading, gameState.getLabel(), gameState.getQuality());
340 
341                     if (boostEnabled) {
342                         if (mPowerManagerInternal == null) {
343                             Slog.d(TAG, "Error setting loading mode for package " + packageName
344                                     + " and userId " + userId);
345                             break;
346                         }
347                         if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
348                             mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
349                         }
350                         Slog.v(TAG, String.format(
351                                 "Game loading power mode %s (game state change isLoading=%b)",
352                                         isLoading ? "ON" : "OFF", isLoading));
353                         mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
354                         if (isLoading) {
355                             int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
356                             loadingBoostDuration = loadingBoostDuration > 0 ? loadingBoostDuration
357                                     : LOADING_BOOST_MAX_DURATION;
358                             mHandler.sendMessageDelayed(
359                                     mHandler.obtainMessage(CANCEL_GAME_LOADING_MODE),
360                                     loadingBoostDuration);
361                         }
362                     }
363                     synchronized (mGameStateListenerLock) {
364                         for (IGameStateListener listener : mGameStateListeners.keySet()) {
365                             try {
366                                 listener.onGameStateChanged(packageName, gameState, userId);
367                             } catch (RemoteException ex) {
368                                 Slog.w(TAG, "Cannot notify game state change for listener added by "
369                                         + mGameStateListeners.get(listener));
370                             }
371                         }
372                     }
373                     break;
374                 }
375                 case CANCEL_GAME_LOADING_MODE: {
376                     Slog.v(TAG, "Game loading power mode OFF (loading boost ended)");
377                     mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
378                     break;
379                 }
380                 case WRITE_GAME_MODE_INTERVENTION_LIST_FILE: {
381                     final int userId = (int) msg.obj;
382                     if (userId < 0) {
383                         Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId);
384                         synchronized (mLock) {
385                             removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
386                         }
387                         break;
388                     }
389 
390                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
391                     removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
392                     writeGameModeInterventionsToFile(userId);
393                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
394                     break;
395                 }
396             }
397         }
398     }
399 
400     private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
401 
DeviceConfigListener()402         DeviceConfigListener() {
403             super();
404             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_GAME_OVERLAY,
405                     mContext.getMainExecutor(), this);
406         }
407 
408         @Override
onPropertiesChanged(Properties properties)409         public void onPropertiesChanged(Properties properties) {
410             final String[] packageNames = properties.getKeyset().toArray(new String[0]);
411             updateConfigsForUser(ActivityManager.getCurrentUser(), true /*checkGamePackage*/,
412                     packageNames);
413         }
414 
415         @Override
finalize()416         public void finalize() {
417             DeviceConfig.removeOnPropertiesChangedListener(this);
418         }
419     }
420 
421     /**
422      * Called by games to communicate the current state to the platform.
423      *
424      * @param packageName The client package name.
425      * @param gameState   An object set to the current state.
426      * @param userId      The user associated with this state.
427      */
setGameState(String packageName, @NonNull GameState gameState, @UserIdInt int userId)428     public void setGameState(String packageName, @NonNull GameState gameState,
429             @UserIdInt int userId) {
430         if (!isPackageGame(packageName, userId)) {
431             Slog.d(TAG, "No-op for attempt to set game state for non-game app: " + packageName);
432             // Restrict to games only.
433             return;
434         }
435         final Message msg = mHandler.obtainMessage(SET_GAME_STATE);
436         final Bundle data = new Bundle();
437         data.putString(PACKAGE_NAME_MSG_KEY, packageName);
438         data.putInt(USER_ID_MSG_KEY, userId);
439         msg.setData(data);
440         msg.obj = gameState;
441         mHandler.sendMessage(msg);
442     }
443 
444     /**
445      * GamePackageConfiguration manages all game mode config details for its associated package.
446      */
447     public static class GamePackageConfiguration {
448         public static final String TAG = "GameManagerService_GamePackageConfiguration";
449 
450         /**
451          * Metadata that can be included in the app manifest to allow/disallow any window manager
452          * downscaling interventions. Default value is TRUE.
453          */
454         public static final String METADATA_WM_ALLOW_DOWNSCALE =
455                 "com.android.graphics.intervention.wm.allowDownscale";
456 
457         /**
458          * Metadata that can be included in the app manifest to allow/disallow any ANGLE
459          * interventions. Default value is TRUE.
460          */
461         public static final String METADATA_ANGLE_ALLOW_ANGLE =
462                 "com.android.graphics.intervention.angle.allowAngle";
463 
464         /**
465          * Metadata that needs to be included in the app manifest to OPT-IN to PERFORMANCE mode.
466          * This means the app will assume full responsibility for the experience provided by this
467          * mode and the system will enable no window manager downscaling.
468          * Default value is FALSE
469          */
470         public static final String METADATA_PERFORMANCE_MODE_ENABLE =
471                 "com.android.app.gamemode.performance.enabled";
472 
473         /**
474          * Metadata that needs to be included in the app manifest to OPT-IN to BATTERY mode.
475          * This means the app will assume full responsibility for the experience provided by this
476          * mode and the system will enable no window manager downscaling.
477          * Default value is FALSE
478          */
479         public static final String METADATA_BATTERY_MODE_ENABLE =
480                 "com.android.app.gamemode.battery.enabled";
481 
482         /**
483          * Metadata that allows a game to specify all intervention information with an XML file in
484          * the application field.
485          */
486         public static final String METADATA_GAME_MODE_CONFIG = "android.game_mode_config";
487 
488         private static final String GAME_MODE_CONFIG_NODE_NAME = "game-mode-config";
489         private final String mPackageName;
490         private final Object mModeConfigLock = new Object();
491         @GuardedBy("mModeConfigLock")
492         private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs = new ArrayMap<>();
493         // if adding new properties or make any of the below overridable, the method
494         // copyAndApplyOverride should be updated accordingly
495         private boolean mPerfModeOverridden = false;
496         private boolean mBatteryModeOverridden = false;
497         private boolean mAllowDownscale = true;
498         private boolean mAllowAngle = true;
499         private boolean mAllowFpsOverride = true;
500 
GamePackageConfiguration(String packageName)501         GamePackageConfiguration(String packageName) {
502             mPackageName = packageName;
503         }
504 
GamePackageConfiguration(PackageManager packageManager, String packageName, int userId)505         GamePackageConfiguration(PackageManager packageManager, String packageName, int userId) {
506             mPackageName = packageName;
507 
508             try {
509                 final ApplicationInfo ai = packageManager.getApplicationInfoAsUser(packageName,
510                         PackageManager.GET_META_DATA, userId);
511                 if (!parseInterventionFromXml(packageManager, ai, packageName)
512                             && ai.metaData != null) {
513                     mPerfModeOverridden = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
514                     mBatteryModeOverridden = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
515                     mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
516                     mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
517                 }
518             } catch (NameNotFoundException e) {
519                 // Not all packages are installed, hence ignore those that are not installed yet.
520                 Slog.v(TAG, "Failed to get package metadata");
521             }
522             final String configString = DeviceConfig.getProperty(
523                     DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName);
524             if (configString != null) {
525                 final String[] gameModeConfigStrings = configString.split(":");
526                 for (String gameModeConfigString : gameModeConfigStrings) {
527                     try {
528                         final KeyValueListParser parser = new KeyValueListParser(',');
529                         parser.setString(gameModeConfigString);
530                         addModeConfig(new GameModeConfiguration(parser));
531                     } catch (IllegalArgumentException e) {
532                         Slog.e(TAG, "Invalid config string");
533                     }
534                 }
535             }
536         }
537 
parseInterventionFromXml(PackageManager packageManager, ApplicationInfo ai, String packageName)538         private boolean parseInterventionFromXml(PackageManager packageManager, ApplicationInfo ai,
539                 String packageName) {
540             boolean xmlFound = false;
541             try (XmlResourceParser parser = ai.loadXmlMetaData(packageManager,
542                     METADATA_GAME_MODE_CONFIG)) {
543                 if (parser == null) {
544                     Slog.v(TAG, "No " + METADATA_GAME_MODE_CONFIG
545                             + " meta-data found for package " + mPackageName);
546                 } else {
547                     xmlFound = true;
548                     final Resources resources = packageManager.getResourcesForApplication(
549                             packageName);
550                     final AttributeSet attributeSet = Xml.asAttributeSet(parser);
551                     int type;
552                     while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
553                             && type != XmlPullParser.START_TAG) {
554                         // Do nothing
555                     }
556 
557                     boolean isStartingTagGameModeConfig =
558                             GAME_MODE_CONFIG_NODE_NAME.equals(parser.getName());
559                     if (!isStartingTagGameModeConfig) {
560                         Slog.w(TAG, "Meta-data does not start with "
561                                 + GAME_MODE_CONFIG_NODE_NAME
562                                 + " tag");
563                     } else {
564                         final TypedArray array = resources.obtainAttributes(attributeSet,
565                                 com.android.internal.R.styleable.GameModeConfig);
566                         mPerfModeOverridden = array.getBoolean(
567                                 GameModeConfig_supportsPerformanceGameMode, false);
568                         mBatteryModeOverridden = array.getBoolean(
569                                 GameModeConfig_supportsBatteryGameMode,
570                                 false);
571                         mAllowDownscale = array.getBoolean(GameModeConfig_allowGameDownscaling,
572                                 true);
573                         mAllowAngle = array.getBoolean(GameModeConfig_allowGameAngleDriver, true);
574                         mAllowFpsOverride = array.getBoolean(GameModeConfig_allowGameFpsOverride,
575                                 true);
576                         array.recycle();
577                     }
578                 }
579             } catch (NameNotFoundException | XmlPullParserException | IOException ex) {
580                 // set flag back to default values when parsing fails
581                 mPerfModeOverridden = false;
582                 mBatteryModeOverridden = false;
583                 mAllowDownscale = true;
584                 mAllowAngle = true;
585                 mAllowFpsOverride = true;
586                 Slog.e(TAG, "Error while parsing XML meta-data for "
587                         + METADATA_GAME_MODE_CONFIG);
588             }
589             return xmlFound;
590         }
591 
getOrAddDefaultGameModeConfiguration(int gameMode)592         GameModeConfiguration getOrAddDefaultGameModeConfiguration(int gameMode) {
593             synchronized (mModeConfigLock) {
594                 mModeConfigs.putIfAbsent(gameMode, new GameModeConfiguration(gameMode));
595                 return mModeConfigs.get(gameMode);
596             }
597         }
598 
599         // used to check if the override package config has any game mode config, if not, it's
600         // considered empty and safe to delete from settings
hasActiveGameModeConfig()601         boolean hasActiveGameModeConfig() {
602             synchronized (mModeConfigLock) {
603                 return !mModeConfigs.isEmpty();
604             }
605         }
606 
607         /**
608          * GameModeConfiguration contains all the values for all the interventions associated with
609          * a game mode.
610          */
611         public class GameModeConfiguration {
612             public static final String TAG = "GameManagerService_GameModeConfiguration";
613             public static final String MODE_KEY = "mode";
614             public static final String SCALING_KEY = "downscaleFactor";
615             public static final String FPS_KEY = "fps";
616             public static final String ANGLE_KEY = "useAngle";
617             public static final String LOADING_BOOST_KEY = "loadingBoost";
618 
619             public static final float DEFAULT_SCALING = -1f;
620             public static final String DEFAULT_FPS = "";
621             public static final boolean DEFAULT_USE_ANGLE = false;
622             public static final int DEFAULT_LOADING_BOOST_DURATION = -1;
623 
624             private final @GameMode int mGameMode;
625             private float mScaling = DEFAULT_SCALING;
626             private String mFps = DEFAULT_FPS;
627             private boolean mUseAngle;
628             private int mLoadingBoostDuration;
629 
GameModeConfiguration(int gameMode)630             GameModeConfiguration(int gameMode) {
631                 mGameMode = gameMode;
632                 mUseAngle = DEFAULT_USE_ANGLE;
633                 mLoadingBoostDuration = DEFAULT_LOADING_BOOST_DURATION;
634             }
635 
GameModeConfiguration(KeyValueListParser parser)636             GameModeConfiguration(KeyValueListParser parser) {
637                 mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
638                 // willGamePerformOptimizations() returns if an app will handle all of the changes
639                 // necessary for a particular game mode. If so, the Android framework (i.e.
640                 // GameManagerService) will not do anything for the app (like window scaling or
641                 // using ANGLE).
642                 mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
643                         ? DEFAULT_SCALING : parser.getFloat(SCALING_KEY, DEFAULT_SCALING);
644 
645                 mFps = mAllowFpsOverride && !willGamePerformOptimizations(mGameMode)
646                         ? parser.getString(FPS_KEY, DEFAULT_FPS) : DEFAULT_FPS;
647                 // We only want to use ANGLE if:
648                 // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
649                 // - The app has not opted in to performing the work itself AND
650                 // - The Phenotype config has enabled it.
651                 mUseAngle = mAllowAngle && !willGamePerformOptimizations(mGameMode)
652                         && parser.getBoolean(ANGLE_KEY, DEFAULT_USE_ANGLE);
653 
654                 mLoadingBoostDuration = willGamePerformOptimizations(mGameMode)
655                         ? DEFAULT_LOADING_BOOST_DURATION
656                         : parser.getInt(LOADING_BOOST_KEY, DEFAULT_LOADING_BOOST_DURATION);
657             }
658 
getGameMode()659             public int getGameMode() {
660                 return mGameMode;
661             }
662 
getScaling()663             public synchronized float getScaling() {
664                 return mScaling;
665             }
666 
getFps()667             public synchronized int getFps() {
668                 try {
669                     final int fpsInt = Integer.parseInt(mFps);
670                     return fpsInt;
671                 } catch (NumberFormatException e) {
672                     return 0;
673                 }
674             }
675 
getFpsStr()676             synchronized String getFpsStr() {
677                 return mFps;
678             }
679 
getUseAngle()680             public synchronized boolean getUseAngle() {
681                 return mUseAngle;
682             }
683 
getLoadingBoostDuration()684             public synchronized int getLoadingBoostDuration() {
685                 return mLoadingBoostDuration;
686             }
687 
setScaling(float scaling)688             public synchronized void setScaling(float scaling) {
689                 mScaling = scaling;
690             }
691 
setFpsStr(String fpsStr)692             public synchronized void setFpsStr(String fpsStr) {
693                 mFps = fpsStr;
694             }
695 
setUseAngle(boolean useAngle)696             public synchronized void setUseAngle(boolean useAngle) {
697                 mUseAngle = useAngle;
698             }
699 
setLoadingBoostDuration(int loadingBoostDuration)700             public synchronized void setLoadingBoostDuration(int loadingBoostDuration) {
701                 mLoadingBoostDuration = loadingBoostDuration;
702             }
703 
isActive()704             public boolean isActive() {
705                 return (mGameMode == GameManager.GAME_MODE_STANDARD
706                         || mGameMode == GameManager.GAME_MODE_PERFORMANCE
707                         || mGameMode == GameManager.GAME_MODE_BATTERY
708                         || mGameMode == GameManager.GAME_MODE_CUSTOM)
709                         && !willGamePerformOptimizations(mGameMode);
710             }
711 
toPublicGameModeConfig()712             android.app.GameModeConfiguration toPublicGameModeConfig() {
713                 int fpsOverride;
714                 try {
715                     fpsOverride = Integer.parseInt(mFps);
716                 } catch (NumberFormatException e) {
717                     fpsOverride = 0;
718                 }
719                 // TODO(b/243448953): match to proper value in case of display change?
720                 fpsOverride = fpsOverride > 0 ? fpsOverride
721                         : android.app.GameModeConfiguration.FPS_OVERRIDE_NONE;
722                 final float scaling = mScaling == DEFAULT_SCALING ? 1.0f : mScaling;
723                 return new android.app.GameModeConfiguration.Builder()
724                         .setScalingFactor(scaling)
725                         .setFpsOverride(fpsOverride).build();
726             }
727 
updateFromPublicGameModeConfig(android.app.GameModeConfiguration config)728             void updateFromPublicGameModeConfig(android.app.GameModeConfiguration config) {
729                 mScaling = config.getScalingFactor();
730                 mFps = String.valueOf(config.getFpsOverride());
731             }
732 
733             /**
734              * @hide
735              */
toString()736             public String toString() {
737                 return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:"
738                         + mUseAngle + ",Fps:" + mFps + ",Loading Boost Duration:"
739                         + mLoadingBoostDuration + "]";
740             }
741         }
742 
getPackageName()743         public String getPackageName() {
744             return mPackageName;
745         }
746 
747         /**
748          * Returns if the app will assume full responsibility for the experience provided by this
749          * mode. If True, the system will not perform any interventions for the app.
750          *
751          * @return True if the app package has specified in its metadata either:
752          * "com.android.app.gamemode.performance.enabled" or
753          * "com.android.app.gamemode.battery.enabled" with a value of "true"
754          */
willGamePerformOptimizations(@ameMode int gameMode)755         public boolean willGamePerformOptimizations(@GameMode int gameMode) {
756             return (mBatteryModeOverridden && gameMode == GameManager.GAME_MODE_BATTERY)
757                     || (mPerfModeOverridden && gameMode == GameManager.GAME_MODE_PERFORMANCE);
758         }
759 
getAvailableGameModesBitfield()760         private int getAvailableGameModesBitfield() {
761             int field = modeToBitmask(GameManager.GAME_MODE_CUSTOM)
762                     | modeToBitmask(GameManager.GAME_MODE_STANDARD);
763             synchronized (mModeConfigLock) {
764                 for (final int mode : mModeConfigs.keySet()) {
765                     field |= modeToBitmask(mode);
766                 }
767             }
768             if (mBatteryModeOverridden) {
769                 field |= modeToBitmask(GameManager.GAME_MODE_BATTERY);
770             }
771             if (mPerfModeOverridden) {
772                 field |= modeToBitmask(GameManager.GAME_MODE_PERFORMANCE);
773             }
774             return field;
775         }
776 
777         /**
778          * Get an array of a package's available game modes.
779          */
getAvailableGameModes()780         public @GameMode int[] getAvailableGameModes() {
781             final int modesBitfield = getAvailableGameModesBitfield();
782             int[] modes = new int[Integer.bitCount(modesBitfield)];
783             int i = 0;
784             final int gameModeInHighestBit =
785                     Integer.numberOfTrailingZeros(Integer.highestOneBit(modesBitfield));
786             for (int mode = 0; mode <= gameModeInHighestBit; ++mode) {
787                 if (((modesBitfield >> mode) & 1) != 0) {
788                     modes[i++] = mode;
789                 }
790             }
791             return modes;
792         }
793 
794         /**
795          * Get an array of a package's overridden game modes.
796          */
getOverriddenGameModes()797         public @GameMode int[] getOverriddenGameModes() {
798             if (mBatteryModeOverridden && mPerfModeOverridden) {
799                 return new int[]{GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE};
800             } else if (mBatteryModeOverridden) {
801                 return new int[]{GameManager.GAME_MODE_BATTERY};
802             } else if (mPerfModeOverridden) {
803                 return new int[]{GameManager.GAME_MODE_PERFORMANCE};
804             } else {
805                 return new int[]{};
806             }
807         }
808 
809         /**
810          * Get a GameModeConfiguration for a given game mode.
811          *
812          * @return The package's GameModeConfiguration for the provided mode or null if absent
813          */
getGameModeConfiguration(@ameMode int gameMode)814         public GameModeConfiguration getGameModeConfiguration(@GameMode int gameMode) {
815             synchronized (mModeConfigLock) {
816                 return mModeConfigs.get(gameMode);
817             }
818         }
819 
820         /**
821          * Inserts a new GameModeConfiguration.
822          */
addModeConfig(GameModeConfiguration config)823         public void addModeConfig(GameModeConfiguration config) {
824             if (config.isActive()) {
825                 synchronized (mModeConfigLock) {
826                     mModeConfigs.put(config.getGameMode(), config);
827                 }
828             } else {
829                 Slog.w(TAG, "Attempt to add inactive game mode config for "
830                         + mPackageName + ":" + config.toString());
831             }
832         }
833 
834         /**
835          * Removes the GameModeConfiguration.
836          */
removeModeConfig(int mode)837         public void removeModeConfig(int mode) {
838             synchronized (mModeConfigLock) {
839                 mModeConfigs.remove(mode);
840             }
841         }
842 
isActive()843         public boolean isActive() {
844             synchronized (mModeConfigLock) {
845                 return mModeConfigs.size() > 0 || mBatteryModeOverridden || mPerfModeOverridden;
846             }
847         }
848 
copyAndApplyOverride(GamePackageConfiguration overrideConfig)849         GamePackageConfiguration copyAndApplyOverride(GamePackageConfiguration overrideConfig) {
850             GamePackageConfiguration copy = new GamePackageConfiguration(mPackageName);
851             // if a game mode is overridden, we treat it with the highest priority and reset any
852             // overridden game modes so that interventions are always executed.
853             copy.mPerfModeOverridden = mPerfModeOverridden && !(overrideConfig != null
854                     && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)
855                     != null);
856             copy.mBatteryModeOverridden = mBatteryModeOverridden && !(overrideConfig != null
857                     && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY)
858                     != null);
859 
860             // if any game mode is overridden, we will consider all interventions forced-active,
861             // this can be done more granular by checking if a specific intervention is
862             // overridden under each game mode override, but only if necessary.
863             copy.mAllowDownscale = mAllowDownscale || overrideConfig != null;
864             copy.mAllowAngle = mAllowAngle || overrideConfig != null;
865             copy.mAllowFpsOverride = mAllowFpsOverride || overrideConfig != null;
866             if (overrideConfig != null) {
867                 synchronized (copy.mModeConfigLock) {
868                     synchronized (mModeConfigLock) {
869                         for (Map.Entry<Integer, GameModeConfiguration> entry :
870                                 mModeConfigs.entrySet()) {
871                             copy.mModeConfigs.put(entry.getKey(), entry.getValue());
872                         }
873                     }
874                     synchronized (overrideConfig.mModeConfigLock) {
875                         for (Map.Entry<Integer, GameModeConfiguration> entry :
876                                 overrideConfig.mModeConfigs.entrySet()) {
877                             copy.mModeConfigs.put(entry.getKey(), entry.getValue());
878                         }
879                     }
880                 }
881             }
882             return copy;
883         }
884 
toString()885         public String toString() {
886             synchronized (mModeConfigLock) {
887                 return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]";
888             }
889         }
890     }
891 
892     private final class LocalService extends GameManagerInternal {
893         @Override
getResolutionScalingFactor(String packageName, int userId)894         public float getResolutionScalingFactor(String packageName, int userId) {
895             final int gameMode = getGameModeFromSettingsUnchecked(packageName, userId);
896             return getResolutionScalingFactorInternal(packageName, gameMode, userId);
897         }
898     }
899 
900     /**
901      * SystemService lifecycle for GameService.
902      *
903      * @hide
904      */
905     public static class Lifecycle extends SystemService {
906         private GameManagerService mService;
907 
Lifecycle(Context context)908         public Lifecycle(Context context) {
909             super(context);
910             mService = new GameManagerService(context);
911         }
912 
913         @Override
onStart()914         public void onStart() {
915             publishBinderService(Context.GAME_SERVICE, mService);
916             mService.publishLocalService();
917             mService.registerDeviceConfigListener();
918             mService.registerPackageReceiver();
919         }
920 
921         @Override
onBootPhase(int phase)922         public void onBootPhase(int phase) {
923             if (phase == PHASE_BOOT_COMPLETED) {
924                 mService.onBootCompleted();
925                 mService.registerStatsCallbacks();
926             }
927         }
928 
929         @Override
onUserStarting(@onNull TargetUser user)930         public void onUserStarting(@NonNull TargetUser user) {
931             Slog.d(TAG, "Starting user " + user.getUserIdentifier());
932             mService.onUserStarting(user,
933                     Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
934         }
935 
936         @Override
onUserUnlocking(@onNull TargetUser user)937         public void onUserUnlocking(@NonNull TargetUser user) {
938             mService.onUserUnlocking(user);
939         }
940 
941         @Override
onUserStopping(@onNull TargetUser user)942         public void onUserStopping(@NonNull TargetUser user) {
943             mService.onUserStopping(user);
944         }
945 
946         @Override
onUserSwitching(@ullable TargetUser from, @NonNull TargetUser to)947         public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
948             mService.onUserSwitching(from, to);
949         }
950     }
951 
isValidPackageName(String packageName, int userId)952     private boolean isValidPackageName(String packageName, int userId) {
953         try {
954             return mPackageManager.getPackageUidAsUser(packageName, userId)
955                     == Binder.getCallingUid();
956         } catch (NameNotFoundException e) {
957             return false;
958         }
959     }
960 
checkPermission(String permission)961     private void checkPermission(String permission) throws SecurityException {
962         if (mContext.checkCallingOrSelfPermission(permission)
963                 != PackageManager.PERMISSION_GRANTED) {
964             throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
965                     + ", must have permission " + permission);
966         }
967     }
968 
getAvailableGameModesUnchecked(String packageName, int userId)969     private @GameMode int[] getAvailableGameModesUnchecked(String packageName, int userId) {
970         final GamePackageConfiguration config = getConfig(packageName, userId);
971         if (config == null) {
972             return new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM};
973         }
974         return config.getAvailableGameModes();
975     }
976 
isPackageGame(String packageName, @UserIdInt int userId)977     private boolean isPackageGame(String packageName, @UserIdInt int userId) {
978         try {
979             final ApplicationInfo applicationInfo = mPackageManager
980                     .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
981             return applicationInfo.category == ApplicationInfo.CATEGORY_GAME;
982         } catch (PackageManager.NameNotFoundException e) {
983             return false;
984         }
985     }
986 
987     /**
988      * Get an array of game modes available for a given package.
989      * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
990      */
991     @Override
992     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
getAvailableGameModes(String packageName, int userId)993     public @GameMode int[] getAvailableGameModes(String packageName, int userId)
994             throws SecurityException {
995         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
996         if (!isPackageGame(packageName, userId)) {
997             return new int[]{};
998         }
999         return getAvailableGameModesUnchecked(packageName, userId);
1000     }
1001 
getGameModeFromSettingsUnchecked(String packageName, @UserIdInt int userId)1002     private @GameMode int getGameModeFromSettingsUnchecked(String packageName,
1003             @UserIdInt int userId) {
1004         synchronized (mLock) {
1005             if (!mSettings.containsKey(userId)) {
1006                 Slog.d(TAG, "User ID '" + userId + "' does not have a Game Mode"
1007                         + " selected for package: '" + packageName + "'");
1008                 return GameManager.GAME_MODE_STANDARD;
1009             }
1010 
1011             return mSettings.get(userId).getGameModeLocked(packageName);
1012         }
1013     }
1014 
1015     /**
1016      * Get the Game Mode for the package name.
1017      * Verifies that the calling process is for the matching package UID or has
1018      * {@link android.Manifest.permission#MANAGE_GAME_MODE}.
1019      */
1020     @Override
getGameMode(@onNull String packageName, @UserIdInt int userId)1021     public @GameMode int getGameMode(@NonNull String packageName, @UserIdInt int userId)
1022             throws SecurityException {
1023         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1024                 Binder.getCallingUid(), userId, false, true, "getGameMode",
1025                 "com.android.server.app.GameManagerService");
1026 
1027         // Restrict to games only.
1028         if (!isPackageGame(packageName, userId)) {
1029             // The game mode for applications that are not identified as game is always
1030             // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)}
1031             return GameManager.GAME_MODE_UNSUPPORTED;
1032         }
1033 
1034         // This function handles two types of queries:
1035         // 1) A normal, non-privileged app querying its own Game Mode.
1036         // 2) A privileged system service querying the Game Mode of another package.
1037         // The least privileged case is a normal app performing a query, so check that first and
1038         // return a value if the package name is valid. Next, check if the caller has the necessary
1039         // permission and return a value. Do this check last, since it can throw an exception.
1040         if (isValidPackageName(packageName, userId)) {
1041             return getGameModeFromSettingsUnchecked(packageName, userId);
1042         }
1043 
1044         // Since the package name doesn't match, check the caller has the necessary permission.
1045         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1046         return getGameModeFromSettingsUnchecked(packageName, userId);
1047     }
1048 
1049     /**
1050      * Get the GameModeInfo for the package name.
1051      * Verifies that the calling process is for the matching package UID or has
1052      * {@link android.Manifest.permission#MANAGE_GAME_MODE}. If the package is not a game,
1053      * null is always returned.
1054      */
1055     @Override
1056     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
1057     @Nullable
getGameModeInfo(@onNull String packageName, @UserIdInt int userId)1058     public GameModeInfo getGameModeInfo(@NonNull String packageName, @UserIdInt int userId) {
1059         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1060                 Binder.getCallingUid(), userId, false, true, "getGameModeInfo",
1061                 "com.android.server.app.GameManagerService");
1062 
1063         // Check the caller has the necessary permission.
1064         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1065 
1066         if (!isPackageGame(packageName, userId)) {
1067             return null;
1068         }
1069 
1070         final @GameMode int activeGameMode = getGameModeFromSettingsUnchecked(packageName, userId);
1071         final GamePackageConfiguration config = getConfig(packageName, userId);
1072         if (config != null) {
1073             final @GameMode int[] overriddenGameModes = config.getOverriddenGameModes();
1074             final @GameMode int[] availableGameModes = config.getAvailableGameModes();
1075             GameModeInfo.Builder gameModeInfoBuilder = new GameModeInfo.Builder()
1076                     .setActiveGameMode(activeGameMode)
1077                     .setAvailableGameModes(availableGameModes)
1078                     .setOverriddenGameModes(overriddenGameModes)
1079                     .setDownscalingAllowed(config.mAllowDownscale)
1080                     .setFpsOverrideAllowed(config.mAllowFpsOverride);
1081             for (int gameMode : availableGameModes) {
1082                 if (!config.willGamePerformOptimizations(gameMode)) {
1083                     GamePackageConfiguration.GameModeConfiguration gameModeConfig =
1084                             config.getGameModeConfiguration(gameMode);
1085                     if (gameModeConfig != null) {
1086                         gameModeInfoBuilder.setGameModeConfiguration(gameMode,
1087                                 gameModeConfig.toPublicGameModeConfig());
1088                     }
1089                 }
1090             }
1091             return gameModeInfoBuilder.build();
1092         } else {
1093             return new GameModeInfo.Builder()
1094                     .setActiveGameMode(activeGameMode)
1095                     .setAvailableGameModes(getAvailableGameModesUnchecked(packageName, userId))
1096                     .build();
1097         }
1098     }
1099 
1100     /**
1101      * Sets the Game Mode for the package name.
1102      * Verifies that the calling process has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
1103      */
1104     @Override
1105     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
setGameMode(String packageName, @GameMode int gameMode, int userId)1106     public void setGameMode(String packageName, @GameMode int gameMode, int userId)
1107             throws SecurityException {
1108         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1109         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
1110             Slog.d(TAG, "No-op for attempt to set UNSUPPORTED mode for app: " + packageName);
1111             return;
1112         } else if (!isPackageGame(packageName, userId)) {
1113             Slog.d(TAG, "No-op for attempt to set game mode for non-game app: " + packageName);
1114             return;
1115         }
1116         int fromGameMode;
1117         synchronized (mLock) {
1118             userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1119                     Binder.getCallingUid(), userId, false, true, "setGameMode",
1120                     "com.android.server.app.GameManagerService");
1121 
1122             if (!mSettings.containsKey(userId)) {
1123                 Slog.d(TAG, "Failed to set game mode for package " + packageName
1124                         + " as user " + userId + " is not started");
1125                 return;
1126             }
1127             GameManagerSettings userSettings = mSettings.get(userId);
1128             fromGameMode = userSettings.getGameModeLocked(packageName);
1129             userSettings.setGameModeLocked(packageName, gameMode);
1130         }
1131         updateInterventions(packageName, gameMode, userId);
1132         synchronized (mGameModeListenerLock) {
1133             for (IGameModeListener listener : mGameModeListeners.keySet()) {
1134                 Binder.allowBlocking(listener.asBinder());
1135                 try {
1136                     listener.onGameModeChanged(packageName, fromGameMode, gameMode, userId);
1137                 } catch (RemoteException ex) {
1138                     Slog.w(TAG, "Cannot notify game mode change for listener added by "
1139                             + mGameModeListeners.get(listener));
1140                 }
1141             }
1142         }
1143         sendUserMessage(userId, WRITE_SETTINGS, EVENT_SET_GAME_MODE, WRITE_DELAY_MILLIS);
1144         sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
1145                 EVENT_SET_GAME_MODE, 0 /*delayMillis*/);
1146         int gameUid = -1;
1147         try {
1148             gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
1149         } catch (NameNotFoundException ex) {
1150             Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
1151         }
1152         FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CHANGED, gameUid,
1153                 Binder.getCallingUid(), gameModeToStatsdGameMode(fromGameMode),
1154                 gameModeToStatsdGameMode(gameMode));
1155     }
1156 
1157     /**
1158      * Get if ANGLE is enabled for the package for the currently enabled game mode.
1159      * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
1160      */
1161     @Override
1162     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
isAngleEnabled(String packageName, int userId)1163     public @GameMode boolean isAngleEnabled(String packageName, int userId)
1164             throws SecurityException {
1165         final int gameMode = getGameMode(packageName, userId);
1166         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
1167             return false;
1168         }
1169         final GamePackageConfiguration config;
1170         synchronized (mDeviceConfigLock) {
1171             config = mConfigs.get(packageName);
1172             if (config == null) {
1173                 return false;
1174             }
1175         }
1176         GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
1177                 config.getGameModeConfiguration(gameMode);
1178         if (gameModeConfiguration == null) {
1179             return false;
1180         }
1181         return gameModeConfiguration.getUseAngle();
1182     }
1183 
1184     /**
1185      * If loading boost is applicable for the package for the currently enabled game mode, return
1186      * the boost duration. If no configuration is available for the selected package or mode, the
1187      * default is returned.
1188      */
getLoadingBoostDuration(String packageName, int userId)1189     public int getLoadingBoostDuration(String packageName, int userId)
1190             throws SecurityException {
1191         final int gameMode = getGameMode(packageName, userId);
1192         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
1193             return -1;
1194         }
1195         final GamePackageConfiguration config;
1196         synchronized (mDeviceConfigLock) {
1197             config = mConfigs.get(packageName);
1198         }
1199         if (config == null) {
1200             return -1;
1201         }
1202         GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
1203                 config.getGameModeConfiguration(gameMode);
1204         if (gameModeConfiguration == null) {
1205             return -1;
1206         }
1207         return gameModeConfiguration.getLoadingBoostDuration();
1208     }
1209 
1210     /**
1211      * If loading boost is enabled, invoke it.
1212      */
1213     @Override
1214     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
notifyGraphicsEnvironmentSetup(String packageName, int userId)1215     @GameMode public void notifyGraphicsEnvironmentSetup(String packageName, int userId)
1216             throws SecurityException {
1217         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1218                 Binder.getCallingUid(), userId, false, true, "notifyGraphicsEnvironmentSetup",
1219                 "com.android.server.app.GameManagerService");
1220 
1221         if (!isValidPackageName(packageName, userId)) {
1222             Slog.d(TAG, "No-op for attempt to notify graphics env setup for different package"
1223                     + "than caller with uid: " + Binder.getCallingUid());
1224             return;
1225         }
1226 
1227         final int gameMode = getGameMode(packageName, userId);
1228         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
1229             Slog.d(TAG, "No-op for attempt to notify graphics env setup for non-game app: "
1230                     + packageName);
1231             return;
1232         }
1233         int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
1234         if (loadingBoostDuration != -1) {
1235             if (loadingBoostDuration == 0 || loadingBoostDuration > LOADING_BOOST_MAX_DURATION) {
1236                 loadingBoostDuration = LOADING_BOOST_MAX_DURATION;
1237             }
1238             if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
1239                 // The loading mode has already been set and is waiting to be unset. It is not
1240                 // required to set the mode again and we should replace the queued cancel
1241                 // instruction.
1242                 mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
1243             } else {
1244                 Slog.v(TAG, "Game loading power mode ON (loading boost on game start)");
1245                 mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, true);
1246             }
1247 
1248             mHandler.sendMessageDelayed(
1249                     mHandler.obtainMessage(CANCEL_GAME_LOADING_MODE), loadingBoostDuration);
1250         }
1251     }
1252 
1253     /**
1254      * Sets the game service provider to a given package, meant for testing.
1255      *
1256      * <p>This setting persists until the next call or until the next reboot.
1257      *
1258      * <p>Checks that the caller has {@link android.Manifest.permission#SET_GAME_SERVICE}.
1259      */
1260     @Override
1261     @RequiresPermission(Manifest.permission.SET_GAME_SERVICE)
setGameServiceProvider(@ullable String packageName)1262     public void setGameServiceProvider(@Nullable String packageName) throws SecurityException {
1263         checkPermission(Manifest.permission.SET_GAME_SERVICE);
1264 
1265         if (mGameServiceController == null) {
1266             return;
1267         }
1268 
1269         mGameServiceController.setGameServiceProvider(packageName);
1270     }
1271 
1272 
1273     /**
1274      * Updates the resolution scaling factor for the package's target game mode and activates it.
1275      *
1276      * @param scalingFactor enable scaling override over any other compat scaling if positive,
1277      *                      or disable the override otherwise
1278      * @throws SecurityException        if caller doesn't have
1279      *                                  {@link android.Manifest.permission#MANAGE_GAME_MODE}
1280      *                                  permission.
1281      * @throws IllegalArgumentException if the user ID provided doesn't exist.
1282      */
1283     @Override
1284     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
updateResolutionScalingFactor(String packageName, int gameMode, float scalingFactor, int userId)1285     public void updateResolutionScalingFactor(String packageName, int gameMode, float scalingFactor,
1286             int userId) throws SecurityException, IllegalArgumentException {
1287         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1288         synchronized (mLock) {
1289             if (!mSettings.containsKey(userId)) {
1290                 throw new IllegalArgumentException("User " + userId + " wasn't started");
1291             }
1292         }
1293         setGameModeConfigOverride(packageName, userId, gameMode, null /*fpsStr*/,
1294                 Float.toString(scalingFactor));
1295     }
1296 
1297     /**
1298      * Gets the resolution scaling factor for the package's target game mode.
1299      *
1300      * @return scaling factor for the game mode if exists or negative value otherwise.
1301      * @throws SecurityException        if caller doesn't have
1302      *                                  {@link android.Manifest.permission#MANAGE_GAME_MODE}
1303      *                                  permission.
1304      * @throws IllegalArgumentException if the user ID provided doesn't exist.
1305      */
1306     @Override
1307     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
getResolutionScalingFactor(String packageName, int gameMode, int userId)1308     public float getResolutionScalingFactor(String packageName, int gameMode, int userId)
1309             throws SecurityException, IllegalArgumentException {
1310         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1311         synchronized (mLock) {
1312             if (!mSettings.containsKey(userId)) {
1313                 throw new IllegalArgumentException("User " + userId + " wasn't started");
1314             }
1315         }
1316         return getResolutionScalingFactorInternal(packageName, gameMode, userId);
1317     }
1318 
getResolutionScalingFactorInternal(String packageName, int gameMode, int userId)1319     float getResolutionScalingFactorInternal(String packageName, int gameMode, int userId) {
1320         final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
1321         if (packageConfig == null) {
1322             return GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING;
1323         }
1324         final GamePackageConfiguration.GameModeConfiguration modeConfig =
1325                 packageConfig.getGameModeConfiguration(gameMode);
1326         if (modeConfig != null) {
1327             return modeConfig.getScaling();
1328         }
1329         return GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING;
1330     }
1331 
1332     /**
1333      * Updates the config for the game's {@link GameManager#GAME_MODE_CUSTOM} mode.
1334      *
1335      * @throws SecurityException        if caller doesn't have
1336      *                                  {@link android.Manifest.permission#MANAGE_GAME_MODE}
1337      *                                  permission.
1338      * @throws IllegalArgumentException if the user ID provided doesn't exist.
1339      */
1340     @Override
1341     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
updateCustomGameModeConfiguration(String packageName, GameModeConfiguration gameModeConfig, int userId)1342     public void updateCustomGameModeConfiguration(String packageName,
1343             GameModeConfiguration gameModeConfig, int userId)
1344             throws SecurityException, IllegalArgumentException {
1345         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1346         if (!isPackageGame(packageName, userId)) {
1347             Slog.d(TAG, "No-op for attempt to update custom game mode for non-game app: "
1348                     + packageName);
1349             return;
1350         }
1351         synchronized (mLock) {
1352             if (!mSettings.containsKey(userId)) {
1353                 throw new IllegalArgumentException("User " + userId + " wasn't started");
1354             }
1355         }
1356         // TODO(b/243448953): add validation on gameModeConfig provided
1357         // Adding game mode config override of the given package name
1358         GamePackageConfiguration configOverride;
1359         synchronized (mLock) {
1360             if (!mSettings.containsKey(userId)) {
1361                 return;
1362             }
1363             final GameManagerSettings settings = mSettings.get(userId);
1364             // look for the existing GamePackageConfiguration override
1365             configOverride = settings.getConfigOverride(packageName);
1366             if (configOverride == null) {
1367                 configOverride = new GamePackageConfiguration(packageName);
1368                 settings.setConfigOverride(packageName, configOverride);
1369             }
1370         }
1371         GamePackageConfiguration.GameModeConfiguration internalConfig =
1372                 configOverride.getOrAddDefaultGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
1373         final float scalingValueFrom = internalConfig.getScaling();
1374         final int fpsValueFrom = internalConfig.getFps();
1375         internalConfig.updateFromPublicGameModeConfig(gameModeConfig);
1376 
1377         sendUserMessage(userId, WRITE_SETTINGS, EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG,
1378                 WRITE_DELAY_MILLIS);
1379         sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
1380                 EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG, WRITE_DELAY_MILLIS /*delayMillis*/);
1381 
1382         final int gameMode = getGameMode(packageName, userId);
1383         if (gameMode == GameManager.GAME_MODE_CUSTOM) {
1384             updateInterventions(packageName, gameMode, userId);
1385         }
1386         Slog.i(TAG, "Updated custom game mode config for package: " + packageName
1387                 + " with FPS=" + internalConfig.getFps() + ";Scaling="
1388                 + internalConfig.getScaling() + " under user " + userId);
1389 
1390         int gameUid = -1;
1391         try {
1392             gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
1393         } catch (NameNotFoundException ex) {
1394             Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
1395         }
1396         FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid,
1397                 Binder.getCallingUid(), gameModeToStatsdGameMode(GameManager.GAME_MODE_CUSTOM),
1398                 scalingValueFrom, gameModeConfig.getScalingFactor(),
1399                 fpsValueFrom, gameModeConfig.getFpsOverride());
1400     }
1401 
1402     /**
1403      * Adds a game mode listener.
1404      *
1405      * @throws SecurityException if caller doesn't have
1406      *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
1407      *                           permission.
1408      */
1409     @Override
1410     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
addGameModeListener(@onNull IGameModeListener listener)1411     public void addGameModeListener(@NonNull IGameModeListener listener) {
1412         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1413         try {
1414             final IBinder listenerBinder = listener.asBinder();
1415             listenerBinder.linkToDeath(new DeathRecipient() {
1416                 @Override public void binderDied() {
1417                     // TODO(b/258851194): add traces on binder death based listener removal
1418                     removeGameModeListenerUnchecked(listener);
1419                     listenerBinder.unlinkToDeath(this, 0 /*flags*/);
1420                 }
1421             }, 0 /*flags*/);
1422             synchronized (mGameModeListenerLock) {
1423                 mGameModeListeners.put(listener, Binder.getCallingUid());
1424             }
1425         } catch (RemoteException ex) {
1426             Slog.e(TAG,
1427                     "Failed to link death recipient for IGameModeListener from caller "
1428                             + Binder.getCallingUid() + ", abandoned its listener registration", ex);
1429         }
1430     }
1431 
1432     /**
1433      * Removes a game mode listener.
1434      *
1435      * @throws SecurityException if caller doesn't have
1436      *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
1437      *                           permission.
1438      */
1439     @Override
1440     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
removeGameModeListener(@onNull IGameModeListener listener)1441     public void removeGameModeListener(@NonNull IGameModeListener listener) {
1442         // TODO(b/258851194): add traces on manual listener removal
1443         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1444         removeGameModeListenerUnchecked(listener);
1445     }
1446 
removeGameModeListenerUnchecked(IGameModeListener listener)1447     private void removeGameModeListenerUnchecked(IGameModeListener listener) {
1448         synchronized (mGameModeListenerLock) {
1449             mGameModeListeners.remove(listener);
1450         }
1451     }
1452 
1453     /**
1454      * Adds a game state listener.
1455      */
1456     @Override
addGameStateListener(@onNull IGameStateListener listener)1457     public void addGameStateListener(@NonNull IGameStateListener listener) {
1458         try {
1459             final IBinder listenerBinder = listener.asBinder();
1460             listenerBinder.linkToDeath(new DeathRecipient() {
1461                 @Override public void binderDied() {
1462                     removeGameStateListenerUnchecked(listener);
1463                     listenerBinder.unlinkToDeath(this, 0 /*flags*/);
1464                 }
1465             }, 0 /*flags*/);
1466             synchronized (mGameStateListenerLock) {
1467                 mGameStateListeners.put(listener, Binder.getCallingUid());
1468             }
1469         } catch (RemoteException ex) {
1470             Slog.e(TAG,
1471                     "Failed to link death recipient for IGameStateListener from caller "
1472                             + Binder.getCallingUid() + ", abandoned its listener registration", ex);
1473         }
1474     }
1475 
1476     /**
1477      * Removes a game state listener.
1478      */
1479     @Override
removeGameStateListener(@onNull IGameStateListener listener)1480     public void removeGameStateListener(@NonNull IGameStateListener listener) {
1481         removeGameStateListenerUnchecked(listener);
1482     }
1483 
removeGameStateListenerUnchecked(IGameStateListener listener)1484     private void removeGameStateListenerUnchecked(IGameStateListener listener) {
1485         synchronized (mGameStateListenerLock) {
1486             mGameStateListeners.remove(listener);
1487         }
1488     }
1489 
1490     /**
1491      * Notified when boot is completed.
1492      */
1493     @VisibleForTesting
onBootCompleted()1494     void onBootCompleted() {
1495         Slog.d(TAG, "onBootCompleted");
1496         if (mGameServiceController != null) {
1497             mGameServiceController.onBootComplete();
1498         }
1499         mContext.registerReceiver(new BroadcastReceiver() {
1500             @Override
1501             public void onReceive(Context context, Intent intent) {
1502                 if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
1503                     synchronized (mLock) {
1504                         // Note that the max wait time of broadcast is 10s (see
1505                         // {@ShutdownThread#MAX_BROADCAST_TIMEMAX_BROADCAST_TIME}) currently so
1506                         // this can be optional only if we have message delay plus processing
1507                         // time significant smaller to prevent data loss.
1508                         for (Map.Entry<Integer, GameManagerSettings> entry : mSettings.entrySet()) {
1509                             final int userId = entry.getKey();
1510                             sendUserMessage(userId, WRITE_SETTINGS,
1511                                     EVENT_RECEIVE_SHUTDOWN_INDENT, 0 /*delayMillis*/);
1512                             sendUserMessage(userId,
1513                                     WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
1514                                     EVENT_RECEIVE_SHUTDOWN_INDENT,
1515                                     0 /*delayMillis*/);
1516                         }
1517                     }
1518                 }
1519             }
1520         }, new IntentFilter(Intent.ACTION_SHUTDOWN));
1521         Slog.v(TAG, "Game loading power mode OFF (game manager service start/restart)");
1522         mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
1523         Slog.v(TAG, "Game power mode OFF (game manager service start/restart)");
1524         mPowerManagerInternal.setPowerMode(Mode.GAME, false);
1525     }
1526 
sendUserMessage(int userId, int what, String eventForLog, int delayMillis)1527     private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
1528         Message msg = mHandler.obtainMessage(what, userId);
1529         if (!mHandler.sendMessageDelayed(msg, delayMillis)) {
1530             Slog.e(TAG, "Failed to send user message " + what + " on " + eventForLog);
1531         }
1532     }
1533 
onUserStarting(@onNull TargetUser user, File settingDataDir)1534     void onUserStarting(@NonNull TargetUser user, File settingDataDir) {
1535         final int userId = user.getUserIdentifier();
1536         synchronized (mLock) {
1537             if (!mSettings.containsKey(userId)) {
1538                 GameManagerSettings userSettings = new GameManagerSettings(settingDataDir);
1539                 mSettings.put(userId, userSettings);
1540                 userSettings.readPersistentDataLocked();
1541             }
1542         }
1543         sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_STARTING,
1544                 0 /*delayMillis*/);
1545 
1546         if (mGameServiceController != null) {
1547             mGameServiceController.notifyUserStarted(user);
1548         }
1549     }
1550 
onUserUnlocking(@onNull TargetUser user)1551     void onUserUnlocking(@NonNull TargetUser user) {
1552         if (mGameServiceController != null) {
1553             mGameServiceController.notifyUserUnlocking(user);
1554         }
1555     }
1556 
onUserStopping(TargetUser user)1557     void onUserStopping(TargetUser user) {
1558         final int userId = user.getUserIdentifier();
1559 
1560         synchronized (mLock) {
1561             if (!mSettings.containsKey(userId)) {
1562                 return;
1563             }
1564             sendUserMessage(userId, REMOVE_SETTINGS, EVENT_ON_USER_STOPPING, 0 /*delayMillis*/);
1565         }
1566 
1567         if (mGameServiceController != null) {
1568             mGameServiceController.notifyUserStopped(user);
1569         }
1570     }
1571 
onUserSwitching(TargetUser from, TargetUser to)1572     void onUserSwitching(TargetUser from, TargetUser to) {
1573         final int toUserId = to.getUserIdentifier();
1574         // we want to re-populate the setting when switching user as the device config may have
1575         // changed, which will only update for the previous user, see
1576         // DeviceConfigListener#onPropertiesChanged.
1577         sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_SWITCHING,
1578                 0 /*delayMillis*/);
1579 
1580         if (mGameServiceController != null) {
1581             mGameServiceController.notifyNewForegroundUser(to);
1582         }
1583     }
1584 
1585     /**
1586      * Remove frame rate override due to mode switch
1587      */
resetFps(String packageName, @UserIdInt int userId)1588     private void resetFps(String packageName, @UserIdInt int userId) {
1589         try {
1590             final float fps = 0.0f;
1591             final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
1592             setOverrideFrameRate(uid, fps);
1593         } catch (PackageManager.NameNotFoundException e) {
1594             return;
1595         }
1596     }
1597 
modeToBitmask(@ameMode int gameMode)1598     private static int modeToBitmask(@GameMode int gameMode) {
1599         return (1 << gameMode);
1600     }
1601 
bitFieldContainsModeBitmask(int bitField, @GameMode int gameMode)1602     private boolean bitFieldContainsModeBitmask(int bitField, @GameMode int gameMode) {
1603         return (bitField & modeToBitmask(gameMode)) != 0;
1604     }
1605 
1606     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
updateUseAngle(String packageName, @GameMode int gameMode)1607     private void updateUseAngle(String packageName, @GameMode int gameMode) {
1608         // TODO (b/188475576): Nothing to do yet. Remove if it's still empty when we're ready to
1609         // ship.
1610     }
1611 
1612 
updateFps(GamePackageConfiguration packageConfig, String packageName, @GameMode int gameMode, @UserIdInt int userId)1613     private void updateFps(GamePackageConfiguration packageConfig, String packageName,
1614             @GameMode int gameMode, @UserIdInt int userId) {
1615         final GamePackageConfiguration.GameModeConfiguration modeConfig =
1616                 packageConfig.getGameModeConfiguration(gameMode);
1617         if (modeConfig == null) {
1618             Slog.d(TAG, "Game mode " + gameMode + " not found for " + packageName);
1619             return;
1620         }
1621         try {
1622             final float fps = modeConfig.getFps();
1623             final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
1624             setOverrideFrameRate(uid, fps);
1625         } catch (PackageManager.NameNotFoundException e) {
1626             return;
1627         }
1628     }
1629 
1630 
updateInterventions(String packageName, @GameMode int gameMode, @UserIdInt int userId)1631     private void updateInterventions(String packageName,
1632             @GameMode int gameMode, @UserIdInt int userId) {
1633         final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
1634         if (gameMode == GameManager.GAME_MODE_STANDARD
1635                 || gameMode == GameManager.GAME_MODE_UNSUPPORTED || packageConfig == null
1636                 || packageConfig.willGamePerformOptimizations(gameMode)
1637                 || packageConfig.getGameModeConfiguration(gameMode) == null) {
1638             resetFps(packageName, userId);
1639             // resolution scaling does not need to be reset as it's now read dynamically on game
1640             // restart, see #getResolutionScalingFactor and CompatModePackages#getCompatScale.
1641             // TODO: reset Angle intervention here once implemented
1642             if (packageConfig == null) {
1643                 Slog.v(TAG, "Package configuration not found for " + packageName);
1644                 return;
1645             }
1646         } else {
1647             updateFps(packageConfig, packageName, gameMode, userId);
1648         }
1649         updateUseAngle(packageName, gameMode);
1650     }
1651 
1652     /**
1653      * Set the Game Mode Configuration override.
1654      * Update the config if exists, create one if not.
1655      */
1656     @VisibleForTesting
1657     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
setGameModeConfigOverride(String packageName, @UserIdInt int userId, @GameMode int gameMode, String fpsStr, String scaling)1658     public void setGameModeConfigOverride(String packageName, @UserIdInt int userId,
1659             @GameMode int gameMode, String fpsStr, String scaling) throws SecurityException {
1660         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1661         int gameUid = -1;
1662         try {
1663             gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
1664         } catch (NameNotFoundException ex) {
1665             Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
1666         }
1667         GamePackageConfiguration pkgConfig = getConfig(packageName, userId);
1668         if (pkgConfig != null && pkgConfig.getGameModeConfiguration(gameMode) != null) {
1669             final GamePackageConfiguration.GameModeConfiguration currentModeConfig =
1670                     pkgConfig.getGameModeConfiguration(gameMode);
1671             FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid,
1672                     Binder.getCallingUid(), gameModeToStatsdGameMode(gameMode),
1673                     currentModeConfig.getScaling() /* fromScaling */,
1674                     scaling == null ? currentModeConfig.getScaling()
1675                             : Float.parseFloat(scaling) /* toScaling */,
1676                     currentModeConfig.getFps() /* fromFps */,
1677                     fpsStr == null ? currentModeConfig.getFps()
1678                             : Integer.parseInt(fpsStr)) /* toFps */;
1679         } else {
1680             FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid,
1681                     Binder.getCallingUid(), gameModeToStatsdGameMode(gameMode),
1682                     GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING /* fromScaling*/,
1683                     scaling == null ? GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING
1684                             : Float.parseFloat(scaling) /* toScaling */,
1685                     0 /* fromFps */,
1686                     fpsStr == null ? 0 : Integer.parseInt(fpsStr) /* toFps */);
1687         }
1688 
1689         // Adding game mode config override of the given package name
1690         GamePackageConfiguration configOverride;
1691         synchronized (mLock) {
1692             if (!mSettings.containsKey(userId)) {
1693                 return;
1694             }
1695             final GameManagerSettings settings = mSettings.get(userId);
1696             // look for the existing GamePackageConfiguration override
1697             configOverride = settings.getConfigOverride(packageName);
1698             if (configOverride == null) {
1699                 configOverride = new GamePackageConfiguration(packageName);
1700                 settings.setConfigOverride(packageName, configOverride);
1701             }
1702         }
1703         // modify GameModeConfiguration intervention settings
1704         GamePackageConfiguration.GameModeConfiguration modeConfigOverride =
1705                 configOverride.getOrAddDefaultGameModeConfiguration(gameMode);
1706 
1707         if (fpsStr != null) {
1708             modeConfigOverride.setFpsStr(fpsStr);
1709         } else {
1710             modeConfigOverride.setFpsStr(
1711                     GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS);
1712         }
1713         if (scaling != null) {
1714             modeConfigOverride.setScaling(Float.parseFloat(scaling));
1715         }
1716         Slog.i(TAG, "Package Name: " + packageName
1717                 + " FPS: " + String.valueOf(modeConfigOverride.getFps())
1718                 + " Scaling: " + modeConfigOverride.getScaling());
1719         setGameMode(packageName, gameMode, userId);
1720     }
1721 
1722     /**
1723      * Reset the overridden gameModeConfiguration of the given mode.
1724      * Remove the config override if game mode is not specified.
1725      */
1726     @VisibleForTesting
1727     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
resetGameModeConfigOverride(String packageName, @UserIdInt int userId, @GameMode int gameModeToReset)1728     public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId,
1729             @GameMode int gameModeToReset) throws SecurityException {
1730         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1731         // resets GamePackageConfiguration of a given packageName.
1732         // If a gameMode is specified, only reset the GameModeConfiguration of the gameMode.
1733         synchronized (mLock) {
1734             if (!mSettings.containsKey(userId)) {
1735                 return;
1736             }
1737             final GameManagerSettings settings = mSettings.get(userId);
1738             if (gameModeToReset != -1) {
1739                 final GamePackageConfiguration configOverride = settings.getConfigOverride(
1740                         packageName);
1741                 if (configOverride == null) {
1742                     return;
1743                 }
1744                 final int modesBitfield = configOverride.getAvailableGameModesBitfield();
1745                 if (!bitFieldContainsModeBitmask(modesBitfield, gameModeToReset)) {
1746                     return;
1747                 }
1748                 configOverride.removeModeConfig(gameModeToReset);
1749                 if (!configOverride.hasActiveGameModeConfig()) {
1750                     settings.removeConfigOverride(packageName);
1751                 }
1752             } else {
1753                 settings.removeConfigOverride(packageName);
1754             }
1755         }
1756 
1757         // Make sure after resetting the game mode is still supported.
1758         // If not, set the game mode to standard
1759         int gameMode = getGameMode(packageName, userId);
1760 
1761         final GamePackageConfiguration config = getConfig(packageName, userId);
1762         final int newGameMode = getNewGameMode(gameMode, config);
1763         if (gameMode != newGameMode) {
1764             setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
1765             return;
1766         }
1767         setGameMode(packageName, gameMode, userId);
1768     }
1769 
getNewGameMode(int gameMode, GamePackageConfiguration config)1770     private int getNewGameMode(int gameMode, GamePackageConfiguration config) {
1771         int newGameMode = gameMode;
1772         if (config != null) {
1773             int modesBitfield = config.getAvailableGameModesBitfield();
1774             // Remove UNSUPPORTED to simplify the logic here, since we really just
1775             // want to check if we support selectable game modes
1776             modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
1777             if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) {
1778                 // always default to STANDARD if there is no mode config
1779                 newGameMode = GameManager.GAME_MODE_STANDARD;
1780             }
1781         } else {
1782             // always default to STANDARD if there is no package config
1783             newGameMode = GameManager.GAME_MODE_STANDARD;
1784         }
1785         return newGameMode;
1786     }
1787 
1788     /**
1789      * Returns the string listing all the interventions currently set to a game.
1790      */
1791     @RequiresPermission(Manifest.permission.QUERY_ALL_PACKAGES)
getInterventionList(String packageName, int userId)1792     public String getInterventionList(String packageName, int userId) {
1793         checkPermission(Manifest.permission.QUERY_ALL_PACKAGES);
1794         final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
1795         final StringBuilder listStrSb = new StringBuilder();
1796         if (packageConfig == null) {
1797             listStrSb.append("\n No intervention found for package ")
1798                     .append(packageName);
1799             return listStrSb.toString();
1800         }
1801         listStrSb.append("\n")
1802                 .append(packageConfig.toString());
1803         return listStrSb.toString();
1804     }
1805 
1806     /**
1807      * @hide
1808      */
1809     @VisibleForTesting
updateConfigsForUser(@serIdInt int userId, boolean checkGamePackage, String... packageNames)1810     void updateConfigsForUser(@UserIdInt int userId, boolean checkGamePackage,
1811             String... packageNames) {
1812         if (checkGamePackage) {
1813             packageNames = Arrays.stream(packageNames).filter(
1814                     p -> isPackageGame(p, userId)).toArray(String[]::new);
1815         }
1816         try {
1817             synchronized (mDeviceConfigLock) {
1818                 for (final String packageName : packageNames) {
1819                     final GamePackageConfiguration config =
1820                             new GamePackageConfiguration(mPackageManager, packageName, userId);
1821                     if (config.isActive()) {
1822                         if (DEBUG) {
1823                             Slog.i(TAG, "Adding config: " + config.toString());
1824                         }
1825                         mConfigs.put(packageName, config);
1826                     } else {
1827                         if (DEBUG) {
1828                             Slog.w(TAG, "Inactive package config for "
1829                                     + config.getPackageName() + ":" + config.toString());
1830                         }
1831                         mConfigs.remove(packageName);
1832                     }
1833                 }
1834             }
1835             synchronized (mLock) {
1836                 if (!mSettings.containsKey(userId)) {
1837                     return;
1838                 }
1839             }
1840             for (final String packageName : packageNames) {
1841                 int gameMode = getGameMode(packageName, userId);
1842                 // Make sure the user settings and package configs don't conflict.
1843                 // I.e. the user setting is set to a mode that no longer available due to
1844                 // config/manifest changes.
1845                 // Most of the time we won't have to change anything.
1846                 GamePackageConfiguration config = null;
1847                 synchronized (mDeviceConfigLock) {
1848                     config = mConfigs.get(packageName);
1849                 }
1850                 final int newGameMode = getNewGameMode(gameMode, config);
1851                 if (newGameMode != gameMode) {
1852                     setGameMode(packageName, newGameMode, userId);
1853                 } else {
1854                     // Make sure we handle the case when the interventions are changed while
1855                     // the game mode remains the same. We call only updateInterventions() here.
1856                     updateInterventions(packageName, gameMode, userId);
1857                 }
1858             }
1859             sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
1860                     "UPDATE_CONFIGS_FOR_USERS", 0 /*delayMillis*/);
1861         } catch (Exception e) {
1862             Slog.e(TAG, "Failed to update configs for user " + userId + ": " + e);
1863         }
1864     }
1865 
1866     /*
1867      Write the interventions and mode of each game to file /system/data/game_mode_intervention.list
1868      Each line will contain the information of each game, separated by tab.
1869      The format of the output is:
1870      <package name> <UID> <current mode> <game mode 1> <interventions> <game mode 2> <interventions>
1871      For example:
1872      com.android.app1   1425    1   2   angle=0,scaling=1.0,fps=60  3   angle=1,scaling=0.5,fps=30
1873      */
writeGameModeInterventionsToFile(@serIdInt int userId)1874     private void writeGameModeInterventionsToFile(@UserIdInt int userId) {
1875         FileOutputStream fileOutputStream = null;
1876         BufferedWriter bufferedWriter;
1877         try {
1878             fileOutputStream = mGameModeInterventionListFile.startWrite();
1879             bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream,
1880                     Charset.defaultCharset()));
1881 
1882             final StringBuilder sb = new StringBuilder();
1883             final List<String> installedGamesList = getInstalledGamePackageNamesByAllUsers(userId);
1884             for (final String packageName : installedGamesList) {
1885                 GamePackageConfiguration packageConfig = getConfig(packageName, userId);
1886                 if (packageConfig == null) {
1887                     continue;
1888                 }
1889                 sb.append(packageName);
1890                 sb.append("\t");
1891                 sb.append(mPackageManager.getPackageUidAsUser(packageName, userId));
1892                 sb.append("\t");
1893                 sb.append(getGameMode(packageName, userId));
1894                 sb.append("\t");
1895                 final int[] modes = packageConfig.getAvailableGameModes();
1896                 for (int mode : modes) {
1897                     final GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
1898                             packageConfig.getGameModeConfiguration(mode);
1899                     if (gameModeConfiguration == null) {
1900                         continue;
1901                     }
1902                     sb.append(mode);
1903                     sb.append("\t");
1904                     final int useAngle = gameModeConfiguration.getUseAngle() ? 1 : 0;
1905                     sb.append(TextUtils.formatSimple("angle=%d", useAngle));
1906                     sb.append(",");
1907                     final float scaling = gameModeConfiguration.getScaling();
1908                     sb.append("scaling=");
1909                     sb.append(scaling);
1910                     sb.append(",");
1911                     final int fps = gameModeConfiguration.getFps();
1912                     sb.append(TextUtils.formatSimple("fps=%d", fps));
1913                     sb.append("\t");
1914                 }
1915                 sb.append("\n");
1916             }
1917             bufferedWriter.append(sb);
1918             bufferedWriter.flush();
1919             FileUtils.sync(fileOutputStream);
1920             mGameModeInterventionListFile.finishWrite(fileOutputStream);
1921         } catch (Exception e) {
1922             mGameModeInterventionListFile.failWrite(fileOutputStream);
1923             Slog.wtf(TAG, "Failed to write game_mode_intervention.list, exception " + e);
1924         }
1925         return;
1926     }
1927 
getAllUserIds(@serIdInt int currentUserId)1928     private int[] getAllUserIds(@UserIdInt int currentUserId) {
1929         final List<UserInfo> users = mUserManager.getUsers();
1930         int[] userIds = new int[users.size()];
1931         for (int i = 0; i < userIds.length; ++i) {
1932             userIds[i] = users.get(i).id;
1933         }
1934         if (currentUserId != -1) {
1935             userIds = ArrayUtils.appendInt(userIds, currentUserId);
1936         }
1937         return userIds;
1938     }
1939 
getInstalledGamePackageNames(@serIdInt int userId)1940     private String[] getInstalledGamePackageNames(@UserIdInt int userId) {
1941         final List<PackageInfo> packages =
1942                 mPackageManager.getInstalledPackagesAsUser(0, userId);
1943         return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category
1944                         == ApplicationInfo.CATEGORY_GAME)
1945                 .map(e -> e.packageName)
1946                 .toArray(String[]::new);
1947     }
1948 
getInstalledGamePackageNamesByAllUsers(@serIdInt int currentUserId)1949     private List<String> getInstalledGamePackageNamesByAllUsers(@UserIdInt int currentUserId) {
1950         HashSet<String> packageSet = new HashSet<>();
1951 
1952         final int[] userIds = getAllUserIds(currentUserId);
1953         for (int userId : userIds) {
1954             packageSet.addAll(Arrays.asList(getInstalledGamePackageNames(userId)));
1955         }
1956 
1957         return new ArrayList<>(packageSet);
1958     }
1959 
1960     /**
1961      * @hide
1962      */
getConfig(String packageName, int userId)1963     public GamePackageConfiguration getConfig(String packageName, int userId) {
1964         GamePackageConfiguration overrideConfig = null;
1965         GamePackageConfiguration config;
1966         synchronized (mDeviceConfigLock) {
1967             config = mConfigs.get(packageName);
1968         }
1969 
1970         synchronized (mLock) {
1971             if (mSettings.containsKey(userId)) {
1972                 overrideConfig = mSettings.get(userId).getConfigOverride(packageName);
1973             }
1974         }
1975         if (overrideConfig == null || config == null) {
1976             return overrideConfig == null ? config : overrideConfig;
1977         }
1978         return config.copyAndApplyOverride(overrideConfig);
1979     }
1980 
registerPackageReceiver()1981     private void registerPackageReceiver() {
1982         final IntentFilter packageFilter = new IntentFilter();
1983         packageFilter.addAction(ACTION_PACKAGE_ADDED);
1984         packageFilter.addAction(ACTION_PACKAGE_REMOVED);
1985         packageFilter.addDataScheme("package");
1986         final BroadcastReceiver packageReceiver = new BroadcastReceiver() {
1987             @Override
1988             public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
1989                 final Uri data = intent.getData();
1990                 try {
1991                     final int userId = getSendingUserId();
1992                     if (userId != ActivityManager.getCurrentUser()) {
1993                         return;
1994                     }
1995                     final String packageName = data.getSchemeSpecificPart();
1996                     try {
1997                         final ApplicationInfo applicationInfo = mPackageManager
1998                                 .getApplicationInfoAsUser(
1999                                         packageName, PackageManager.MATCH_ALL, userId);
2000                         if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
2001                             return;
2002                         }
2003                     } catch (NameNotFoundException e) {
2004                         // Ignore the exception.
2005                     }
2006                     switch (intent.getAction()) {
2007                         case ACTION_PACKAGE_ADDED:
2008                             updateConfigsForUser(userId, true /*checkGamePackage*/, packageName);
2009                             break;
2010                         case ACTION_PACKAGE_REMOVED:
2011                             if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
2012                                 synchronized (mDeviceConfigLock) {
2013                                     mConfigs.remove(packageName);
2014                                 }
2015                                 synchronized (mLock) {
2016                                     if (mSettings.containsKey(userId)) {
2017                                         mSettings.get(userId).removeGame(packageName);
2018                                     }
2019                                     sendUserMessage(userId, WRITE_SETTINGS,
2020                                             Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
2021                                     sendUserMessage(userId,
2022                                             WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
2023                                             Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
2024                                 }
2025                             }
2026                             break;
2027                         default:
2028                             // do nothing
2029                             break;
2030                     }
2031                 } catch (NullPointerException e) {
2032                     Slog.e(TAG, "Failed to get package name for new package");
2033                 }
2034             }
2035         };
2036         mContext.registerReceiverForAllUsers(packageReceiver, packageFilter,
2037                 /* broadcastPermission= */ null, /* scheduler= */ null);
2038     }
2039 
registerDeviceConfigListener()2040     private void registerDeviceConfigListener() {
2041         mDeviceConfigListener = new DeviceConfigListener();
2042     }
2043 
publishLocalService()2044     private void publishLocalService() {
2045         LocalServices.addService(GameManagerInternal.class, new LocalService());
2046     }
2047 
registerStatsCallbacks()2048     private void registerStatsCallbacks() {
2049         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
2050         statsManager.setPullAtomCallback(
2051                 FrameworkStatsLog.GAME_MODE_INFO,
2052                 null, // use default PullAtomMetadata values
2053                 DIRECT_EXECUTOR,
2054                 this::onPullAtom);
2055         statsManager.setPullAtomCallback(
2056                 FrameworkStatsLog.GAME_MODE_CONFIGURATION,
2057                 null, // use default PullAtomMetadata values
2058                 DIRECT_EXECUTOR,
2059                 this::onPullAtom);
2060         statsManager.setPullAtomCallback(
2061                 FrameworkStatsLog.GAME_MODE_LISTENER,
2062                 null, // use default PullAtomMetadata values
2063                 DIRECT_EXECUTOR,
2064                 this::onPullAtom);
2065     }
2066 
onPullAtom(int atomTag, @NonNull List<StatsEvent> data)2067     private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
2068         if (atomTag == FrameworkStatsLog.GAME_MODE_INFO
2069                 || atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
2070             int userId = ActivityManager.getCurrentUser();
2071             Set<String> packages;
2072             synchronized (mDeviceConfigLock) {
2073                 packages = mConfigs.keySet();
2074             }
2075             for (String p : packages) {
2076                 GamePackageConfiguration config = getConfig(p, userId);
2077                 if (config == null) {
2078                     continue;
2079                 }
2080                 int uid = -1;
2081                 try {
2082                     uid = mPackageManager.getPackageUidAsUser(p, userId);
2083                 } catch (NameNotFoundException ex) {
2084                     Slog.d(TAG,
2085                             "Cannot find UID for package " + p + " under user handle id " + userId);
2086                 }
2087                 if (atomTag == FrameworkStatsLog.GAME_MODE_INFO) {
2088                     data.add(
2089                             FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_INFO, uid,
2090                                     gameModesToStatsdGameModes(config.getOverriddenGameModes()),
2091                                     gameModesToStatsdGameModes(config.getAvailableGameModes())));
2092                 } else if (atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
2093                     for (int gameMode : config.getAvailableGameModes()) {
2094                         GamePackageConfiguration.GameModeConfiguration modeConfig =
2095                                 config.getGameModeConfiguration(gameMode);
2096                         if (modeConfig != null) {
2097                             data.add(FrameworkStatsLog.buildStatsEvent(
2098                                     FrameworkStatsLog.GAME_MODE_CONFIGURATION, uid,
2099                                     gameModeToStatsdGameMode(gameMode), modeConfig.getFps(),
2100                                     modeConfig.getScaling()));
2101                         }
2102                     }
2103                 }
2104             }
2105         } else if (atomTag == FrameworkStatsLog.GAME_MODE_LISTENER) {
2106             synchronized (mGameModeListenerLock) {
2107                 data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_LISTENER,
2108                         mGameModeListeners.size()));
2109             }
2110         }
2111         return android.app.StatsManager.PULL_SUCCESS;
2112     }
2113 
gameModesToStatsdGameModes(int[] modes)2114     private static int[] gameModesToStatsdGameModes(int[] modes) {
2115         if (modes == null) {
2116             return null;
2117         }
2118         int[] statsdModes = new int[modes.length];
2119         int i = 0;
2120         for (int mode : modes) {
2121             statsdModes[i++] = gameModeToStatsdGameMode(mode);
2122         }
2123         return statsdModes;
2124     }
2125 
gameModeToStatsdGameMode(int mode)2126     private static int gameModeToStatsdGameMode(int mode) {
2127         switch (mode) {
2128             case GameManager.GAME_MODE_BATTERY:
2129                 return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_BATTERY;
2130             case GameManager.GAME_MODE_PERFORMANCE:
2131                 return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_PERFORMANCE;
2132             case GameManager.GAME_MODE_CUSTOM:
2133                 return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_CUSTOM;
2134             case GameManager.GAME_MODE_STANDARD:
2135                 return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_STANDARD;
2136             case GameManager.GAME_MODE_UNSUPPORTED:
2137                 return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_UNSUPPORTED;
2138             default:
2139                 return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_UNSPECIFIED;
2140         }
2141     }
2142 
gameStateModeToStatsdGameState(int mode)2143     private static int gameStateModeToStatsdGameState(int mode) {
2144         switch (mode) {
2145             case GameState.MODE_NONE:
2146                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_NONE;
2147             case GameState.MODE_GAMEPLAY_INTERRUPTIBLE:
2148                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_GAMEPLAY_INTERRUPTIBLE;
2149             case GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE:
2150                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_GAMEPLAY_UNINTERRUPTIBLE;
2151             case GameState.MODE_CONTENT:
2152                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_CONTENT;
2153             case GameState.MODE_UNKNOWN:
2154             default:
2155                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_UNKNOWN;
2156         }
2157     }
2158 
createServiceThread()2159     private static ServiceThread createServiceThread() {
2160         ServiceThread handlerThread = new ServiceThread(TAG,
2161                 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
2162         handlerThread.start();
2163         return handlerThread;
2164     }
2165 
2166     @VisibleForTesting
setOverrideFrameRate(int uid, float frameRate)2167     void setOverrideFrameRate(int uid, float frameRate) {
2168         nativeSetOverrideFrameRate(uid, frameRate);
2169     }
2170 
2171     /**
2172      * load dynamic library for frame rate overriding JNI calls
2173      */
nativeSetOverrideFrameRate(int uid, float frameRate)2174     private static native void nativeSetOverrideFrameRate(int uid, float frameRate);
2175 
2176     final class MyUidObserver extends UidObserver {
2177         @Override
onUidGone(int uid, boolean disabled)2178         public void onUidGone(int uid, boolean disabled) {
2179             synchronized (mUidObserverLock) {
2180                 disableGameMode(uid);
2181             }
2182         }
2183 
2184         @Override
onUidStateChanged(int uid, int procState, long procStateSeq, int capability)2185         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
2186             synchronized (mUidObserverLock) {
2187                 if (procState != ActivityManager.PROCESS_STATE_TOP) {
2188                     disableGameMode(uid);
2189                     return;
2190                 }
2191 
2192                 final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
2193                 if (packages == null || packages.length == 0) {
2194                     return;
2195                 }
2196 
2197                 final int userId = mContext.getUserId();
2198                 if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
2199                     return;
2200                 }
2201 
2202                 if (mForegroundGameUids.isEmpty()) {
2203                     Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
2204                     mPowerManagerInternal.setPowerMode(Mode.GAME, true);
2205                 }
2206                 mForegroundGameUids.add(uid);
2207             }
2208         }
2209 
disableGameMode(int uid)2210         private void disableGameMode(int uid) {
2211             synchronized (mUidObserverLock) {
2212                 if (!mForegroundGameUids.contains(uid)) {
2213                     return;
2214                 }
2215                 mForegroundGameUids.remove(uid);
2216                 if (!mForegroundGameUids.isEmpty()) {
2217                     return;
2218                 }
2219                 Slog.v(TAG,
2220                         "Game power mode OFF (process remomved or state changed to background)");
2221                 mPowerManagerInternal.setPowerMode(Mode.GAME, false);
2222             }
2223         }
2224     }
2225 }
2226