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