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