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