1 /* 2 * Copyright 2016 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 android.os; 18 19 import android.app.Activity; 20 import android.content.BroadcastReceiver; 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.IPackageManager; 26 import android.content.pm.PackageInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.content.res.AssetFileDescriptor; 30 import android.content.res.AssetManager; 31 import android.provider.Settings; 32 import android.util.Log; 33 import android.widget.Toast; 34 35 import dalvik.system.VMRuntime; 36 37 import java.io.BufferedReader; 38 import java.io.File; 39 import java.io.FileDescriptor; 40 import java.io.FileInputStream; 41 import java.io.FileNotFoundException; 42 import java.io.IOException; 43 import java.io.InputStreamReader; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.HashMap; 47 import java.util.List; 48 import java.util.Map; 49 50 /** @hide */ 51 public class GraphicsEnvironment { 52 53 private static final GraphicsEnvironment sInstance = new GraphicsEnvironment(); 54 55 /** 56 * Returns the shared {@link GraphicsEnvironment} instance. 57 */ getInstance()58 public static GraphicsEnvironment getInstance() { 59 return sInstance; 60 } 61 62 private static final boolean DEBUG = false; 63 private static final String TAG = "GraphicsEnvironment"; 64 private static final String SYSTEM_DRIVER_NAME = "system"; 65 private static final String SYSTEM_DRIVER_VERSION_NAME = ""; 66 private static final long SYSTEM_DRIVER_VERSION_CODE = 0; 67 private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0"; 68 private static final String PROPERTY_GFX_DRIVER_PRERELEASE = "ro.gfx.driver.1"; 69 private static final String PROPERTY_GFX_DRIVER_BUILD_TIME = "ro.gfx.driver_build_time"; 70 private static final String METADATA_DRIVER_BUILD_TIME = "com.android.gamedriver.build_time"; 71 private static final String METADATA_DEVELOPER_DRIVER_ENABLE = 72 "com.android.graphics.developerdriver.enable"; 73 private static final String METADATA_INJECT_LAYERS_ENABLE = 74 "com.android.graphics.injectLayers.enable"; 75 private static final String ANGLE_RULES_FILE = "a4a_rules.json"; 76 private static final String ANGLE_TEMP_RULES = "debug.angle.rules"; 77 private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID"; 78 private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE = 79 "android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE"; 80 private static final String INTENT_KEY_A4A_TOAST_MESSAGE = "A4A Toast Message"; 81 private static final String GAME_DRIVER_WHITELIST_ALL = "*"; 82 private static final String GAME_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt"; 83 private static final int VULKAN_1_0 = 0x00400000; 84 private static final int VULKAN_1_1 = 0x00401000; 85 86 // GAME_DRIVER_ALL_APPS 87 // 0: Default (Invalid values fallback to default as well) 88 // 1: All apps use Game Driver 89 // 2: All apps use Prerelease Driver 90 // 3: All apps use system graphics driver 91 private static final int GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT = 0; 92 private static final int GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER = 1; 93 private static final int GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2; 94 private static final int GAME_DRIVER_GLOBAL_OPT_IN_OFF = 3; 95 96 private ClassLoader mClassLoader; 97 private String mLibrarySearchPaths; 98 private String mLibraryPermittedPaths; 99 100 /** 101 * Set up GraphicsEnvironment 102 */ setup(Context context, Bundle coreSettings)103 public void setup(Context context, Bundle coreSettings) { 104 final PackageManager pm = context.getPackageManager(); 105 final String packageName = context.getPackageName(); 106 final ApplicationInfo appInfoWithMetaData = 107 getAppInfoWithMetadata(context, pm, packageName); 108 Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers"); 109 setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData); 110 Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); 111 Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle"); 112 setupAngle(context, coreSettings, pm, packageName); 113 Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); 114 Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver"); 115 if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) { 116 setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE, 117 SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0), packageName, 118 getVulkanVersion(pm)); 119 } 120 Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); 121 } 122 123 /** 124 * Hint for GraphicsEnvironment that an activity is launching on the process. 125 * Then the app process is allowed to send stats to GpuStats module. 126 */ hintActivityLaunch()127 public static native void hintActivityLaunch(); 128 129 /** 130 * Query to determine if ANGLE should be used 131 */ shouldUseAngle(Context context, Bundle coreSettings, String packageName)132 public static boolean shouldUseAngle(Context context, Bundle coreSettings, 133 String packageName) { 134 if (packageName.isEmpty()) { 135 Log.v(TAG, "No package name available yet, ANGLE should not be used"); 136 return false; 137 } 138 139 final String devOptIn = getDriverForPkg(context, coreSettings, packageName); 140 if (DEBUG) { 141 Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " 142 + "set to: '" + devOptIn + "'"); 143 } 144 145 // We only want to use ANGLE if the app is whitelisted or the developer has 146 // explicitly chosen something other than default driver. 147 // The whitelist will be generated by the ANGLE APK at both boot time and 148 // ANGLE update time. It will only include apps mentioned in the rules file. 149 final boolean whitelisted = checkAngleWhitelist(context, coreSettings, packageName); 150 final boolean requested = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.ANGLE)); 151 final boolean useAngle = (whitelisted || requested); 152 if (!useAngle) { 153 return false; 154 } 155 156 if (whitelisted) { 157 Log.v(TAG, "ANGLE whitelist includes " + packageName); 158 } 159 if (requested) { 160 Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn); 161 } 162 163 return true; 164 } 165 getVulkanVersion(PackageManager pm)166 private static int getVulkanVersion(PackageManager pm) { 167 // PackageManager doesn't have an API to retrieve the version of a specific feature, and we 168 // need to avoid retrieving all system features here and looping through them. 169 if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_1)) { 170 return VULKAN_1_1; 171 } 172 173 if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_0)) { 174 return VULKAN_1_0; 175 } 176 177 return 0; 178 } 179 180 /** 181 * Check whether application is has set the manifest metadata for layer injection. 182 */ canInjectLayers(ApplicationInfo ai)183 private static boolean canInjectLayers(ApplicationInfo ai) { 184 return (ai.metaData != null && ai.metaData.getBoolean(METADATA_INJECT_LAYERS_ENABLE) 185 && setInjectLayersPrSetDumpable()); 186 } 187 188 /** 189 * Store the class loader for namespace lookup later. 190 */ setLayerPaths(ClassLoader classLoader, String searchPaths, String permittedPaths)191 public void setLayerPaths(ClassLoader classLoader, 192 String searchPaths, 193 String permittedPaths) { 194 // We have to store these in the class because they are set up before we 195 // have access to the Context to properly set up GraphicsEnvironment 196 mClassLoader = classLoader; 197 mLibrarySearchPaths = searchPaths; 198 mLibraryPermittedPaths = permittedPaths; 199 } 200 201 /** 202 * Returns the debug layer paths from settings. 203 * Returns null if: 204 * 1) The application process is not debuggable or layer injection metadata flag is not 205 * true; Or 206 * 2) ENABLE_GPU_DEBUG_LAYERS is not true; Or 207 * 3) Package name is not equal to GPU_DEBUG_APP. 208 */ getDebugLayerPathsFromSettings( Bundle coreSettings, IPackageManager pm, String packageName, ApplicationInfo ai)209 public String getDebugLayerPathsFromSettings( 210 Bundle coreSettings, IPackageManager pm, String packageName, 211 ApplicationInfo ai) { 212 if (!debugLayerEnabled(coreSettings, packageName, ai)) { 213 return null; 214 } 215 Log.i(TAG, "GPU debug layers enabled for " + packageName); 216 String debugLayerPaths = ""; 217 218 // Grab all debug layer apps and add to paths. 219 final String gpuDebugLayerApps = 220 coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP, ""); 221 if (!gpuDebugLayerApps.isEmpty()) { 222 Log.i(TAG, "GPU debug layer apps: " + gpuDebugLayerApps); 223 // If a colon is present, treat this as multiple apps, so Vulkan and GLES 224 // layer apps can be provided at the same time. 225 final String[] layerApps = gpuDebugLayerApps.split(":"); 226 for (int i = 0; i < layerApps.length; i++) { 227 String paths = getDebugLayerAppPaths(pm, layerApps[i]); 228 if (!paths.isEmpty()) { 229 // Append the path so files placed in the app's base directory will 230 // override the external path 231 debugLayerPaths += paths + File.pathSeparator; 232 } 233 } 234 } 235 return debugLayerPaths; 236 } 237 238 /** 239 * Return the debug layer app's on-disk and in-APK lib directories 240 */ getDebugLayerAppPaths(IPackageManager pm, String packageName)241 private static String getDebugLayerAppPaths(IPackageManager pm, String packageName) { 242 final ApplicationInfo appInfo; 243 try { 244 appInfo = pm.getApplicationInfo(packageName, PackageManager.MATCH_ALL, 245 UserHandle.myUserId()); 246 } catch (RemoteException e) { 247 return ""; 248 } 249 if (appInfo == null) { 250 Log.w(TAG, "Debug layer app '" + packageName + "' not installed"); 251 } 252 253 final String abi = chooseAbi(appInfo); 254 final StringBuilder sb = new StringBuilder(); 255 sb.append(appInfo.nativeLibraryDir) 256 .append(File.pathSeparator) 257 .append(appInfo.sourceDir) 258 .append("!/lib/") 259 .append(abi); 260 final String paths = sb.toString(); 261 if (DEBUG) Log.v(TAG, "Debug layer app libs: " + paths); 262 263 return paths; 264 } 265 debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai)266 private boolean debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai) { 267 // Only enable additional debug functionality if the following conditions are met: 268 // 1. App is debuggable or device is rooted or layer injection metadata flag is true 269 // 2. ENABLE_GPU_DEBUG_LAYERS is true 270 // 3. Package name is equal to GPU_DEBUG_APP 271 if (!isDebuggable() && !canInjectLayers(ai)) { 272 return false; 273 } 274 final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); 275 if (enable == 0) { 276 return false; 277 } 278 final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP, ""); 279 if (packageName == null 280 || (gpuDebugApp.isEmpty() || packageName.isEmpty()) 281 || !gpuDebugApp.equals(packageName)) { 282 return false; 283 } 284 return true; 285 } 286 287 /** 288 * Set up layer search paths for all apps 289 */ setupGpuLayers( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai)290 private void setupGpuLayers( 291 Context context, Bundle coreSettings, PackageManager pm, String packageName, 292 ApplicationInfo ai) { 293 final boolean enabled = debugLayerEnabled(coreSettings, packageName, ai); 294 String layerPaths = ""; 295 if (enabled) { 296 layerPaths = mLibraryPermittedPaths; 297 298 final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS); 299 Log.i(TAG, "Vulkan debug layer list: " + layers); 300 if (layers != null && !layers.isEmpty()) { 301 setDebugLayers(layers); 302 } 303 304 final String layersGLES = 305 coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES); 306 Log.i(TAG, "GLES debug layer list: " + layersGLES); 307 if (layersGLES != null && !layersGLES.isEmpty()) { 308 setDebugLayersGLES(layersGLES); 309 } 310 } 311 312 // Include the app's lib directory in all cases 313 layerPaths += mLibrarySearchPaths; 314 setLayerPaths(mClassLoader, layerPaths); 315 } 316 317 enum OpenGlDriverChoice { 318 DEFAULT, 319 NATIVE, 320 ANGLE 321 } 322 323 private static final Map<OpenGlDriverChoice, String> sDriverMap = buildMap(); buildMap()324 private static Map<OpenGlDriverChoice, String> buildMap() { 325 final Map<OpenGlDriverChoice, String> map = new HashMap<>(); 326 map.put(OpenGlDriverChoice.DEFAULT, "default"); 327 map.put(OpenGlDriverChoice.ANGLE, "angle"); 328 map.put(OpenGlDriverChoice.NATIVE, "native"); 329 330 return map; 331 } 332 333 getGlobalSettingsString(ContentResolver contentResolver, Bundle bundle, String globalSetting)334 private static List<String> getGlobalSettingsString(ContentResolver contentResolver, 335 Bundle bundle, 336 String globalSetting) { 337 final List<String> valueList; 338 final String settingsValue; 339 340 if (bundle != null) { 341 settingsValue = bundle.getString(globalSetting); 342 } else { 343 settingsValue = Settings.Global.getString(contentResolver, globalSetting); 344 } 345 346 if (settingsValue != null) { 347 valueList = new ArrayList<>(Arrays.asList(settingsValue.split(","))); 348 } else { 349 valueList = new ArrayList<>(); 350 } 351 352 return valueList; 353 } 354 getGlobalSettingsPkgIndex(String pkgName, List<String> globalSettingsDriverPkgs)355 private static int getGlobalSettingsPkgIndex(String pkgName, 356 List<String> globalSettingsDriverPkgs) { 357 for (int pkgIndex = 0; pkgIndex < globalSettingsDriverPkgs.size(); pkgIndex++) { 358 if (globalSettingsDriverPkgs.get(pkgIndex).equals(pkgName)) { 359 return pkgIndex; 360 } 361 } 362 363 return -1; 364 } 365 getAppInfoWithMetadata(Context context, PackageManager pm, String packageName)366 private static ApplicationInfo getAppInfoWithMetadata(Context context, 367 PackageManager pm, String packageName) { 368 ApplicationInfo ai; 369 try { 370 // Get the ApplicationInfo from PackageManager so that metadata fields present. 371 ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); 372 } catch (PackageManager.NameNotFoundException e) { 373 // Unlikely to fail for applications, but in case of failure, fall back to use the 374 // ApplicationInfo from context directly. 375 ai = context.getApplicationInfo(); 376 } 377 return ai; 378 } 379 getDriverForPkg(Context context, Bundle bundle, String packageName)380 private static String getDriverForPkg(Context context, Bundle bundle, String packageName) { 381 final String allUseAngle; 382 if (bundle != null) { 383 allUseAngle = 384 bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); 385 } else { 386 ContentResolver contentResolver = context.getContentResolver(); 387 allUseAngle = Settings.Global.getString(contentResolver, 388 Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); 389 } 390 if ((allUseAngle != null) && allUseAngle.equals("1")) { 391 return sDriverMap.get(OpenGlDriverChoice.ANGLE); 392 } 393 394 final ContentResolver contentResolver = context.getContentResolver(); 395 final List<String> globalSettingsDriverPkgs = 396 getGlobalSettingsString(contentResolver, bundle, 397 Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS); 398 final List<String> globalSettingsDriverValues = 399 getGlobalSettingsString(contentResolver, bundle, 400 Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES); 401 402 // Make sure we have a good package name 403 if ((packageName == null) || (packageName.isEmpty())) { 404 return sDriverMap.get(OpenGlDriverChoice.DEFAULT); 405 } 406 // Make sure we have good settings to use 407 if (globalSettingsDriverPkgs.size() != globalSettingsDriverValues.size()) { 408 Log.w(TAG, 409 "Global.Settings values are invalid: " 410 + "globalSettingsDriverPkgs.size = " 411 + globalSettingsDriverPkgs.size() + ", " 412 + "globalSettingsDriverValues.size = " 413 + globalSettingsDriverValues.size()); 414 return sDriverMap.get(OpenGlDriverChoice.DEFAULT); 415 } 416 417 final int pkgIndex = getGlobalSettingsPkgIndex(packageName, globalSettingsDriverPkgs); 418 419 if (pkgIndex < 0) { 420 return sDriverMap.get(OpenGlDriverChoice.DEFAULT); 421 } 422 423 return globalSettingsDriverValues.get(pkgIndex); 424 } 425 426 /** 427 * Get the ANGLE package name. 428 */ getAnglePackageName(PackageManager pm)429 private String getAnglePackageName(PackageManager pm) { 430 final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID); 431 432 final List<ResolveInfo> resolveInfos = 433 pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY); 434 if (resolveInfos.size() != 1) { 435 Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: " 436 + resolveInfos.size()); 437 for (ResolveInfo resolveInfo : resolveInfos) { 438 Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName); 439 } 440 return ""; 441 } 442 443 // Must be exactly 1 ANGLE PKG found to get here. 444 return resolveInfos.get(0).activityInfo.packageName; 445 } 446 447 /** 448 * Check for ANGLE debug package, but only for apps that can load them (dumpable) 449 */ getAngleDebugPackage(Context context, Bundle coreSettings)450 private String getAngleDebugPackage(Context context, Bundle coreSettings) { 451 if (isDebuggable()) { 452 String debugPackage; 453 454 if (coreSettings != null) { 455 debugPackage = 456 coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE); 457 } else { 458 ContentResolver contentResolver = context.getContentResolver(); 459 debugPackage = Settings.Global.getString(contentResolver, 460 Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE); 461 } 462 463 if ((debugPackage != null) && (!debugPackage.isEmpty())) { 464 return debugPackage; 465 } 466 } 467 468 return ""; 469 } 470 471 /** 472 * Attempt to setup ANGLE with a temporary rules file. 473 * True: Temporary rules file was loaded. 474 * False: Temporary rules file was *not* loaded. 475 */ setupAngleWithTempRulesFile(Context context, String packageName, String paths, String devOptIn)476 private static boolean setupAngleWithTempRulesFile(Context context, 477 String packageName, 478 String paths, 479 String devOptIn) { 480 /** 481 * We only want to load a temp rules file for: 482 * - apps that are marked 'debuggable' in their manifest 483 * - devices that are running a userdebug build (ro.debuggable) or can inject libraries for 484 * debugging (PR_SET_DUMPABLE). 485 */ 486 if (!isDebuggable()) { 487 Log.v(TAG, "Skipping loading temporary rules file"); 488 return false; 489 } 490 491 final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES); 492 493 if ((angleTempRules == null) || angleTempRules.isEmpty()) { 494 Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty"); 495 return false; 496 } 497 498 Log.i(TAG, "Detected system property " + ANGLE_TEMP_RULES + ": " + angleTempRules); 499 500 final File tempRulesFile = new File(angleTempRules); 501 if (tempRulesFile.exists()) { 502 Log.i(TAG, angleTempRules + " exists, loading file."); 503 try { 504 final FileInputStream stream = new FileInputStream(angleTempRules); 505 506 try { 507 final FileDescriptor rulesFd = stream.getFD(); 508 final long rulesOffset = 0; 509 final long rulesLength = stream.getChannel().size(); 510 Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules); 511 512 setAngleInfo(paths, packageName, devOptIn, rulesFd, rulesOffset, rulesLength); 513 514 stream.close(); 515 516 // We successfully setup ANGLE, so return with good status 517 return true; 518 } catch (IOException e) { 519 Log.w(TAG, "Hit IOException thrown by FileInputStream: " + e); 520 } 521 } catch (FileNotFoundException e) { 522 Log.w(TAG, "Temp ANGLE rules file not found: " + e); 523 } catch (SecurityException e) { 524 Log.w(TAG, "Temp ANGLE rules file not accessible: " + e); 525 } 526 } 527 528 return false; 529 } 530 531 /** 532 * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK. 533 * True: APK rules file was loaded. 534 * False: APK rules file was *not* loaded. 535 */ setupAngleRulesApk(String anglePkgName, ApplicationInfo angleInfo, PackageManager pm, String packageName, String paths, String devOptIn)536 private static boolean setupAngleRulesApk(String anglePkgName, 537 ApplicationInfo angleInfo, 538 PackageManager pm, 539 String packageName, 540 String paths, 541 String devOptIn) { 542 // Pass the rules file to loader for ANGLE decisions 543 try { 544 final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets(); 545 546 try { 547 final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE); 548 549 setAngleInfo(paths, packageName, devOptIn, assetsFd.getFileDescriptor(), 550 assetsFd.getStartOffset(), assetsFd.getLength()); 551 552 assetsFd.close(); 553 554 return true; 555 } catch (IOException e) { 556 Log.w(TAG, "Failed to get AssetFileDescriptor for " + ANGLE_RULES_FILE 557 + " from '" + anglePkgName + "': " + e); 558 } 559 } catch (PackageManager.NameNotFoundException e) { 560 Log.w(TAG, "Failed to get AssetManager for '" + anglePkgName + "': " + e); 561 } 562 563 return false; 564 } 565 566 /** 567 * Pull ANGLE whitelist from GlobalSettings and compare against current package 568 */ checkAngleWhitelist(Context context, Bundle bundle, String packageName)569 private static boolean checkAngleWhitelist(Context context, Bundle bundle, String packageName) { 570 final ContentResolver contentResolver = context.getContentResolver(); 571 final List<String> angleWhitelist = 572 getGlobalSettingsString(contentResolver, bundle, 573 Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST); 574 575 if (DEBUG) Log.v(TAG, "ANGLE whitelist: " + angleWhitelist); 576 577 return angleWhitelist.contains(packageName); 578 } 579 580 /** 581 * Pass ANGLE details down to trigger enable logic 582 * 583 * @param context 584 * @param bundle 585 * @param packageName 586 * @return true: ANGLE setup successfully 587 * false: ANGLE not setup (not on whitelist, ANGLE not present, etc.) 588 */ setupAngle(Context context, Bundle bundle, PackageManager pm, String packageName)589 public boolean setupAngle(Context context, Bundle bundle, PackageManager pm, 590 String packageName) { 591 592 if (!shouldUseAngle(context, bundle, packageName)) { 593 return false; 594 } 595 596 ApplicationInfo angleInfo = null; 597 598 // If the developer has specified a debug package over ADB, attempt to find it 599 String anglePkgName = getAngleDebugPackage(context, bundle); 600 if (!anglePkgName.isEmpty()) { 601 Log.i(TAG, "ANGLE debug package enabled: " + anglePkgName); 602 try { 603 // Note the debug package does not have to be pre-installed 604 angleInfo = pm.getApplicationInfo(anglePkgName, 0); 605 } catch (PackageManager.NameNotFoundException e) { 606 Log.w(TAG, "ANGLE debug package '" + anglePkgName + "' not installed"); 607 return false; 608 } 609 } 610 611 // Otherwise, check to see if ANGLE is properly installed 612 if (angleInfo == null) { 613 anglePkgName = getAnglePackageName(pm); 614 if (!anglePkgName.isEmpty()) { 615 Log.i(TAG, "ANGLE package enabled: " + anglePkgName); 616 try { 617 // Production ANGLE libraries must be pre-installed as a system app 618 angleInfo = pm.getApplicationInfo(anglePkgName, 619 PackageManager.MATCH_SYSTEM_ONLY); 620 } catch (PackageManager.NameNotFoundException e) { 621 Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed"); 622 return false; 623 } 624 } else { 625 Log.e(TAG, "Failed to find ANGLE package."); 626 return false; 627 } 628 } 629 630 final String abi = chooseAbi(angleInfo); 631 632 // Build a path that includes installed native libs and APK 633 final String paths = angleInfo.nativeLibraryDir 634 + File.pathSeparator 635 + angleInfo.sourceDir 636 + "!/lib/" 637 + abi; 638 639 if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths); 640 641 // If the user has set the developer option to something other than default, 642 // we need to call setupAngleRulesApk() with the package name and the developer 643 // option value (native/angle/other). Then later when we are actually trying to 644 // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before 645 // and can confidently answer yes/no based on the previously set developer 646 // option value. 647 final String devOptIn = getDriverForPkg(context, bundle, packageName); 648 649 if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) { 650 // We setup ANGLE with a temp rules file, so we're done here. 651 return true; 652 } 653 654 if (setupAngleRulesApk(anglePkgName, angleInfo, pm, packageName, paths, devOptIn)) { 655 // We setup ANGLE with rules from the APK, so we're done here. 656 return true; 657 } 658 659 return false; 660 } 661 662 /** 663 * Determine if the "ANGLE In Use" dialog box should be shown. 664 */ shouldShowAngleInUseDialogBox(Context context)665 private boolean shouldShowAngleInUseDialogBox(Context context) { 666 try { 667 ContentResolver contentResolver = context.getContentResolver(); 668 final int showDialogBox = Settings.Global.getInt(contentResolver, 669 Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX); 670 671 return (showDialogBox == 1); 672 } catch (Settings.SettingNotFoundException | SecurityException e) { 673 // Do nothing and move on 674 } 675 676 // No setting, so assume false 677 return false; 678 } 679 680 /** 681 * Determine if ANGLE will be used and setup the environment 682 */ setupAndUseAngle(Context context, String packageName)683 private boolean setupAndUseAngle(Context context, String packageName) { 684 // Need to make sure we are evaluating ANGLE usage for the correct circumstances 685 if (!setupAngle(context, null, context.getPackageManager(), packageName)) { 686 Log.v(TAG, "Package '" + packageName + "' should not use ANGLE"); 687 return false; 688 } 689 690 final boolean useAngle = getShouldUseAngle(packageName); 691 Log.v(TAG, "Package '" + packageName + "' should use ANGLE = '" + useAngle + "'"); 692 693 return useAngle; 694 } 695 696 /** 697 * Show the ANGLE in Use Dialog Box 698 * @param context 699 */ showAngleInUseDialogBox(Context context)700 public void showAngleInUseDialogBox(Context context) { 701 final String packageName = context.getPackageName(); 702 703 if (shouldShowAngleInUseDialogBox(context) && setupAndUseAngle(context, packageName)) { 704 final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE); 705 String anglePkg = getAnglePackageName(context.getPackageManager()); 706 intent.setPackage(anglePkg); 707 708 context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 709 @Override 710 public void onReceive(Context context, Intent intent) { 711 Bundle results = getResultExtras(true); 712 713 String toastMsg = results.getString(INTENT_KEY_A4A_TOAST_MESSAGE); 714 final Toast toast = Toast.makeText(context, toastMsg, Toast.LENGTH_LONG); 715 toast.show(); 716 } 717 }, null, Activity.RESULT_OK, null, null); 718 } 719 } 720 721 /** 722 * Return the driver package name to use. Return null for system driver. 723 */ chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai)724 private static String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) { 725 final String gameDriver = SystemProperties.get(PROPERTY_GFX_DRIVER); 726 final boolean hasGameDriver = gameDriver != null && !gameDriver.isEmpty(); 727 728 final String prereleaseDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRERELEASE); 729 final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty(); 730 731 if (!hasGameDriver && !hasPrereleaseDriver) { 732 if (DEBUG) Log.v(TAG, "Neither Game Driver nor prerelease driver is supported."); 733 return null; 734 } 735 736 // To minimize risk of driver updates crippling the device beyond user repair, never use an 737 // updated driver for privileged or non-updated system apps. Presumably pre-installed apps 738 // were tested thoroughly with the pre-installed driver. 739 if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) { 740 if (DEBUG) Log.v(TAG, "Ignoring driver package for privileged/non-updated system app."); 741 return null; 742 } 743 744 final boolean enablePrereleaseDriver = 745 (ai.metaData != null && ai.metaData.getBoolean(METADATA_DEVELOPER_DRIVER_ENABLE)) 746 || isDebuggable(); 747 748 // Priority for Game Driver settings global on confliction (Higher priority comes first): 749 // 1. GAME_DRIVER_ALL_APPS 750 // 2. GAME_DRIVER_OPT_OUT_APPS 751 // 3. GAME_DRIVER_PRERELEASE_OPT_IN_APPS 752 // 4. GAME_DRIVER_OPT_IN_APPS 753 // 5. GAME_DRIVER_BLACKLIST 754 // 6. GAME_DRIVER_WHITELIST 755 switch (coreSettings.getInt(Settings.Global.GAME_DRIVER_ALL_APPS, 0)) { 756 case GAME_DRIVER_GLOBAL_OPT_IN_OFF: 757 if (DEBUG) Log.v(TAG, "Game Driver is turned off on this device."); 758 return null; 759 case GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER: 760 if (DEBUG) Log.v(TAG, "All apps opt in to use Game Driver."); 761 return hasGameDriver ? gameDriver : null; 762 case GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER: 763 if (DEBUG) Log.v(TAG, "All apps opt in to use prerelease driver."); 764 return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null; 765 case GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT: 766 default: 767 break; 768 } 769 770 final String appPackageName = ai.packageName; 771 if (getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_OUT_APPS) 772 .contains(appPackageName)) { 773 if (DEBUG) Log.v(TAG, "App opts out for Game Driver."); 774 return null; 775 } 776 777 if (getGlobalSettingsString( 778 null, coreSettings, Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS) 779 .contains(appPackageName)) { 780 if (DEBUG) Log.v(TAG, "App opts in for prerelease Game Driver."); 781 return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null; 782 } 783 784 // Early return here since the rest logic is only for Game Driver. 785 if (!hasGameDriver) { 786 if (DEBUG) Log.v(TAG, "Game Driver is not supported on the device."); 787 return null; 788 } 789 790 final boolean isOptIn = 791 getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_IN_APPS) 792 .contains(appPackageName); 793 final List<String> whitelist = 794 getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_WHITELIST); 795 if (!isOptIn && whitelist.indexOf(GAME_DRIVER_WHITELIST_ALL) != 0 796 && !whitelist.contains(appPackageName)) { 797 if (DEBUG) Log.v(TAG, "App is not on the whitelist for Game Driver."); 798 return null; 799 } 800 801 // If the application is not opted-in, then check whether it's on the blacklist, 802 // terminate early if it's on the blacklist and fallback to system driver. 803 if (!isOptIn 804 && getGlobalSettingsString( 805 null, coreSettings, Settings.Global.GAME_DRIVER_BLACKLIST) 806 .contains(appPackageName)) { 807 if (DEBUG) Log.v(TAG, "App is on the blacklist for Game Driver."); 808 return null; 809 } 810 811 return gameDriver; 812 } 813 814 /** 815 * Choose whether the current process should use the builtin or an updated driver. 816 */ chooseDriver( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai)817 private static boolean chooseDriver( 818 Context context, Bundle coreSettings, PackageManager pm, String packageName, 819 ApplicationInfo ai) { 820 final String driverPackageName = chooseDriverInternal(coreSettings, ai); 821 if (driverPackageName == null) { 822 return false; 823 } 824 825 final PackageInfo driverPackageInfo; 826 try { 827 driverPackageInfo = pm.getPackageInfo(driverPackageName, 828 PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA); 829 } catch (PackageManager.NameNotFoundException e) { 830 Log.w(TAG, "driver package '" + driverPackageName + "' not installed"); 831 return false; 832 } 833 834 // O drivers are restricted to the sphal linker namespace, so don't try to use 835 // packages unless they declare they're compatible with that restriction. 836 final ApplicationInfo driverAppInfo = driverPackageInfo.applicationInfo; 837 if (driverAppInfo.targetSdkVersion < Build.VERSION_CODES.O) { 838 if (DEBUG) { 839 Log.w(TAG, "updated driver package is not known to be compatible with O"); 840 } 841 return false; 842 } 843 844 final String abi = chooseAbi(driverAppInfo); 845 if (abi == null) { 846 if (DEBUG) { 847 // This is the normal case for the pre-installed empty driver package, don't spam 848 if (driverAppInfo.isUpdatedSystemApp()) { 849 Log.w(TAG, "updated driver package has no compatible native libraries"); 850 } 851 } 852 return false; 853 } 854 855 final StringBuilder sb = new StringBuilder(); 856 sb.append(driverAppInfo.nativeLibraryDir) 857 .append(File.pathSeparator); 858 sb.append(driverAppInfo.sourceDir) 859 .append("!/lib/") 860 .append(abi); 861 final String paths = sb.toString(); 862 final String sphalLibraries = getSphalLibraries(context, driverPackageName); 863 if (DEBUG) { 864 Log.v(TAG, 865 "gfx driver package search path: " + paths 866 + ", required sphal libraries: " + sphalLibraries); 867 } 868 setDriverPathAndSphalLibraries(paths, sphalLibraries); 869 870 if (driverAppInfo.metaData == null) { 871 throw new NullPointerException("apk's meta-data cannot be null"); 872 } 873 874 final String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME); 875 if (driverBuildTime == null || driverBuildTime.isEmpty()) { 876 throw new IllegalArgumentException("com.android.gamedriver.build_time is not set"); 877 } 878 // driver_build_time in the meta-data is in "L<Unix epoch timestamp>" format. e.g. L123456. 879 // Long.parseLong will throw if the meta-data "driver_build_time" is not set properly. 880 setGpuStats(driverPackageName, driverPackageInfo.versionName, driverAppInfo.longVersionCode, 881 Long.parseLong(driverBuildTime.substring(1)), packageName, 0); 882 883 return true; 884 } 885 chooseAbi(ApplicationInfo ai)886 private static String chooseAbi(ApplicationInfo ai) { 887 final String isa = VMRuntime.getCurrentInstructionSet(); 888 if (ai.primaryCpuAbi != null && 889 isa.equals(VMRuntime.getInstructionSet(ai.primaryCpuAbi))) { 890 return ai.primaryCpuAbi; 891 } 892 if (ai.secondaryCpuAbi != null && 893 isa.equals(VMRuntime.getInstructionSet(ai.secondaryCpuAbi))) { 894 return ai.secondaryCpuAbi; 895 } 896 return null; 897 } 898 getSphalLibraries(Context context, String driverPackageName)899 private static String getSphalLibraries(Context context, String driverPackageName) { 900 try { 901 final Context driverContext = 902 context.createPackageContext(driverPackageName, Context.CONTEXT_RESTRICTED); 903 final BufferedReader reader = new BufferedReader(new InputStreamReader( 904 driverContext.getAssets().open(GAME_DRIVER_SPHAL_LIBRARIES_FILENAME))); 905 final ArrayList<String> assetStrings = new ArrayList<>(); 906 for (String assetString; (assetString = reader.readLine()) != null;) { 907 assetStrings.add(assetString); 908 } 909 return String.join(":", assetStrings); 910 } catch (PackageManager.NameNotFoundException e) { 911 if (DEBUG) { 912 Log.w(TAG, "Driver package '" + driverPackageName + "' not installed"); 913 } 914 } catch (IOException e) { 915 if (DEBUG) { 916 Log.w(TAG, "Failed to load '" + GAME_DRIVER_SPHAL_LIBRARIES_FILENAME + "'"); 917 } 918 } 919 return ""; 920 } 921 isDebuggable()922 private static native boolean isDebuggable(); setLayerPaths(ClassLoader classLoader, String layerPaths)923 private static native void setLayerPaths(ClassLoader classLoader, String layerPaths); setDebugLayers(String layers)924 private static native void setDebugLayers(String layers); setDebugLayersGLES(String layers)925 private static native void setDebugLayersGLES(String layers); setDriverPathAndSphalLibraries(String path, String sphalLibraries)926 private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries); setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion)927 private static native void setGpuStats(String driverPackageName, String driverVersionName, 928 long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion); setAngleInfo(String path, String appPackage, String devOptIn, FileDescriptor rulesFd, long rulesOffset, long rulesLength)929 private static native void setAngleInfo(String path, String appPackage, String devOptIn, 930 FileDescriptor rulesFd, long rulesOffset, long rulesLength); getShouldUseAngle(String packageName)931 private static native boolean getShouldUseAngle(String packageName); setInjectLayersPrSetDumpable()932 private static native boolean setInjectLayersPrSetDumpable(); 933 } 934