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