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