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