• 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 
23 import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver;
24 import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling;
25 import static com.android.internal.R.styleable.GameModeConfig_allowGameFpsOverride;
26 import static com.android.internal.R.styleable.GameModeConfig_supportsBatteryGameMode;
27 import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanceGameMode;
28 import static com.android.server.wm.CompatModePackages.DOWNSCALED;
29 import static com.android.server.wm.CompatModePackages.DOWNSCALE_30;
30 import static com.android.server.wm.CompatModePackages.DOWNSCALE_35;
31 import static com.android.server.wm.CompatModePackages.DOWNSCALE_40;
32 import static com.android.server.wm.CompatModePackages.DOWNSCALE_45;
33 import static com.android.server.wm.CompatModePackages.DOWNSCALE_50;
34 import static com.android.server.wm.CompatModePackages.DOWNSCALE_55;
35 import static com.android.server.wm.CompatModePackages.DOWNSCALE_60;
36 import static com.android.server.wm.CompatModePackages.DOWNSCALE_65;
37 import static com.android.server.wm.CompatModePackages.DOWNSCALE_70;
38 import static com.android.server.wm.CompatModePackages.DOWNSCALE_75;
39 import static com.android.server.wm.CompatModePackages.DOWNSCALE_80;
40 import static com.android.server.wm.CompatModePackages.DOWNSCALE_85;
41 import static com.android.server.wm.CompatModePackages.DOWNSCALE_90;
42 
43 import android.Manifest;
44 import android.annotation.NonNull;
45 import android.annotation.Nullable;
46 import android.annotation.RequiresPermission;
47 import android.annotation.UserIdInt;
48 import android.app.ActivityManager;
49 import android.app.GameManager;
50 import android.app.GameManager.GameMode;
51 import android.app.GameModeInfo;
52 import android.app.GameState;
53 import android.app.IGameManagerService;
54 import android.app.IUidObserver;
55 import android.app.compat.PackageOverride;
56 import android.content.BroadcastReceiver;
57 import android.content.Context;
58 import android.content.Intent;
59 import android.content.IntentFilter;
60 import android.content.pm.ApplicationInfo;
61 import android.content.pm.PackageInfo;
62 import android.content.pm.PackageManager;
63 import android.content.pm.PackageManager.NameNotFoundException;
64 import android.content.pm.UserInfo;
65 import android.content.res.Resources;
66 import android.content.res.TypedArray;
67 import android.content.res.XmlResourceParser;
68 import android.hardware.power.Mode;
69 import android.net.Uri;
70 import android.os.Binder;
71 import android.os.Bundle;
72 import android.os.Environment;
73 import android.os.FileUtils;
74 import android.os.Handler;
75 import android.os.Looper;
76 import android.os.Message;
77 import android.os.PowerManagerInternal;
78 import android.os.Process;
79 import android.os.RemoteException;
80 import android.os.ResultReceiver;
81 import android.os.ServiceManager;
82 import android.os.ShellCallback;
83 import android.os.UserManager;
84 import android.provider.DeviceConfig;
85 import android.provider.DeviceConfig.Properties;
86 import android.text.TextUtils;
87 import android.util.ArrayMap;
88 import android.util.AtomicFile;
89 import android.util.AttributeSet;
90 import android.util.KeyValueListParser;
91 import android.util.Slog;
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.compat.CompatibilityOverrideConfig;
97 import com.android.internal.compat.IPlatformCompat;
98 import com.android.internal.os.BackgroundThread;
99 import com.android.internal.util.ArrayUtils;
100 import com.android.internal.util.FrameworkStatsLog;
101 import com.android.server.LocalServices;
102 import com.android.server.ServiceThread;
103 import com.android.server.SystemService;
104 import com.android.server.SystemService.TargetUser;
105 
106 import org.xmlpull.v1.XmlPullParser;
107 import org.xmlpull.v1.XmlPullParserException;
108 
109 import java.io.BufferedWriter;
110 import java.io.File;
111 import java.io.FileDescriptor;
112 import java.io.FileOutputStream;
113 import java.io.IOException;
114 import java.io.OutputStreamWriter;
115 import java.io.PrintWriter;
116 import java.nio.charset.Charset;
117 import java.util.ArrayList;
118 import java.util.Arrays;
119 import java.util.HashSet;
120 import java.util.List;
121 import java.util.Map;
122 import java.util.Set;
123 
124 /**
125  * Service to manage game related features.
126  *
127  * <p>Game service is a core service that monitors, coordinates game related features,
128  * as well as collect metrics.</p>
129  *
130  * @hide
131  */
132 public final class GameManagerService extends IGameManagerService.Stub {
133     public static final String TAG = "GameManagerService";
134 
135     private static final boolean DEBUG = false;
136 
137     static final int WRITE_SETTINGS = 1;
138     static final int REMOVE_SETTINGS = 2;
139     static final int POPULATE_GAME_MODE_SETTINGS = 3;
140     static final int SET_GAME_STATE = 4;
141     static final int CANCEL_GAME_LOADING_MODE = 5;
142     static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6;
143     static final int WRITE_SETTINGS_DELAY = 10 * 1000;  // 10 seconds
144     static final int LOADING_BOOST_MAX_DURATION = 5 * 1000;  // 5 seconds
145 
146     static final PackageOverride COMPAT_ENABLED = new PackageOverride.Builder().setEnabled(true)
147             .build();
148     static final PackageOverride COMPAT_DISABLED = new PackageOverride.Builder().setEnabled(false)
149             .build();
150     private static final String PACKAGE_NAME_MSG_KEY = "packageName";
151     private static final String USER_ID_MSG_KEY = "userId";
152     private static final String GAME_MODE_INTERVENTION_LIST_FILE_NAME =
153             "game_mode_intervention.list";
154 
155     private final Context mContext;
156     private final Object mLock = new Object();
157     private final Object mDeviceConfigLock = new Object();
158     private final Object mOverrideConfigLock = new Object();
159     private final Handler mHandler;
160     private final PackageManager mPackageManager;
161     private final UserManager mUserManager;
162     private final IPlatformCompat mPlatformCompat;
163     private final PowerManagerInternal mPowerManagerInternal;
164     private final File mSystemDir;
165     @VisibleForTesting
166     final AtomicFile mGameModeInterventionListFile;
167     private DeviceConfigListener mDeviceConfigListener;
168     @GuardedBy("mLock")
169     private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
170     @GuardedBy("mDeviceConfigLock")
171     private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
172     @GuardedBy("mOverrideConfigLock")
173     private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
174     @Nullable
175     private final GameServiceController mGameServiceController;
176     private final Object mUidObserverLock = new Object();
177     @VisibleForTesting
178     @Nullable
179     final UidObserver mUidObserver;
180     @GuardedBy("mUidObserverLock")
181     private final Set<Integer> mForegroundGameUids = new HashSet<>();
182 
GameManagerService(Context context)183     public GameManagerService(Context context) {
184         this(context, createServiceThread().getLooper());
185     }
186 
GameManagerService(Context context, Looper looper)187     GameManagerService(Context context, Looper looper) {
188         this(context, looper, Environment.getDataDirectory());
189     }
190 
191     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
GameManagerService(Context context, Looper looper, File dataDir)192     GameManagerService(Context context, Looper looper, File dataDir) {
193         mContext = context;
194         mHandler = new SettingsHandler(looper);
195         mPackageManager = mContext.getPackageManager();
196         mUserManager = mContext.getSystemService(UserManager.class);
197         mPlatformCompat = IPlatformCompat.Stub.asInterface(
198                 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
199         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
200         mSystemDir = new File(dataDir, "system");
201         mSystemDir.mkdirs();
202         FileUtils.setPermissions(mSystemDir.toString(),
203                 FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
204                 -1, -1);
205         mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
206                 GAME_MODE_INTERVENTION_LIST_FILE_NAME));
207         FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
208                 FileUtils.S_IRUSR | FileUtils.S_IWUSR
209                         | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
210                 -1, -1);
211         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
212             mGameServiceController = new GameServiceController(
213                     context, BackgroundThread.getExecutor(),
214                     new GameServiceProviderSelectorImpl(
215                             context.getResources(),
216                             context.getPackageManager()),
217                     new GameServiceProviderInstanceFactoryImpl(context));
218         } else {
219             mGameServiceController = null;
220         }
221         mUidObserver = new UidObserver();
222         try {
223             ActivityManager.getService().registerUidObserver(mUidObserver,
224                     ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
225                     ActivityManager.PROCESS_STATE_UNKNOWN, null);
226         } catch (RemoteException e) {
227             Slog.w(TAG, "Could not register UidObserver");
228         }
229     }
230 
231     @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result)232     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
233             String[] args, ShellCallback callback, ResultReceiver result) {
234         new GameManagerShellCommand().exec(this, in, out, err, args, callback, result);
235     }
236 
237     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)238     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
239         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
240                 != PackageManager.PERMISSION_GRANTED) {
241             writer.println("Permission Denial: can't dump GameManagerService from from pid="
242                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
243                     + " without permission " + android.Manifest.permission.DUMP);
244             return;
245         }
246         if (args == null || args.length == 0) {
247             writer.println("*Dump GameManagerService*");
248             dumpAllGameConfigs(writer);
249         }
250     }
251 
dumpAllGameConfigs(PrintWriter pw)252     private void dumpAllGameConfigs(PrintWriter pw) {
253         final int userId = ActivityManager.getCurrentUser();
254         String[] packageList = getInstalledGamePackageNames(userId);
255         for (final String packageName : packageList) {
256             pw.println(getInterventionList(packageName));
257         }
258     }
259 
260     class SettingsHandler extends Handler {
261 
SettingsHandler(Looper looper)262         SettingsHandler(Looper looper) {
263             super(looper);
264         }
265 
266         @Override
handleMessage(Message msg)267         public void handleMessage(Message msg) {
268             doHandleMessage(msg);
269         }
270 
doHandleMessage(Message msg)271         void doHandleMessage(Message msg) {
272             switch (msg.what) {
273                 case WRITE_SETTINGS: {
274                     final int userId = (int) msg.obj;
275                     if (userId < 0) {
276                         Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
277                         synchronized (mLock) {
278                             removeMessages(WRITE_SETTINGS, msg.obj);
279                         }
280                         break;
281                     }
282 
283                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
284                     synchronized (mLock) {
285                         removeMessages(WRITE_SETTINGS, msg.obj);
286                         if (mSettings.containsKey(userId)) {
287                             GameManagerSettings userSettings = mSettings.get(userId);
288                             userSettings.writePersistentDataLocked();
289                         }
290                     }
291                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
292                     break;
293                 }
294                 case REMOVE_SETTINGS: {
295                     final int userId = (int) msg.obj;
296                     if (userId < 0) {
297                         Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
298                         synchronized (mLock) {
299                             removeMessages(WRITE_SETTINGS, msg.obj);
300                             removeMessages(REMOVE_SETTINGS, msg.obj);
301                         }
302                         break;
303                     }
304 
305                     synchronized (mLock) {
306                         // Since the user was removed, ignore previous write message
307                         // and do write here.
308                         removeMessages(WRITE_SETTINGS, msg.obj);
309                         removeMessages(REMOVE_SETTINGS, msg.obj);
310                         if (mSettings.containsKey(userId)) {
311                             final GameManagerSettings userSettings = mSettings.get(userId);
312                             mSettings.remove(userId);
313                             userSettings.writePersistentDataLocked();
314                         }
315                     }
316                     break;
317                 }
318                 case POPULATE_GAME_MODE_SETTINGS: {
319                     // Scan all game packages and re-enforce the configured compat mode overrides
320                     // as the DeviceConfig may have be wiped/since last reboot and we can't risk
321                     // having overrides configured for packages that no longer have any DeviceConfig
322                     // and thus any way to escape compat mode.
323                     removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
324                     final int userId = (int) msg.obj;
325                     final String[] packageNames = getInstalledGamePackageNames(userId);
326                     updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
327                     break;
328                 }
329                 case SET_GAME_STATE: {
330                     final GameState gameState = (GameState) msg.obj;
331                     final boolean isLoading = gameState.isLoading();
332                     final Bundle data = msg.getData();
333                     final String packageName = data.getString(PACKAGE_NAME_MSG_KEY);
334                     final int userId = data.getInt(USER_ID_MSG_KEY);
335 
336                     // Restrict to games only. Requires performance mode to be enabled.
337                     final boolean boostEnabled =
338                             getGameMode(packageName, userId) == GameManager.GAME_MODE_PERFORMANCE;
339                     int uid;
340                     try {
341                         uid = mPackageManager.getPackageUidAsUser(packageName, userId);
342                     } catch (NameNotFoundException e) {
343                         Slog.v(TAG, "Failed to get package metadata");
344                         uid = -1;
345                     }
346                     FrameworkStatsLog.write(FrameworkStatsLog.GAME_STATE_CHANGED, packageName, uid,
347                             boostEnabled, gameStateModeToStatsdGameState(gameState.getMode()),
348                             isLoading, gameState.getLabel(), gameState.getQuality());
349 
350                     if (boostEnabled) {
351                         if (mPowerManagerInternal == null) {
352                             Slog.d(TAG, "Error setting loading mode for package " + packageName
353                                     + " and userId " + userId);
354                             break;
355                         }
356                         mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
357                     }
358                     break;
359                 }
360                 case CANCEL_GAME_LOADING_MODE: {
361                     mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
362                     break;
363                 }
364                 case WRITE_GAME_MODE_INTERVENTION_LIST_FILE: {
365                     final int userId = (int) msg.obj;
366                     if (userId < 0) {
367                         Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId);
368                         synchronized (mLock) {
369                             removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
370                         }
371                         break;
372                     }
373 
374                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
375                     removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
376                     writeGameModeInterventionsToFile(userId);
377                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
378                     break;
379                 }
380             }
381         }
382     }
383 
384     private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
385 
DeviceConfigListener()386         DeviceConfigListener() {
387             super();
388             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_GAME_OVERLAY,
389                     mContext.getMainExecutor(), this);
390         }
391 
392         @Override
onPropertiesChanged(Properties properties)393         public void onPropertiesChanged(Properties properties) {
394             final String[] packageNames = properties.getKeyset().toArray(new String[0]);
395             updateConfigsForUser(ActivityManager.getCurrentUser(), true /*checkGamePackage*/,
396                     packageNames);
397         }
398 
399         @Override
finalize()400         public void finalize() {
401             DeviceConfig.removeOnPropertiesChangedListener(this);
402         }
403     }
404 
405     // Turn the raw string to the corresponding CompatChange id.
getCompatChangeId(String raw)406     static long getCompatChangeId(String raw) {
407         switch (raw) {
408             case "0.3":
409                 return DOWNSCALE_30;
410             case "0.35":
411                 return DOWNSCALE_35;
412             case "0.4":
413                 return DOWNSCALE_40;
414             case "0.45":
415                 return DOWNSCALE_45;
416             case "0.5":
417                 return DOWNSCALE_50;
418             case "0.55":
419                 return DOWNSCALE_55;
420             case "0.6":
421                 return DOWNSCALE_60;
422             case "0.65":
423                 return DOWNSCALE_65;
424             case "0.7":
425                 return DOWNSCALE_70;
426             case "0.75":
427                 return DOWNSCALE_75;
428             case "0.8":
429                 return DOWNSCALE_80;
430             case "0.85":
431                 return DOWNSCALE_85;
432             case "0.9":
433                 return DOWNSCALE_90;
434         }
435         return 0;
436     }
437 
438     public enum FrameRate {
439         FPS_DEFAULT(0),
440         FPS_30(30),
441         FPS_40(40),
442         FPS_45(45),
443         FPS_60(60),
444         FPS_90(90),
445         FPS_120(120),
446         FPS_INVALID(-1);
447 
448         public final int fps;
449 
FrameRate(int fps)450         FrameRate(int fps) {
451             this.fps = fps;
452         }
453     }
454 
455     // Turn the raw string to the corresponding fps int.
456     // Return 0 when disabling, -1 for invalid fps.
getFpsInt(String raw)457     static int getFpsInt(String raw) {
458         switch (raw) {
459             case "30":
460                 return FrameRate.FPS_30.fps;
461             case "40":
462                 return FrameRate.FPS_40.fps;
463             case "45":
464                 return FrameRate.FPS_45.fps;
465             case "60":
466                 return FrameRate.FPS_60.fps;
467             case "90":
468                 return FrameRate.FPS_90.fps;
469             case "120":
470                 return FrameRate.FPS_120.fps;
471             case "disable":
472             case "":
473                 return FrameRate.FPS_DEFAULT.fps;
474         }
475         return FrameRate.FPS_INVALID.fps;
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      */
setGameState(String packageName, @NonNull GameState gameState, @UserIdInt int userId)485     public void setGameState(String packageName, @NonNull GameState gameState,
486             @UserIdInt int userId) {
487         if (!isPackageGame(packageName, userId)) {
488             // Restrict to games only.
489             return;
490         }
491         final Message msg = mHandler.obtainMessage(SET_GAME_STATE);
492         final Bundle data = new Bundle();
493         data.putString(PACKAGE_NAME_MSG_KEY, packageName);
494         data.putInt(USER_ID_MSG_KEY, userId);
495         msg.setData(data);
496         msg.obj = gameState;
497         mHandler.sendMessage(msg);
498     }
499 
500     /**
501      * GamePackageConfiguration manages all game mode config details for its associated package.
502      */
503     @VisibleForTesting
504     public 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 mPerfModeOptedIn = false;
553         private boolean mBatteryModeOptedIn = false;
554         private boolean mAllowDownscale = true;
555         private boolean mAllowAngle = true;
556         private boolean mAllowFpsOverride = true;
557 
GamePackageConfiguration(String packageName)558         GamePackageConfiguration(String packageName) {
559             mPackageName = packageName;
560         }
561 
GamePackageConfiguration(String packageName, int userId)562         GamePackageConfiguration(String packageName, int userId) {
563             mPackageName = packageName;
564 
565             try {
566                 final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName,
567                         PackageManager.GET_META_DATA, userId);
568                 if (!parseInterventionFromXml(ai, packageName) && ai.metaData != null) {
569                     mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
570                     mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
571                     mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
572                     mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
573                 }
574             } catch (NameNotFoundException e) {
575                 // Not all packages are installed, hence ignore those that are not installed yet.
576                 Slog.v(TAG, "Failed to get package metadata");
577             }
578             final String configString = DeviceConfig.getProperty(
579                     DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName);
580             if (configString != null) {
581                 final String[] gameModeConfigStrings = configString.split(":");
582                 for (String gameModeConfigString : gameModeConfigStrings) {
583                     try {
584                         final KeyValueListParser parser = new KeyValueListParser(',');
585                         parser.setString(gameModeConfigString);
586                         addModeConfig(new GameModeConfiguration(parser));
587                     } catch (IllegalArgumentException e) {
588                         Slog.e(TAG, "Invalid config string");
589                     }
590                 }
591             }
592         }
593 
parseInterventionFromXml(ApplicationInfo ai, String packageName)594         private boolean parseInterventionFromXml(ApplicationInfo ai, String packageName) {
595             boolean xmlFound = false;
596             try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager,
597                     METADATA_GAME_MODE_CONFIG)) {
598                 if (parser == null) {
599                     Slog.v(TAG, "No " + METADATA_GAME_MODE_CONFIG
600                             + " meta-data found for package " + mPackageName);
601                 } else {
602                     xmlFound = true;
603                     final Resources resources = mPackageManager.getResourcesForApplication(
604                             packageName);
605                     final AttributeSet attributeSet = Xml.asAttributeSet(parser);
606                     int type;
607                     while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
608                             && type != XmlPullParser.START_TAG) {
609                         // Do nothing
610                     }
611 
612                     boolean isStartingTagGameModeConfig =
613                             GAME_MODE_CONFIG_NODE_NAME.equals(parser.getName());
614                     if (!isStartingTagGameModeConfig) {
615                         Slog.w(TAG, "Meta-data does not start with "
616                                 + GAME_MODE_CONFIG_NODE_NAME
617                                 + " tag");
618                     } else {
619                         final TypedArray array = resources.obtainAttributes(attributeSet,
620                                 com.android.internal.R.styleable.GameModeConfig);
621                         mPerfModeOptedIn = array.getBoolean(
622                                 GameModeConfig_supportsPerformanceGameMode, false);
623                         mBatteryModeOptedIn = array.getBoolean(
624                                 GameModeConfig_supportsBatteryGameMode,
625                                 false);
626                         mAllowDownscale = array.getBoolean(GameModeConfig_allowGameDownscaling,
627                                 true);
628                         mAllowAngle = array.getBoolean(GameModeConfig_allowGameAngleDriver, true);
629                         mAllowFpsOverride = array.getBoolean(GameModeConfig_allowGameFpsOverride,
630                                 true);
631                         array.recycle();
632                     }
633                 }
634             } catch (NameNotFoundException | XmlPullParserException | IOException ex) {
635                 // set flag back to default values when parsing fails
636                 mPerfModeOptedIn = false;
637                 mBatteryModeOptedIn = false;
638                 mAllowDownscale = true;
639                 mAllowAngle = true;
640                 mAllowFpsOverride = true;
641                 Slog.e(TAG, "Error while parsing XML meta-data for "
642                         + METADATA_GAME_MODE_CONFIG);
643             }
644             return xmlFound;
645         }
646 
getOrAddDefaultGameModeConfiguration(int gameMode)647         GameModeConfiguration getOrAddDefaultGameModeConfiguration(int gameMode) {
648             synchronized (mModeConfigLock) {
649                 mModeConfigs.putIfAbsent(gameMode, new GameModeConfiguration(gameMode));
650                 return mModeConfigs.get(gameMode);
651             }
652         }
653 
654         /**
655          * GameModeConfiguration contains all the values for all the interventions associated with
656          * a game mode.
657          */
658         @VisibleForTesting
659         public class GameModeConfiguration {
660             public static final String TAG = "GameManagerService_GameModeConfiguration";
661             public static final String MODE_KEY = "mode";
662             public static final String SCALING_KEY = "downscaleFactor";
663             public static final String FPS_KEY = "fps";
664             public static final String DEFAULT_SCALING = "1.0";
665             public static final String DEFAULT_FPS = "";
666             public static final boolean DEFAULT_USE_ANGLE = false;
667             public static final int DEFAULT_LOADING_BOOST_DURATION = -1;
668             public static final String ANGLE_KEY = "useAngle";
669             public static final String LOADING_BOOST_KEY = "loadingBoost";
670 
671             private final @GameMode int mGameMode;
672             private String mScaling = DEFAULT_SCALING;
673             private String mFps = DEFAULT_FPS;
674             private final boolean mUseAngle;
675             private final int mLoadingBoostDuration;
676 
GameModeConfiguration(int gameMode)677             GameModeConfiguration(int gameMode) {
678                 mGameMode = gameMode;
679                 mUseAngle = DEFAULT_USE_ANGLE;
680                 mLoadingBoostDuration = DEFAULT_LOADING_BOOST_DURATION;
681             }
682 
GameModeConfiguration(KeyValueListParser parser)683             GameModeConfiguration(KeyValueListParser parser) {
684                 mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
685                 // isGameModeOptedIn() returns if an app will handle all of the changes necessary
686                 // for a particular game mode. If so, the Android framework (i.e.
687                 // GameManagerService) will not do anything for the app (like window scaling or
688                 // using ANGLE).
689                 mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
690                         ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
691 
692                 mFps = mAllowFpsOverride && !willGamePerformOptimizations(mGameMode)
693                         ? parser.getString(FPS_KEY, DEFAULT_FPS) : DEFAULT_FPS;
694                 // We only want to use ANGLE if:
695                 // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
696                 // - The app has not opted in to performing the work itself AND
697                 // - The Phenotype config has enabled it.
698                 mUseAngle = mAllowAngle && !willGamePerformOptimizations(mGameMode)
699                         && parser.getBoolean(ANGLE_KEY, false);
700 
701                 mLoadingBoostDuration = willGamePerformOptimizations(mGameMode) ? -1
702                         : parser.getInt(LOADING_BOOST_KEY, -1);
703             }
704 
getGameMode()705             public int getGameMode() {
706                 return mGameMode;
707             }
708 
getScaling()709             public synchronized String getScaling() {
710                 return mScaling;
711             }
712 
getFps()713             public synchronized int getFps() {
714                 return GameManagerService.getFpsInt(mFps);
715             }
716 
getUseAngle()717             public boolean getUseAngle() {
718                 return mUseAngle;
719             }
720 
getLoadingBoostDuration()721             public int getLoadingBoostDuration() {
722                 return mLoadingBoostDuration;
723             }
724 
setScaling(String scaling)725             public synchronized void setScaling(String scaling) {
726                 mScaling = scaling;
727             }
728 
setFpsStr(String fpsStr)729             public synchronized void setFpsStr(String fpsStr) {
730                 mFps = fpsStr;
731             }
732 
isActive()733             public boolean isActive() {
734                 return (mGameMode == GameManager.GAME_MODE_STANDARD
735                         || mGameMode == GameManager.GAME_MODE_PERFORMANCE
736                         || mGameMode == GameManager.GAME_MODE_BATTERY)
737                         && !willGamePerformOptimizations(mGameMode);
738             }
739 
740             /**
741              * @hide
742              */
toString()743             public String toString() {
744                 return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:"
745                         + mUseAngle + ",Fps:" + mFps + ",Loading Boost Duration:"
746                         + mLoadingBoostDuration + "]";
747             }
748 
749             /**
750              * Get the corresponding compat change id for the current scaling string.
751              */
getCompatChangeId()752             public long getCompatChangeId() {
753                 return GameManagerService.getCompatChangeId(mScaling);
754             }
755         }
756 
getPackageName()757         public String getPackageName() {
758             return mPackageName;
759         }
760 
761         /**
762          * Returns if the app will assume full responsibility for the experience provided by this
763          * mode. If True, the system will not perform any interventions for the app.
764          *
765          * @return True if the app package has specified in its metadata either:
766          * "com.android.app.gamemode.performance.enabled" or
767          * "com.android.app.gamemode.battery.enabled" with a value of "true"
768          */
willGamePerformOptimizations(@ameMode int gameMode)769         public boolean willGamePerformOptimizations(@GameMode int gameMode) {
770             return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
771                     || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
772         }
773 
getAvailableGameModesBitfield()774         private int getAvailableGameModesBitfield() {
775             int field = 0;
776             synchronized (mModeConfigLock) {
777                 for (final int mode : mModeConfigs.keySet()) {
778                     field |= modeToBitmask(mode);
779                 }
780             }
781             if (mBatteryModeOptedIn) {
782                 field |= modeToBitmask(GameManager.GAME_MODE_BATTERY);
783             }
784             if (mPerfModeOptedIn) {
785                 field |= modeToBitmask(GameManager.GAME_MODE_PERFORMANCE);
786             }
787             // The lowest bit is reserved for UNSUPPORTED, STANDARD is supported if we support any
788             // other mode.
789             if (field > 1) {
790                 field |= modeToBitmask(GameManager.GAME_MODE_STANDARD);
791             } else {
792                 field |= modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
793             }
794             return field;
795         }
796 
797         /**
798          * Get an array of a package's available game modes.
799          */
getAvailableGameModes()800         public @GameMode int[] getAvailableGameModes() {
801             final int modesBitfield = getAvailableGameModesBitfield();
802             int[] modes = new int[Integer.bitCount(modesBitfield)];
803             int i = 0;
804             final int gameModeInHighestBit =
805                     Integer.numberOfTrailingZeros(Integer.highestOneBit(modesBitfield));
806             for (int mode = 0; mode <= gameModeInHighestBit; ++mode) {
807                 if (((modesBitfield >> mode) & 1) != 0) {
808                     modes[i++] = mode;
809                 }
810             }
811             return modes;
812         }
813 
814         /**
815          * Get a GameModeConfiguration for a given game mode.
816          *
817          * @return The package's GameModeConfiguration for the provided mode or null if absent
818          */
getGameModeConfiguration(@ameMode int gameMode)819         public GameModeConfiguration getGameModeConfiguration(@GameMode int gameMode) {
820             synchronized (mModeConfigLock) {
821                 return mModeConfigs.get(gameMode);
822             }
823         }
824 
825         /**
826          * Insert a new GameModeConfiguration
827          */
addModeConfig(GameModeConfiguration config)828         public void addModeConfig(GameModeConfiguration config) {
829             if (config.isActive()) {
830                 synchronized (mModeConfigLock) {
831                     mModeConfigs.put(config.getGameMode(), config);
832                 }
833             } else {
834                 Slog.w(TAG, "Attempt to add inactive game mode config for "
835                         + mPackageName + ":" + config.toString());
836             }
837         }
838 
isActive()839         public boolean isActive() {
840             synchronized (mModeConfigLock) {
841                 return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
842             }
843         }
844 
copyAndApplyOverride(GamePackageConfiguration overrideConfig)845         GamePackageConfiguration copyAndApplyOverride(GamePackageConfiguration overrideConfig) {
846             GamePackageConfiguration copy = new GamePackageConfiguration(mPackageName);
847             // if a game mode is overridden, we treat it with the highest priority and reset any
848             // opt-in game modes so that interventions are always executed.
849             copy.mPerfModeOptedIn = mPerfModeOptedIn && !(overrideConfig != null
850                     && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)
851                     != null);
852             copy.mBatteryModeOptedIn = mBatteryModeOptedIn && !(overrideConfig != null
853                     && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY)
854                     != null);
855 
856             // if any game mode is overridden, we will consider all interventions forced-active,
857             // this can be done more granular by checking if a specific intervention is
858             // overridden under each game mode override, but only if necessary.
859             copy.mAllowDownscale = mAllowDownscale || overrideConfig != null;
860             copy.mAllowAngle = mAllowAngle || overrideConfig != null;
861             copy.mAllowFpsOverride = mAllowFpsOverride || overrideConfig != null;
862             if (overrideConfig != null) {
863                 synchronized (copy.mModeConfigLock) {
864                     synchronized (mModeConfigLock) {
865                         for (Map.Entry<Integer, GameModeConfiguration> entry :
866                                 mModeConfigs.entrySet()) {
867                             copy.mModeConfigs.put(entry.getKey(), entry.getValue());
868                         }
869                     }
870                     synchronized (overrideConfig.mModeConfigLock) {
871                         for (Map.Entry<Integer, GameModeConfiguration> entry :
872                                 overrideConfig.mModeConfigs.entrySet()) {
873                             copy.mModeConfigs.put(entry.getKey(), entry.getValue());
874                         }
875                     }
876                 }
877             }
878             return copy;
879         }
880 
toString()881         public String toString() {
882             synchronized (mModeConfigLock) {
883                 return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]";
884             }
885         }
886     }
887 
888     /**
889      * SystemService lifecycle for GameService.
890      *
891      * @hide
892      */
893     public static class Lifecycle extends SystemService {
894         private GameManagerService mService;
895 
Lifecycle(Context context)896         public Lifecycle(Context context) {
897             super(context);
898         }
899 
900         @Override
onStart()901         public void onStart() {
902             final Context context = getContext();
903             mService = new GameManagerService(context);
904             publishBinderService(Context.GAME_SERVICE, mService);
905             mService.registerDeviceConfigListener();
906             mService.registerPackageReceiver();
907         }
908 
909         @Override
onBootPhase(int phase)910         public void onBootPhase(int phase) {
911             if (phase == PHASE_BOOT_COMPLETED) {
912                 mService.onBootCompleted();
913             }
914         }
915 
916         @Override
onUserStarting(@onNull TargetUser user)917         public void onUserStarting(@NonNull TargetUser user) {
918             mService.onUserStarting(user);
919         }
920 
921         @Override
onUserUnlocking(@onNull TargetUser user)922         public void onUserUnlocking(@NonNull TargetUser user) {
923             mService.onUserUnlocking(user);
924         }
925 
926         @Override
onUserStopping(@onNull TargetUser user)927         public void onUserStopping(@NonNull TargetUser user) {
928             mService.onUserStopping(user);
929         }
930 
931         @Override
onUserSwitching(@ullable TargetUser from, @NonNull TargetUser to)932         public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
933             mService.onUserSwitching(from, to);
934         }
935     }
936 
isValidPackageName(String packageName, int userId)937     private boolean isValidPackageName(String packageName, int userId) {
938         try {
939             return mPackageManager.getPackageUidAsUser(packageName, userId)
940                     == Binder.getCallingUid();
941         } catch (NameNotFoundException e) {
942             return false;
943         }
944     }
945 
checkPermission(String permission)946     private void checkPermission(String permission) throws SecurityException {
947         if (mContext.checkCallingOrSelfPermission(permission)
948                 != PackageManager.PERMISSION_GRANTED) {
949             throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
950                     + ", must have permission " + permission);
951         }
952     }
953 
getAvailableGameModesUnchecked(String packageName)954     private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
955         final GamePackageConfiguration config = getConfig(packageName);
956         if (config == null) {
957             return new int[]{};
958         }
959         return config.getAvailableGameModes();
960     }
961 
isPackageGame(String packageName, @UserIdInt int userId)962     private boolean isPackageGame(String packageName, @UserIdInt int userId) {
963         try {
964             final ApplicationInfo applicationInfo = mPackageManager
965                     .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
966             return applicationInfo.category == ApplicationInfo.CATEGORY_GAME;
967         } catch (PackageManager.NameNotFoundException e) {
968             return false;
969         }
970     }
971 
972     /**
973      * Get an array of game modes available for a given package.
974      * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
975      */
976     @Override
977     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
getAvailableGameModes(String packageName)978     public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException {
979         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
980         return getAvailableGameModesUnchecked(packageName);
981     }
982 
getGameModeFromSettings(String packageName, @UserIdInt int userId)983     private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) {
984         synchronized (mLock) {
985             if (!mSettings.containsKey(userId)) {
986                 Slog.w(TAG, "User ID '" + userId + "' does not have a Game Mode"
987                         + " selected for package: '" + packageName + "'");
988                 return GameManager.GAME_MODE_UNSUPPORTED;
989             }
990 
991             return mSettings.get(userId).getGameModeLocked(packageName);
992         }
993     }
994 
995     /**
996      * Get the Game Mode for the package name.
997      * Verifies that the calling process is for the matching package UID or has
998      * {@link android.Manifest.permission#MANAGE_GAME_MODE}.
999      */
1000     @Override
getGameMode(@onNull String packageName, @UserIdInt int userId)1001     public @GameMode int getGameMode(@NonNull String packageName, @UserIdInt int userId)
1002             throws SecurityException {
1003         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1004                 Binder.getCallingUid(), userId, false, true, "getGameMode",
1005                 "com.android.server.app.GameManagerService");
1006 
1007         // Restrict to games only.
1008         if (!isPackageGame(packageName, userId)) {
1009             // The game mode for applications that are not identified as game is always
1010             // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)}
1011             return GameManager.GAME_MODE_UNSUPPORTED;
1012         }
1013 
1014         // This function handles two types of queries:
1015         // 1) A normal, non-privileged app querying its own Game Mode.
1016         // 2) A privileged system service querying the Game Mode of another package.
1017         // The least privileged case is a normal app performing a query, so check that first and
1018         // return a value if the package name is valid. Next, check if the caller has the necessary
1019         // permission and return a value. Do this check last, since it can throw an exception.
1020         if (isValidPackageName(packageName, userId)) {
1021             return getGameModeFromSettings(packageName, userId);
1022         }
1023 
1024         // Since the package name doesn't match, check the caller has the necessary permission.
1025         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1026         return getGameModeFromSettings(packageName, userId);
1027     }
1028 
1029     /**
1030      * Get the GameModeInfo for the package name.
1031      * Verifies that the calling process is for the matching package UID or has
1032      * {@link android.Manifest.permission#MANAGE_GAME_MODE}. If the package is not a game,
1033      * null is always returned.
1034      */
1035     @Override
1036     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
1037     @Nullable
getGameModeInfo(@onNull String packageName, @UserIdInt int userId)1038     public GameModeInfo getGameModeInfo(@NonNull String packageName, @UserIdInt int userId) {
1039         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1040                 Binder.getCallingUid(), userId, false, true, "getGameModeInfo",
1041                 "com.android.server.app.GameManagerService");
1042 
1043         // Check the caller has the necessary permission.
1044         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1045 
1046         // Restrict to games only.
1047         if (!isPackageGame(packageName, userId)) {
1048             return null;
1049         }
1050 
1051         final @GameMode int activeGameMode = getGameModeFromSettings(packageName, userId);
1052         final @GameMode int[] availableGameModes = getAvailableGameModesUnchecked(packageName);
1053 
1054         return new GameModeInfo(activeGameMode, availableGameModes);
1055     }
1056 
1057     /**
1058      * Sets the Game Mode for the package name.
1059      * Verifies that the calling process has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
1060      */
1061     @Override
1062     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
setGameMode(String packageName, @GameMode int gameMode, int userId)1063     public void setGameMode(String packageName, @GameMode int gameMode, int userId)
1064             throws SecurityException {
1065         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1066 
1067         if (!isPackageGame(packageName, userId)) {
1068             // Restrict to games only.
1069             return;
1070         }
1071 
1072         synchronized (mLock) {
1073             userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1074                     Binder.getCallingUid(), userId, false, true, "setGameMode",
1075                     "com.android.server.app.GameManagerService");
1076 
1077             if (!mSettings.containsKey(userId)) {
1078                 return;
1079             }
1080             GameManagerSettings userSettings = mSettings.get(userId);
1081             userSettings.setGameModeLocked(packageName, gameMode);
1082             final Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
1083             msg.obj = userId;
1084             if (!mHandler.hasEqualMessages(WRITE_SETTINGS, userId)) {
1085                 mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
1086             }
1087         }
1088         updateInterventions(packageName, gameMode, userId);
1089         final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
1090         msg.obj = userId;
1091         if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
1092             mHandler.sendMessage(msg);
1093         }
1094     }
1095 
1096     /**
1097      * Get if ANGLE is enabled for the package for the currently enabled game mode.
1098      * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
1099      */
1100     @Override
1101     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
isAngleEnabled(String packageName, int userId)1102     public @GameMode boolean isAngleEnabled(String packageName, int userId)
1103             throws SecurityException {
1104         final int gameMode = getGameMode(packageName, userId);
1105         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
1106             return false;
1107         }
1108         final GamePackageConfiguration config;
1109         synchronized (mDeviceConfigLock) {
1110             config = mConfigs.get(packageName);
1111             if (config == null) {
1112                 return false;
1113             }
1114         }
1115         GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
1116                 config.getGameModeConfiguration(gameMode);
1117         if (gameModeConfiguration == null) {
1118             return false;
1119         }
1120         return gameModeConfiguration.getUseAngle();
1121     }
1122 
1123     /**
1124      * If loading boost is applicable for the package for the currently enabled game mode, return
1125      * the boost duration. If no configuration is available for the selected package or mode, the
1126      * default is returned.
1127      */
1128     @VisibleForTesting
getLoadingBoostDuration(String packageName, int userId)1129     public int getLoadingBoostDuration(String packageName, int userId)
1130             throws SecurityException {
1131         final int gameMode = getGameMode(packageName, userId);
1132         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
1133             return -1;
1134         }
1135         final GamePackageConfiguration config;
1136         synchronized (mDeviceConfigLock) {
1137             config = mConfigs.get(packageName);
1138         }
1139         if (config == null) {
1140             return -1;
1141         }
1142         GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
1143                 config.getGameModeConfiguration(gameMode);
1144         if (gameModeConfiguration == null) {
1145             return -1;
1146         }
1147         return gameModeConfiguration.getLoadingBoostDuration();
1148     }
1149 
1150     /**
1151      * If loading boost is enabled, invoke it.
1152      */
1153     @Override
1154     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
notifyGraphicsEnvironmentSetup(String packageName, int userId)1155     @GameMode public void notifyGraphicsEnvironmentSetup(String packageName, int userId)
1156             throws SecurityException {
1157         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1158                 Binder.getCallingUid(), userId, false, true, "notifyGraphicsEnvironmentSetup",
1159                 "com.android.server.app.GameManagerService");
1160 
1161         // Restrict to games only.
1162         if (!isPackageGame(packageName, userId)) {
1163             return;
1164         }
1165 
1166         if (!isValidPackageName(packageName, userId)) {
1167             return;
1168         }
1169 
1170         final int gameMode = getGameMode(packageName, userId);
1171         if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
1172             return;
1173         }
1174         int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
1175         if (loadingBoostDuration != -1) {
1176             if (loadingBoostDuration == 0 || loadingBoostDuration > LOADING_BOOST_MAX_DURATION) {
1177                 loadingBoostDuration = LOADING_BOOST_MAX_DURATION;
1178             }
1179             if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
1180                 // The loading mode has already been set and is waiting to be unset. It is not
1181                 // required to set the mode again and we should replace the queued cancel
1182                 // instruction.
1183                 mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
1184             } else {
1185                 mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, true);
1186             }
1187 
1188             mHandler.sendMessageDelayed(
1189                     mHandler.obtainMessage(CANCEL_GAME_LOADING_MODE), loadingBoostDuration);
1190         }
1191     }
1192 
1193     /**
1194      * Sets the game service provider to a given package, meant for testing.
1195      *
1196      * <p>This setting persists until the next call or until the next reboot.
1197      *
1198      * <p>Checks that the caller has {@link android.Manifest.permission#SET_GAME_SERVICE}.
1199      */
1200     @Override
1201     @RequiresPermission(Manifest.permission.SET_GAME_SERVICE)
setGameServiceProvider(@ullable String packageName)1202     public void setGameServiceProvider(@Nullable String packageName) throws SecurityException {
1203         checkPermission(Manifest.permission.SET_GAME_SERVICE);
1204 
1205         if (mGameServiceController == null) {
1206             return;
1207         }
1208 
1209         mGameServiceController.setGameServiceProvider(packageName);
1210     }
1211 
1212     /**
1213      * Notified when boot is completed.
1214      */
1215     @VisibleForTesting
onBootCompleted()1216     void onBootCompleted() {
1217         Slog.d(TAG, "onBootCompleted");
1218         if (mGameServiceController != null) {
1219             mGameServiceController.onBootComplete();
1220         }
1221     }
1222 
onUserStarting(@onNull TargetUser user)1223     void onUserStarting(@NonNull TargetUser user) {
1224         final int userId = user.getUserIdentifier();
1225 
1226         synchronized (mLock) {
1227             if (!mSettings.containsKey(userId)) {
1228                 GameManagerSettings userSettings =
1229                         new GameManagerSettings(Environment.getDataSystemDeDirectory(userId));
1230                 mSettings.put(userId, userSettings);
1231                 userSettings.readPersistentDataLocked();
1232             }
1233         }
1234         final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
1235         msg.obj = userId;
1236         mHandler.sendMessage(msg);
1237 
1238         if (mGameServiceController != null) {
1239             mGameServiceController.notifyUserStarted(user);
1240         }
1241     }
1242 
onUserUnlocking(@onNull TargetUser user)1243     void onUserUnlocking(@NonNull TargetUser user) {
1244         if (mGameServiceController != null) {
1245             mGameServiceController.notifyUserUnlocking(user);
1246         }
1247     }
1248 
onUserStopping(TargetUser user)1249     void onUserStopping(TargetUser user) {
1250         final int userId = user.getUserIdentifier();
1251 
1252         synchronized (mLock) {
1253             if (!mSettings.containsKey(userId)) {
1254                 return;
1255             }
1256             final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
1257             msg.obj = userId;
1258             mHandler.sendMessage(msg);
1259         }
1260 
1261         if (mGameServiceController != null) {
1262             mGameServiceController.notifyUserStopped(user);
1263         }
1264     }
1265 
onUserSwitching(TargetUser from, TargetUser to)1266     void onUserSwitching(TargetUser from, TargetUser to) {
1267         final int toUserId = to.getUserIdentifier();
1268         // we want to re-populate the setting when switching user as the device config may have
1269         // changed, which will only update for the previous user, see
1270         // DeviceConfigListener#onPropertiesChanged.
1271         final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
1272         msg.obj = toUserId;
1273         mHandler.sendMessage(msg);
1274         if (mGameServiceController != null) {
1275             mGameServiceController.notifyNewForegroundUser(to);
1276         }
1277     }
1278 
1279     /**
1280      * @hide
1281      */
1282     @VisibleForTesting
disableCompatScale(String packageName)1283     public void disableCompatScale(String packageName) {
1284         final long uid = Binder.clearCallingIdentity();
1285         try {
1286             Slog.i(TAG, "Disabling downscale for " + packageName);
1287             final ArrayMap<Long, PackageOverride> overrides = new ArrayMap<>();
1288             overrides.put(DOWNSCALED, COMPAT_DISABLED);
1289             final CompatibilityOverrideConfig changeConfig = new CompatibilityOverrideConfig(
1290                     overrides);
1291             try {
1292                 mPlatformCompat.putOverridesOnReleaseBuilds(changeConfig, packageName);
1293             } catch (RemoteException e) {
1294                 Slog.e(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e);
1295             }
1296         } finally {
1297             Binder.restoreCallingIdentity(uid);
1298         }
1299     }
1300 
1301     /**
1302      * Remove frame rate override due to mode switch
1303      */
resetFps(String packageName, @UserIdInt int userId)1304     private void resetFps(String packageName, @UserIdInt int userId) {
1305         try {
1306             final float fps = 0.0f;
1307             final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
1308             setOverrideFrameRate(uid, fps);
1309         } catch (PackageManager.NameNotFoundException e) {
1310             return;
1311         }
1312     }
1313 
enableCompatScale(String packageName, long scaleId)1314     private void enableCompatScale(String packageName, long scaleId) {
1315         final long uid = Binder.clearCallingIdentity();
1316         try {
1317             Slog.i(TAG, "Enabling downscale: " + scaleId + " for " + packageName);
1318             final ArrayMap<Long, PackageOverride> overrides = new ArrayMap<>();
1319             overrides.put(DOWNSCALED, COMPAT_ENABLED);
1320             overrides.put(DOWNSCALE_30, COMPAT_DISABLED);
1321             overrides.put(DOWNSCALE_35, COMPAT_DISABLED);
1322             overrides.put(DOWNSCALE_40, COMPAT_DISABLED);
1323             overrides.put(DOWNSCALE_45, COMPAT_DISABLED);
1324             overrides.put(DOWNSCALE_50, COMPAT_DISABLED);
1325             overrides.put(DOWNSCALE_55, COMPAT_DISABLED);
1326             overrides.put(DOWNSCALE_60, COMPAT_DISABLED);
1327             overrides.put(DOWNSCALE_65, COMPAT_DISABLED);
1328             overrides.put(DOWNSCALE_70, COMPAT_DISABLED);
1329             overrides.put(DOWNSCALE_75, COMPAT_DISABLED);
1330             overrides.put(DOWNSCALE_80, COMPAT_DISABLED);
1331             overrides.put(DOWNSCALE_85, COMPAT_DISABLED);
1332             overrides.put(DOWNSCALE_90, COMPAT_DISABLED);
1333             overrides.put(scaleId, COMPAT_ENABLED);
1334             final CompatibilityOverrideConfig changeConfig = new CompatibilityOverrideConfig(
1335                     overrides);
1336             try {
1337                 mPlatformCompat.putOverridesOnReleaseBuilds(changeConfig, packageName);
1338             } catch (RemoteException e) {
1339                 Slog.e(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e);
1340             }
1341         } finally {
1342             Binder.restoreCallingIdentity(uid);
1343         }
1344     }
1345 
updateCompatModeDownscale(GamePackageConfiguration packageConfig, String packageName, @GameMode int gameMode)1346     private void updateCompatModeDownscale(GamePackageConfiguration packageConfig,
1347             String packageName, @GameMode int gameMode) {
1348 
1349         if (DEBUG) {
1350             Slog.v(TAG, dumpDeviceConfigs());
1351         }
1352         final GamePackageConfiguration.GameModeConfiguration modeConfig =
1353                 packageConfig.getGameModeConfiguration(gameMode);
1354         if (modeConfig == null) {
1355             Slog.i(TAG, "Game mode " + gameMode + " not found for " + packageName);
1356             return;
1357         }
1358         long scaleId = modeConfig.getCompatChangeId();
1359         if (scaleId == 0) {
1360             Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for "
1361                     + packageName);
1362             return;
1363         }
1364 
1365         enableCompatScale(packageName, scaleId);
1366     }
1367 
modeToBitmask(@ameMode int gameMode)1368     private int modeToBitmask(@GameMode int gameMode) {
1369         return (1 << gameMode);
1370     }
1371 
bitFieldContainsModeBitmask(int bitField, @GameMode int gameMode)1372     private boolean bitFieldContainsModeBitmask(int bitField, @GameMode int gameMode) {
1373         return (bitField & modeToBitmask(gameMode)) != 0;
1374     }
1375 
1376     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
updateUseAngle(String packageName, @GameMode int gameMode)1377     private void updateUseAngle(String packageName, @GameMode int gameMode) {
1378         // TODO (b/188475576): Nothing to do yet. Remove if it's still empty when we're ready to
1379         // ship.
1380     }
1381 
1382 
updateFps(GamePackageConfiguration packageConfig, String packageName, @GameMode int gameMode, @UserIdInt int userId)1383     private void updateFps(GamePackageConfiguration packageConfig, String packageName,
1384             @GameMode int gameMode, @UserIdInt int userId) {
1385         final GamePackageConfiguration.GameModeConfiguration modeConfig =
1386                 packageConfig.getGameModeConfiguration(gameMode);
1387         if (modeConfig == null) {
1388             Slog.d(TAG, "Game mode " + gameMode + " not found for " + packageName);
1389             return;
1390         }
1391         try {
1392             final float fps = modeConfig.getFps();
1393             final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
1394             setOverrideFrameRate(uid, fps);
1395         } catch (PackageManager.NameNotFoundException e) {
1396             return;
1397         }
1398     }
1399 
1400 
updateInterventions(String packageName, @GameMode int gameMode, @UserIdInt int userId)1401     private void updateInterventions(String packageName,
1402             @GameMode int gameMode, @UserIdInt int userId) {
1403         final GamePackageConfiguration packageConfig = getConfig(packageName);
1404         if (gameMode == GameManager.GAME_MODE_STANDARD
1405                 || gameMode == GameManager.GAME_MODE_UNSUPPORTED || packageConfig == null
1406                 || packageConfig.willGamePerformOptimizations(gameMode)) {
1407             disableCompatScale(packageName);
1408             resetFps(packageName, userId);
1409             if (packageConfig == null) {
1410                 Slog.v(TAG, "Package configuration not found for " + packageName);
1411                 return;
1412             }
1413         } else {
1414             updateFps(packageConfig, packageName, gameMode, userId);
1415             updateCompatModeDownscale(packageConfig, packageName, gameMode);
1416         }
1417         updateUseAngle(packageName, gameMode);
1418     }
1419 
1420     /**
1421      * Set the override Game Mode Configuration.
1422      * Update the config if exists, create one if not.
1423      */
1424     @VisibleForTesting
1425     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
setGameModeConfigOverride(String packageName, @UserIdInt int userId, @GameMode int gameMode, String fpsStr, String scaling)1426     public void setGameModeConfigOverride(String packageName, @UserIdInt int userId,
1427             @GameMode int gameMode, String fpsStr, String scaling) throws SecurityException {
1428         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1429         synchronized (mLock) {
1430             if (!mSettings.containsKey(userId)) {
1431                 return;
1432             }
1433         }
1434         // Adding override game mode configuration of the given package name
1435         GamePackageConfiguration overrideConfig;
1436         synchronized (mOverrideConfigLock) {
1437             // look for the existing override GamePackageConfiguration
1438             overrideConfig = mOverrideConfigs.get(packageName);
1439             if (overrideConfig == null) {
1440                 overrideConfig = new GamePackageConfiguration(packageName);
1441                 mOverrideConfigs.put(packageName, overrideConfig);
1442             }
1443         }
1444         // modify GameModeConfiguration intervention settings
1445         GamePackageConfiguration.GameModeConfiguration overrideModeConfig =
1446                 overrideConfig.getOrAddDefaultGameModeConfiguration(gameMode);
1447 
1448         if (fpsStr != null) {
1449             overrideModeConfig.setFpsStr(fpsStr);
1450         } else {
1451             overrideModeConfig.setFpsStr(
1452                     GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS);
1453         }
1454         if (scaling != null) {
1455             overrideModeConfig.setScaling(scaling);
1456         } else {
1457             overrideModeConfig.setScaling(
1458                     GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING);
1459         }
1460         Slog.i(TAG, "Package Name: " + packageName
1461                 + " FPS: " + String.valueOf(overrideModeConfig.getFps())
1462                 + " Scaling: " + overrideModeConfig.getScaling());
1463         setGameMode(packageName, gameMode, userId);
1464     }
1465 
1466     /**
1467      * Reset the overridden gameModeConfiguration of the given mode.
1468      * Remove the override config if game mode is not specified.
1469      */
1470     @VisibleForTesting
1471     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
resetGameModeConfigOverride(String packageName, @UserIdInt int userId, @GameMode int gameModeToReset)1472     public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId,
1473             @GameMode int gameModeToReset) throws SecurityException {
1474         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
1475         synchronized (mLock) {
1476             if (!mSettings.containsKey(userId)) {
1477                 return;
1478             }
1479         }
1480 
1481         // resets GamePackageConfiguration of a given packageName.
1482         // If a gameMode is specified, only reset the GameModeConfiguration of the gameMode.
1483         if (gameModeToReset != -1) {
1484             GamePackageConfiguration overrideConfig = null;
1485             synchronized (mOverrideConfigLock) {
1486                 overrideConfig = mOverrideConfigs.get(packageName);
1487             }
1488 
1489             GamePackageConfiguration config = null;
1490             synchronized (mDeviceConfigLock) {
1491                 config = mConfigs.get(packageName);
1492             }
1493 
1494             int[] modes = overrideConfig.getAvailableGameModes();
1495 
1496             // First check if the mode to reset exists
1497             boolean isGameModeExist = false;
1498             for (int mode : modes) {
1499                 if (gameModeToReset == mode) {
1500                     isGameModeExist = true;
1501                 }
1502             }
1503             if (!isGameModeExist) {
1504                 return;
1505             }
1506 
1507             // If the game mode to reset is the only mode other than standard mode,
1508             // The override config is removed.
1509             if (modes.length <= 2) {
1510                 synchronized (mOverrideConfigLock) {
1511                     mOverrideConfigs.remove(packageName);
1512                 }
1513             } else {
1514                 // otherwise we reset the mode by copying the original config.
1515                 overrideConfig.addModeConfig(config.getGameModeConfiguration(gameModeToReset));
1516             }
1517         } else {
1518             synchronized (mOverrideConfigLock) {
1519                 // remove override config if there is one
1520                 mOverrideConfigs.remove(packageName);
1521             }
1522         }
1523 
1524         // Make sure after resetting the game mode is still supported.
1525         // If not, set the game mode to standard
1526         int gameMode = getGameMode(packageName, userId);
1527 
1528         final GamePackageConfiguration config = getConfig(packageName);
1529         final int newGameMode = getNewGameMode(gameMode, config);
1530         if (gameMode != newGameMode) {
1531             setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
1532             return;
1533         }
1534         setGameMode(packageName, gameMode, userId);
1535     }
1536 
getNewGameMode(int gameMode, GamePackageConfiguration config)1537     private int getNewGameMode(int gameMode, GamePackageConfiguration config) {
1538         int newGameMode = gameMode;
1539         if (config != null) {
1540             int modesBitfield = config.getAvailableGameModesBitfield();
1541             // Remove UNSUPPORTED to simplify the logic here, since we really just
1542             // want to check if we support selectable game modes
1543             modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
1544             if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) {
1545                 if (bitFieldContainsModeBitmask(modesBitfield,
1546                         GameManager.GAME_MODE_STANDARD)) {
1547                     // If the current set mode isn't supported,
1548                     // but we support STANDARD, then set the mode to STANDARD.
1549                     newGameMode = GameManager.GAME_MODE_STANDARD;
1550                 } else {
1551                     // If we don't support any game modes, then set to UNSUPPORTED
1552                     newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
1553                 }
1554             }
1555         } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) {
1556             // If we have no config for the package, but the configured mode is not
1557             // UNSUPPORTED, then set to UNSUPPORTED
1558             newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
1559         }
1560         return newGameMode;
1561     }
1562 
1563     /**
1564      * Returns the string listing all the interventions currently set to a game.
1565      */
getInterventionList(String packageName)1566     public String getInterventionList(String packageName) {
1567         final GamePackageConfiguration packageConfig = getConfig(packageName);
1568         final StringBuilder listStrSb = new StringBuilder();
1569         if (packageConfig == null) {
1570             listStrSb.append("\n No intervention found for package ")
1571                     .append(packageName);
1572             return listStrSb.toString();
1573         }
1574         listStrSb.append("\n")
1575                 .append(packageConfig.toString());
1576         return listStrSb.toString();
1577     }
1578 
1579     /**
1580      * @hide
1581      */
1582     @VisibleForTesting
updateConfigsForUser(@serIdInt int userId, boolean checkGamePackage, String... packageNames)1583     void updateConfigsForUser(@UserIdInt int userId, boolean checkGamePackage,
1584             String... packageNames) {
1585         if (checkGamePackage) {
1586             packageNames = Arrays.stream(packageNames).filter(
1587                     p -> isPackageGame(p, userId)).toArray(String[]::new);
1588         }
1589         try {
1590             synchronized (mDeviceConfigLock) {
1591                 for (final String packageName : packageNames) {
1592                     final GamePackageConfiguration config =
1593                             new GamePackageConfiguration(packageName, userId);
1594                     if (config.isActive()) {
1595                         if (DEBUG) {
1596                             Slog.i(TAG, "Adding config: " + config.toString());
1597                         }
1598                         mConfigs.put(packageName, config);
1599                     } else {
1600                         if (DEBUG) {
1601                             Slog.w(TAG, "Inactive package config for "
1602                                     + config.getPackageName() + ":" + config.toString());
1603                         }
1604                         mConfigs.remove(packageName);
1605                     }
1606                 }
1607             }
1608             synchronized (mLock) {
1609                 if (!mSettings.containsKey(userId)) {
1610                     return;
1611                 }
1612             }
1613             for (final String packageName : packageNames) {
1614                 int gameMode = getGameMode(packageName, userId);
1615                 // Make sure the user settings and package configs don't conflict.
1616                 // I.e. the user setting is set to a mode that no longer available due to
1617                 // config/manifest changes.
1618                 // Most of the time we won't have to change anything.
1619                 GamePackageConfiguration config = null;
1620                 synchronized (mDeviceConfigLock) {
1621                     config = mConfigs.get(packageName);
1622                 }
1623                 final int newGameMode = getNewGameMode(gameMode, config);
1624                 if (newGameMode != gameMode) {
1625                     setGameMode(packageName, newGameMode, userId);
1626                 } else {
1627                     // Make sure we handle the case when the interventions are changed while
1628                     // the game mode remains the same. We call only updateInterventions() here.
1629                     updateInterventions(packageName, gameMode, userId);
1630                 }
1631             }
1632         } catch (Exception e) {
1633             Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e);
1634         }
1635 
1636         final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
1637         msg.obj = userId;
1638         if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
1639             mHandler.sendMessage(msg);
1640         }
1641     }
1642 
1643     /*
1644      Write the interventions and mode of each game to file /system/data/game_mode_intervention.list
1645      Each line will contain the information of each game, separated by tab.
1646      The format of the output is:
1647      <package name> <UID> <current mode> <game mode 1> <interventions> <game mode 2> <interventions>
1648      For example:
1649      com.android.app1   1425    1   2   angle=0,scaling=1.0,fps=60  3   angle=1,scaling=0.5,fps=30
1650      */
writeGameModeInterventionsToFile(@serIdInt int userId)1651     private void writeGameModeInterventionsToFile(@UserIdInt int userId) {
1652         FileOutputStream fileOutputStream = null;
1653         BufferedWriter bufferedWriter;
1654         try {
1655             fileOutputStream = mGameModeInterventionListFile.startWrite();
1656             bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream,
1657                     Charset.defaultCharset()));
1658 
1659             final StringBuilder sb = new StringBuilder();
1660             final List<String> installedGamesList = getInstalledGamePackageNamesByAllUsers(userId);
1661             for (final String packageName : installedGamesList) {
1662                 GamePackageConfiguration packageConfig = getConfig(packageName);
1663                 if (packageConfig == null) {
1664                     continue;
1665                 }
1666                 sb.append(packageName);
1667                 sb.append("\t");
1668                 sb.append(mPackageManager.getPackageUidAsUser(packageName, userId));
1669                 sb.append("\t");
1670                 sb.append(getGameMode(packageName, userId));
1671                 sb.append("\t");
1672                 final int[] modes = packageConfig.getAvailableGameModes();
1673                 for (int mode : modes) {
1674                     final GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
1675                             packageConfig.getGameModeConfiguration(mode);
1676                     if (gameModeConfiguration == null) {
1677                         continue;
1678                     }
1679                     sb.append(mode);
1680                     sb.append("\t");
1681                     final int useAngle = gameModeConfiguration.getUseAngle() ? 1 : 0;
1682                     sb.append(TextUtils.formatSimple("angle=%d", useAngle));
1683                     sb.append(",");
1684                     final String scaling = gameModeConfiguration.getScaling();
1685                     sb.append("scaling=");
1686                     sb.append(scaling);
1687                     sb.append(",");
1688                     final int fps = gameModeConfiguration.getFps();
1689                     sb.append(TextUtils.formatSimple("fps=%d", fps));
1690                     sb.append("\t");
1691                 }
1692                 sb.append("\n");
1693             }
1694             bufferedWriter.append(sb);
1695             bufferedWriter.flush();
1696             FileUtils.sync(fileOutputStream);
1697             mGameModeInterventionListFile.finishWrite(fileOutputStream);
1698         } catch (Exception e) {
1699             mGameModeInterventionListFile.failWrite(fileOutputStream);
1700             Slog.wtf(TAG, "Failed to write game_mode_intervention.list, exception " + e);
1701         }
1702         return;
1703     }
1704 
getAllUserIds(@serIdInt int currentUserId)1705     private int[] getAllUserIds(@UserIdInt int currentUserId) {
1706         final List<UserInfo> users = mUserManager.getUsers();
1707         int[] userIds = new int[users.size()];
1708         for (int i = 0; i < userIds.length; ++i) {
1709             userIds[i] = users.get(i).id;
1710         }
1711         if (currentUserId != -1) {
1712             userIds = ArrayUtils.appendInt(userIds, currentUserId);
1713         }
1714         return userIds;
1715     }
1716 
getInstalledGamePackageNames(@serIdInt int userId)1717     private String[] getInstalledGamePackageNames(@UserIdInt int userId) {
1718         final List<PackageInfo> packages =
1719                 mPackageManager.getInstalledPackagesAsUser(0, userId);
1720         return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category
1721                         == ApplicationInfo.CATEGORY_GAME)
1722                 .map(e -> e.packageName)
1723                 .toArray(String[]::new);
1724     }
1725 
getInstalledGamePackageNamesByAllUsers(@serIdInt int currentUserId)1726     private List<String> getInstalledGamePackageNamesByAllUsers(@UserIdInt int currentUserId) {
1727         HashSet<String> packageSet = new HashSet<>();
1728 
1729         final int[] userIds = getAllUserIds(currentUserId);
1730         for (int userId : userIds) {
1731             packageSet.addAll(Arrays.asList(getInstalledGamePackageNames(userId)));
1732         }
1733 
1734         return new ArrayList<>(packageSet);
1735     }
1736 
1737     /**
1738      * @hide
1739      */
1740     @VisibleForTesting
getConfig(String packageName)1741     public GamePackageConfiguration getConfig(String packageName) {
1742         GamePackageConfiguration overrideConfig = null;
1743         GamePackageConfiguration config;
1744         synchronized (mDeviceConfigLock) {
1745             config = mConfigs.get(packageName);
1746         }
1747         synchronized (mOverrideConfigLock) {
1748             overrideConfig = mOverrideConfigs.get(packageName);
1749         }
1750         if (overrideConfig == null || config == null) {
1751             return overrideConfig == null ? config : overrideConfig;
1752         }
1753         return config.copyAndApplyOverride(overrideConfig);
1754     }
1755 
registerPackageReceiver()1756     private void registerPackageReceiver() {
1757         final IntentFilter packageFilter = new IntentFilter();
1758         packageFilter.addAction(ACTION_PACKAGE_ADDED);
1759         packageFilter.addAction(ACTION_PACKAGE_REMOVED);
1760         packageFilter.addDataScheme("package");
1761         final BroadcastReceiver packageReceiver = new BroadcastReceiver() {
1762             @Override
1763             public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
1764                 final Uri data = intent.getData();
1765                 try {
1766                     final int userId = getSendingUserId();
1767                     if (userId != ActivityManager.getCurrentUser()) {
1768                         return;
1769                     }
1770                     final String packageName = data.getSchemeSpecificPart();
1771                     try {
1772                         final ApplicationInfo applicationInfo = mPackageManager
1773                                 .getApplicationInfoAsUser(
1774                                         packageName, PackageManager.MATCH_ALL, userId);
1775                         if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
1776                             return;
1777                         }
1778                     } catch (NameNotFoundException e) {
1779                         // Ignore the exception.
1780                     }
1781                     switch (intent.getAction()) {
1782                         case ACTION_PACKAGE_ADDED:
1783                             updateConfigsForUser(userId, true /*checkGamePackage*/, packageName);
1784                             break;
1785                         case ACTION_PACKAGE_REMOVED:
1786                             disableCompatScale(packageName);
1787                             // If EXTRA_REPLACING is true, it means there will be an
1788                             // ACTION_PACKAGE_ADDED triggered after this because this
1789                             // is an updated package that gets installed. Hence, disable
1790                             // resolution downscaling effort but avoid removing the server
1791                             // or commandline overriding configurations because those will
1792                             // not change but the package game mode configurations may change
1793                             // which may opt in and/or opt out some game mode configurations.
1794                             if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
1795                                 synchronized (mOverrideConfigLock) {
1796                                     mOverrideConfigs.remove(packageName);
1797                                 }
1798                                 synchronized (mDeviceConfigLock) {
1799                                     mConfigs.remove(packageName);
1800                                 }
1801                                 synchronized (mLock) {
1802                                     if (mSettings.containsKey(userId)) {
1803                                         mSettings.get(userId).removeGame(packageName);
1804                                     }
1805                                 }
1806                             }
1807                             break;
1808                         default:
1809                             // do nothing
1810                             break;
1811                     }
1812                 } catch (NullPointerException e) {
1813                     Slog.e(TAG, "Failed to get package name for new package");
1814                 }
1815             }
1816         };
1817         mContext.registerReceiverForAllUsers(packageReceiver, packageFilter,
1818                 /* broadcastPermission= */ null, /* scheduler= */ null);
1819     }
1820 
registerDeviceConfigListener()1821     private void registerDeviceConfigListener() {
1822         mDeviceConfigListener = new DeviceConfigListener();
1823     }
1824 
dumpDeviceConfigs()1825     private String dumpDeviceConfigs() {
1826         StringBuilder out = new StringBuilder();
1827         for (String key : mConfigs.keySet()) {
1828             out.append("[\nName: ").append(key)
1829                     .append("\nConfig: ").append(mConfigs.get(key).toString()).append("\n]");
1830         }
1831         return out.toString();
1832     }
1833 
gameStateModeToStatsdGameState(int mode)1834     private static int gameStateModeToStatsdGameState(int mode) {
1835         switch (mode) {
1836             case GameState.MODE_NONE:
1837                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_NONE;
1838             case GameState.MODE_GAMEPLAY_INTERRUPTIBLE:
1839                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_GAMEPLAY_INTERRUPTIBLE;
1840             case GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE:
1841                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_GAMEPLAY_UNINTERRUPTIBLE;
1842             case GameState.MODE_CONTENT:
1843                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_CONTENT;
1844             case GameState.MODE_UNKNOWN:
1845             default:
1846                 return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_UNKNOWN;
1847         }
1848     }
1849 
createServiceThread()1850     private static ServiceThread createServiceThread() {
1851         ServiceThread handlerThread = new ServiceThread(TAG,
1852                 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
1853         handlerThread.start();
1854         return handlerThread;
1855     }
1856 
1857     @VisibleForTesting
setOverrideFrameRate(int uid, float frameRate)1858     void setOverrideFrameRate(int uid, float frameRate) {
1859         nativeSetOverrideFrameRate(uid, frameRate);
1860     }
1861 
1862     /**
1863      * load dynamic library for frame rate overriding JNI calls
1864      */
nativeSetOverrideFrameRate(int uid, float frameRate)1865     private static native void nativeSetOverrideFrameRate(int uid, float frameRate);
1866 
1867     final class UidObserver extends IUidObserver.Stub {
1868         @Override
onUidIdle(int uid, boolean disabled)1869         public void onUidIdle(int uid, boolean disabled) {}
1870 
1871         @Override
onUidGone(int uid, boolean disabled)1872         public void onUidGone(int uid, boolean disabled) {
1873             synchronized (mUidObserverLock) {
1874                 disableGameMode(uid);
1875             }
1876         }
1877 
1878         @Override
onUidActive(int uid)1879         public void onUidActive(int uid) {}
1880 
1881         @Override
onUidStateChanged(int uid, int procState, long procStateSeq, int capability)1882         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
1883             synchronized (mUidObserverLock) {
1884                 if (ActivityManager.isProcStateBackground(procState)) {
1885                     disableGameMode(uid);
1886                     return;
1887                 }
1888 
1889                 final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
1890                 if (packages == null || packages.length == 0) {
1891                     return;
1892                 }
1893 
1894                 final int userId = mContext.getUserId();
1895                 if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
1896                     return;
1897                 }
1898 
1899                 if (mForegroundGameUids.isEmpty()) {
1900                     Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
1901                     mPowerManagerInternal.setPowerMode(Mode.GAME, true);
1902                 }
1903                 mForegroundGameUids.add(uid);
1904             }
1905         }
1906 
disableGameMode(int uid)1907         private void disableGameMode(int uid) {
1908             synchronized (mUidObserverLock) {
1909                 if (!mForegroundGameUids.contains(uid)) {
1910                     return;
1911                 }
1912                 mForegroundGameUids.remove(uid);
1913                 if (!mForegroundGameUids.isEmpty()) {
1914                     return;
1915                 }
1916                 Slog.v(TAG,
1917                         "Game power mode OFF (process remomved or state changed to background)");
1918                 mPowerManagerInternal.setPowerMode(Mode.GAME, false);
1919             }
1920         }
1921 
1922         @Override
onUidCachedChanged(int uid, boolean cached)1923         public void onUidCachedChanged(int uid, boolean cached) {}
1924 
1925         @Override
onUidProcAdjChanged(int uid)1926         public void onUidProcAdjChanged(int uid) {}
1927     }
1928 }
1929