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 return ""; 252 } 253 254 final String abi = chooseAbi(appInfo); 255 final StringBuilder sb = new StringBuilder(); 256 sb.append(appInfo.nativeLibraryDir) 257 .append(File.pathSeparator) 258 .append(appInfo.sourceDir) 259 .append("!/lib/") 260 .append(abi); 261 final String paths = sb.toString(); 262 if (DEBUG) Log.v(TAG, "Debug layer app libs: " + paths); 263 264 return paths; 265 } 266 debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai)267 private boolean debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai) { 268 // Only enable additional debug functionality if the following conditions are met: 269 // 1. App is debuggable or device is rooted or layer injection metadata flag is true 270 // 2. ENABLE_GPU_DEBUG_LAYERS is true 271 // 3. Package name is equal to GPU_DEBUG_APP 272 if (!isDebuggable() && !canInjectLayers(ai)) { 273 return false; 274 } 275 final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); 276 if (enable == 0) { 277 return false; 278 } 279 final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP, ""); 280 if (packageName == null 281 || (gpuDebugApp.isEmpty() || packageName.isEmpty()) 282 || !gpuDebugApp.equals(packageName)) { 283 return false; 284 } 285 return true; 286 } 287 288 /** 289 * Set up layer search paths for all apps 290 */ setupGpuLayers( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai)291 private void setupGpuLayers( 292 Context context, Bundle coreSettings, PackageManager pm, String packageName, 293 ApplicationInfo ai) { 294 final boolean enabled = debugLayerEnabled(coreSettings, packageName, ai); 295 String layerPaths = ""; 296 if (enabled) { 297 layerPaths = mLibraryPermittedPaths; 298 299 final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS); 300 Log.i(TAG, "Vulkan debug layer list: " + layers); 301 if (layers != null && !layers.isEmpty()) { 302 setDebugLayers(layers); 303 } 304 305 final String layersGLES = 306 coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES); 307 Log.i(TAG, "GLES debug layer list: " + layersGLES); 308 if (layersGLES != null && !layersGLES.isEmpty()) { 309 setDebugLayersGLES(layersGLES); 310 } 311 } 312 313 // Include the app's lib directory in all cases 314 layerPaths += mLibrarySearchPaths; 315 setLayerPaths(mClassLoader, layerPaths); 316 } 317 318 enum OpenGlDriverChoice { 319 DEFAULT, 320 NATIVE, 321 ANGLE 322 } 323 324 private static final Map<OpenGlDriverChoice, String> sDriverMap = buildMap(); buildMap()325 private static Map<OpenGlDriverChoice, String> buildMap() { 326 final Map<OpenGlDriverChoice, String> map = new HashMap<>(); 327 map.put(OpenGlDriverChoice.DEFAULT, "default"); 328 map.put(OpenGlDriverChoice.ANGLE, "angle"); 329 map.put(OpenGlDriverChoice.NATIVE, "native"); 330 331 return map; 332 } 333 334 getGlobalSettingsString(ContentResolver contentResolver, Bundle bundle, String globalSetting)335 private static List<String> getGlobalSettingsString(ContentResolver contentResolver, 336 Bundle bundle, 337 String globalSetting) { 338 final List<String> valueList; 339 final String settingsValue; 340 341 if (bundle != null) { 342 settingsValue = bundle.getString(globalSetting); 343 } else { 344 settingsValue = Settings.Global.getString(contentResolver, globalSetting); 345 } 346 347 if (settingsValue != null) { 348 valueList = new ArrayList<>(Arrays.asList(settingsValue.split(","))); 349 } else { 350 valueList = new ArrayList<>(); 351 } 352 353 return valueList; 354 } 355 getGlobalSettingsPkgIndex(String pkgName, List<String> globalSettingsDriverPkgs)356 private static int getGlobalSettingsPkgIndex(String pkgName, 357 List<String> globalSettingsDriverPkgs) { 358 for (int pkgIndex = 0; pkgIndex < globalSettingsDriverPkgs.size(); pkgIndex++) { 359 if (globalSettingsDriverPkgs.get(pkgIndex).equals(pkgName)) { 360 return pkgIndex; 361 } 362 } 363 364 return -1; 365 } 366 getAppInfoWithMetadata(Context context, PackageManager pm, String packageName)367 private static ApplicationInfo getAppInfoWithMetadata(Context context, 368 PackageManager pm, String packageName) { 369 ApplicationInfo ai; 370 try { 371 // Get the ApplicationInfo from PackageManager so that metadata fields present. 372 ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); 373 } catch (PackageManager.NameNotFoundException e) { 374 // Unlikely to fail for applications, but in case of failure, fall back to use the 375 // ApplicationInfo from context directly. 376 ai = context.getApplicationInfo(); 377 } 378 return ai; 379 } 380 getDriverForPkg(Context context, Bundle bundle, String packageName)381 private static String getDriverForPkg(Context context, Bundle bundle, String packageName) { 382 final String allUseAngle; 383 if (bundle != null) { 384 allUseAngle = 385 bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); 386 } else { 387 ContentResolver contentResolver = context.getContentResolver(); 388 allUseAngle = Settings.Global.getString(contentResolver, 389 Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); 390 } 391 if ((allUseAngle != null) && allUseAngle.equals("1")) { 392 return sDriverMap.get(OpenGlDriverChoice.ANGLE); 393 } 394 395 final ContentResolver contentResolver = context.getContentResolver(); 396 final List<String> globalSettingsDriverPkgs = 397 getGlobalSettingsString(contentResolver, bundle, 398 Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS); 399 final List<String> globalSettingsDriverValues = 400 getGlobalSettingsString(contentResolver, bundle, 401 Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES); 402 403 // Make sure we have a good package name 404 if ((packageName == null) || (packageName.isEmpty())) { 405 return sDriverMap.get(OpenGlDriverChoice.DEFAULT); 406 } 407 // Make sure we have good settings to use 408 if (globalSettingsDriverPkgs.size() != globalSettingsDriverValues.size()) { 409 Log.w(TAG, 410 "Global.Settings values are invalid: " 411 + "globalSettingsDriverPkgs.size = " 412 + globalSettingsDriverPkgs.size() + ", " 413 + "globalSettingsDriverValues.size = " 414 + globalSettingsDriverValues.size()); 415 return sDriverMap.get(OpenGlDriverChoice.DEFAULT); 416 } 417 418 final int pkgIndex = getGlobalSettingsPkgIndex(packageName, globalSettingsDriverPkgs); 419 420 if (pkgIndex < 0) { 421 return sDriverMap.get(OpenGlDriverChoice.DEFAULT); 422 } 423 424 return globalSettingsDriverValues.get(pkgIndex); 425 } 426 427 /** 428 * Get the ANGLE package name. 429 */ getAnglePackageName(PackageManager pm)430 private String getAnglePackageName(PackageManager pm) { 431 final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID); 432 433 final List<ResolveInfo> resolveInfos = 434 pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY); 435 if (resolveInfos.size() != 1) { 436 Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: " 437 + resolveInfos.size()); 438 for (ResolveInfo resolveInfo : resolveInfos) { 439 Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName); 440 } 441 return ""; 442 } 443 444 // Must be exactly 1 ANGLE PKG found to get here. 445 return resolveInfos.get(0).activityInfo.packageName; 446 } 447 448 /** 449 * Check for ANGLE debug package, but only for apps that can load them (dumpable) 450 */ getAngleDebugPackage(Context context, Bundle coreSettings)451 private String getAngleDebugPackage(Context context, Bundle coreSettings) { 452 if (isDebuggable()) { 453 String debugPackage; 454 455 if (coreSettings != null) { 456 debugPackage = 457 coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE); 458 } else { 459 ContentResolver contentResolver = context.getContentResolver(); 460 debugPackage = Settings.Global.getString(contentResolver, 461 Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE); 462 } 463 464 if ((debugPackage != null) && (!debugPackage.isEmpty())) { 465 return debugPackage; 466 } 467 } 468 469 return ""; 470 } 471 472 /** 473 * Attempt to setup ANGLE with a temporary rules file. 474 * True: Temporary rules file was loaded. 475 * False: Temporary rules file was *not* loaded. 476 */ setupAngleWithTempRulesFile(Context context, String packageName, String paths, String devOptIn)477 private static boolean setupAngleWithTempRulesFile(Context context, 478 String packageName, 479 String paths, 480 String devOptIn) { 481 /** 482 * We only want to load a temp rules file for: 483 * - apps that are marked 'debuggable' in their manifest 484 * - devices that are running a userdebug build (ro.debuggable) or can inject libraries for 485 * debugging (PR_SET_DUMPABLE). 486 */ 487 if (!isDebuggable()) { 488 Log.v(TAG, "Skipping loading temporary rules file"); 489 return false; 490 } 491 492 final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES); 493 494 if ((angleTempRules == null) || angleTempRules.isEmpty()) { 495 Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty"); 496 return false; 497 } 498 499 Log.i(TAG, "Detected system property " + ANGLE_TEMP_RULES + ": " + angleTempRules); 500 501 final File tempRulesFile = new File(angleTempRules); 502 if (tempRulesFile.exists()) { 503 Log.i(TAG, angleTempRules + " exists, loading file."); 504 try { 505 final FileInputStream stream = new FileInputStream(angleTempRules); 506 507 try { 508 final FileDescriptor rulesFd = stream.getFD(); 509 final long rulesOffset = 0; 510 final long rulesLength = stream.getChannel().size(); 511 Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules); 512 513 setAngleInfo(paths, packageName, devOptIn, rulesFd, rulesOffset, rulesLength); 514 515 stream.close(); 516 517 // We successfully setup ANGLE, so return with good status 518 return true; 519 } catch (IOException e) { 520 Log.w(TAG, "Hit IOException thrown by FileInputStream: " + e); 521 } 522 } catch (FileNotFoundException e) { 523 Log.w(TAG, "Temp ANGLE rules file not found: " + e); 524 } catch (SecurityException e) { 525 Log.w(TAG, "Temp ANGLE rules file not accessible: " + e); 526 } 527 } 528 529 return false; 530 } 531 532 /** 533 * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK. 534 * True: APK rules file was loaded. 535 * False: APK rules file was *not* loaded. 536 */ setupAngleRulesApk(String anglePkgName, ApplicationInfo angleInfo, PackageManager pm, String packageName, String paths, String devOptIn)537 private static boolean setupAngleRulesApk(String anglePkgName, 538 ApplicationInfo angleInfo, 539 PackageManager pm, 540 String packageName, 541 String paths, 542 String devOptIn) { 543 // Pass the rules file to loader for ANGLE decisions 544 try { 545 final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets(); 546 547 try { 548 final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE); 549 550 setAngleInfo(paths, packageName, devOptIn, assetsFd.getFileDescriptor(), 551 assetsFd.getStartOffset(), assetsFd.getLength()); 552 553 assetsFd.close(); 554 555 return true; 556 } catch (IOException e) { 557 Log.w(TAG, "Failed to get AssetFileDescriptor for " + ANGLE_RULES_FILE 558 + " from '" + anglePkgName + "': " + e); 559 } 560 } catch (PackageManager.NameNotFoundException e) { 561 Log.w(TAG, "Failed to get AssetManager for '" + anglePkgName + "': " + e); 562 } 563 564 return false; 565 } 566 567 /** 568 * Pull ANGLE whitelist from GlobalSettings and compare against current package 569 */ checkAngleWhitelist(Context context, Bundle bundle, String packageName)570 private static boolean checkAngleWhitelist(Context context, Bundle bundle, String packageName) { 571 final ContentResolver contentResolver = context.getContentResolver(); 572 final List<String> angleWhitelist = 573 getGlobalSettingsString(contentResolver, bundle, 574 Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST); 575 576 if (DEBUG) Log.v(TAG, "ANGLE whitelist: " + angleWhitelist); 577 578 return angleWhitelist.contains(packageName); 579 } 580 581 /** 582 * Pass ANGLE details down to trigger enable logic 583 * 584 * @param context 585 * @param bundle 586 * @param packageName 587 * @return true: ANGLE setup successfully 588 * false: ANGLE not setup (not on whitelist, ANGLE not present, etc.) 589 */ setupAngle(Context context, Bundle bundle, PackageManager pm, String packageName)590 public boolean setupAngle(Context context, Bundle bundle, PackageManager pm, 591 String packageName) { 592 593 if (!shouldUseAngle(context, bundle, packageName)) { 594 return false; 595 } 596 597 ApplicationInfo angleInfo = null; 598 599 // If the developer has specified a debug package over ADB, attempt to find it 600 String anglePkgName = getAngleDebugPackage(context, bundle); 601 if (!anglePkgName.isEmpty()) { 602 Log.i(TAG, "ANGLE debug package enabled: " + anglePkgName); 603 try { 604 // Note the debug package does not have to be pre-installed 605 angleInfo = pm.getApplicationInfo(anglePkgName, 0); 606 } catch (PackageManager.NameNotFoundException e) { 607 Log.w(TAG, "ANGLE debug package '" + anglePkgName + "' not installed"); 608 return false; 609 } 610 } 611 612 // Otherwise, check to see if ANGLE is properly installed 613 if (angleInfo == null) { 614 anglePkgName = getAnglePackageName(pm); 615 if (!anglePkgName.isEmpty()) { 616 Log.i(TAG, "ANGLE package enabled: " + anglePkgName); 617 try { 618 // Production ANGLE libraries must be pre-installed as a system app 619 angleInfo = pm.getApplicationInfo(anglePkgName, 620 PackageManager.MATCH_SYSTEM_ONLY); 621 } catch (PackageManager.NameNotFoundException e) { 622 Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed"); 623 return false; 624 } 625 } else { 626 Log.e(TAG, "Failed to find ANGLE package."); 627 return false; 628 } 629 } 630 631 final String abi = chooseAbi(angleInfo); 632 633 // Build a path that includes installed native libs and APK 634 final String paths = angleInfo.nativeLibraryDir 635 + File.pathSeparator 636 + angleInfo.sourceDir 637 + "!/lib/" 638 + abi; 639 640 if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths); 641 642 // If the user has set the developer option to something other than default, 643 // we need to call setupAngleRulesApk() with the package name and the developer 644 // option value (native/angle/other). Then later when we are actually trying to 645 // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before 646 // and can confidently answer yes/no based on the previously set developer 647 // option value. 648 final String devOptIn = getDriverForPkg(context, bundle, packageName); 649 650 if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) { 651 // We setup ANGLE with a temp rules file, so we're done here. 652 return true; 653 } 654 655 if (setupAngleRulesApk(anglePkgName, angleInfo, pm, packageName, paths, devOptIn)) { 656 // We setup ANGLE with rules from the APK, so we're done here. 657 return true; 658 } 659 660 return false; 661 } 662 663 /** 664 * Determine if the "ANGLE In Use" dialog box should be shown. 665 */ shouldShowAngleInUseDialogBox(Context context)666 private boolean shouldShowAngleInUseDialogBox(Context context) { 667 try { 668 ContentResolver contentResolver = context.getContentResolver(); 669 final int showDialogBox = Settings.Global.getInt(contentResolver, 670 Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX); 671 672 return (showDialogBox == 1); 673 } catch (Settings.SettingNotFoundException | SecurityException e) { 674 // Do nothing and move on 675 } 676 677 // No setting, so assume false 678 return false; 679 } 680 681 /** 682 * Determine if ANGLE will be used and setup the environment 683 */ setupAndUseAngle(Context context, String packageName)684 private boolean setupAndUseAngle(Context context, String packageName) { 685 // Need to make sure we are evaluating ANGLE usage for the correct circumstances 686 if (!setupAngle(context, null, context.getPackageManager(), packageName)) { 687 Log.v(TAG, "Package '" + packageName + "' should not use ANGLE"); 688 return false; 689 } 690 691 final boolean useAngle = getShouldUseAngle(packageName); 692 Log.v(TAG, "Package '" + packageName + "' should use ANGLE = '" + useAngle + "'"); 693 694 return useAngle; 695 } 696 697 /** 698 * Show the ANGLE in Use Dialog Box 699 * @param context 700 */ showAngleInUseDialogBox(Context context)701 public void showAngleInUseDialogBox(Context context) { 702 final String packageName = context.getPackageName(); 703 704 if (shouldShowAngleInUseDialogBox(context) && setupAndUseAngle(context, packageName)) { 705 final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE); 706 String anglePkg = getAnglePackageName(context.getPackageManager()); 707 intent.setPackage(anglePkg); 708 709 context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 710 @Override 711 public void onReceive(Context context, Intent intent) { 712 Bundle results = getResultExtras(true); 713 714 String toastMsg = results.getString(INTENT_KEY_A4A_TOAST_MESSAGE); 715 final Toast toast = Toast.makeText(context, toastMsg, Toast.LENGTH_LONG); 716 toast.show(); 717 } 718 }, null, Activity.RESULT_OK, null, null); 719 } 720 } 721 722 /** 723 * Return the driver package name to use. Return null for system driver. 724 */ chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai)725 private static String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) { 726 final String gameDriver = SystemProperties.get(PROPERTY_GFX_DRIVER); 727 final boolean hasGameDriver = gameDriver != null && !gameDriver.isEmpty(); 728 729 final String prereleaseDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRERELEASE); 730 final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty(); 731 732 if (!hasGameDriver && !hasPrereleaseDriver) { 733 if (DEBUG) Log.v(TAG, "Neither Game Driver nor prerelease driver is supported."); 734 return null; 735 } 736 737 // To minimize risk of driver updates crippling the device beyond user repair, never use an 738 // updated driver for privileged or non-updated system apps. Presumably pre-installed apps 739 // were tested thoroughly with the pre-installed driver. 740 if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) { 741 if (DEBUG) Log.v(TAG, "Ignoring driver package for privileged/non-updated system app."); 742 return null; 743 } 744 745 final boolean enablePrereleaseDriver = 746 (ai.metaData != null && ai.metaData.getBoolean(METADATA_DEVELOPER_DRIVER_ENABLE)) 747 || isDebuggable(); 748 749 // Priority for Game Driver settings global on confliction (Higher priority comes first): 750 // 1. GAME_DRIVER_ALL_APPS 751 // 2. GAME_DRIVER_OPT_OUT_APPS 752 // 3. GAME_DRIVER_PRERELEASE_OPT_IN_APPS 753 // 4. GAME_DRIVER_OPT_IN_APPS 754 // 5. GAME_DRIVER_BLACKLIST 755 // 6. GAME_DRIVER_WHITELIST 756 switch (coreSettings.getInt(Settings.Global.GAME_DRIVER_ALL_APPS, 0)) { 757 case GAME_DRIVER_GLOBAL_OPT_IN_OFF: 758 if (DEBUG) Log.v(TAG, "Game Driver is turned off on this device."); 759 return null; 760 case GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER: 761 if (DEBUG) Log.v(TAG, "All apps opt in to use Game Driver."); 762 return hasGameDriver ? gameDriver : null; 763 case GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER: 764 if (DEBUG) Log.v(TAG, "All apps opt in to use prerelease driver."); 765 return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null; 766 case GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT: 767 default: 768 break; 769 } 770 771 final String appPackageName = ai.packageName; 772 if (getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_OUT_APPS) 773 .contains(appPackageName)) { 774 if (DEBUG) Log.v(TAG, "App opts out for Game Driver."); 775 return null; 776 } 777 778 if (getGlobalSettingsString( 779 null, coreSettings, Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS) 780 .contains(appPackageName)) { 781 if (DEBUG) Log.v(TAG, "App opts in for prerelease Game Driver."); 782 return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null; 783 } 784 785 // Early return here since the rest logic is only for Game Driver. 786 if (!hasGameDriver) { 787 if (DEBUG) Log.v(TAG, "Game Driver is not supported on the device."); 788 return null; 789 } 790 791 final boolean isOptIn = 792 getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_IN_APPS) 793 .contains(appPackageName); 794 final List<String> whitelist = 795 getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_WHITELIST); 796 if (!isOptIn && whitelist.indexOf(GAME_DRIVER_WHITELIST_ALL) != 0 797 && !whitelist.contains(appPackageName)) { 798 if (DEBUG) Log.v(TAG, "App is not on the whitelist for Game Driver."); 799 return null; 800 } 801 802 // If the application is not opted-in, then check whether it's on the blacklist, 803 // terminate early if it's on the blacklist and fallback to system driver. 804 if (!isOptIn 805 && getGlobalSettingsString( 806 null, coreSettings, Settings.Global.GAME_DRIVER_BLACKLIST) 807 .contains(appPackageName)) { 808 if (DEBUG) Log.v(TAG, "App is on the blacklist for Game Driver."); 809 return null; 810 } 811 812 return gameDriver; 813 } 814 815 /** 816 * Choose whether the current process should use the builtin or an updated driver. 817 */ chooseDriver( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai)818 private static boolean chooseDriver( 819 Context context, Bundle coreSettings, PackageManager pm, String packageName, 820 ApplicationInfo ai) { 821 final String driverPackageName = chooseDriverInternal(coreSettings, ai); 822 if (driverPackageName == null) { 823 return false; 824 } 825 826 final PackageInfo driverPackageInfo; 827 try { 828 driverPackageInfo = pm.getPackageInfo(driverPackageName, 829 PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA); 830 } catch (PackageManager.NameNotFoundException e) { 831 Log.w(TAG, "driver package '" + driverPackageName + "' not installed"); 832 return false; 833 } 834 835 // O drivers are restricted to the sphal linker namespace, so don't try to use 836 // packages unless they declare they're compatible with that restriction. 837 final ApplicationInfo driverAppInfo = driverPackageInfo.applicationInfo; 838 if (driverAppInfo.targetSdkVersion < Build.VERSION_CODES.O) { 839 if (DEBUG) { 840 Log.w(TAG, "updated driver package is not known to be compatible with O"); 841 } 842 return false; 843 } 844 845 final String abi = chooseAbi(driverAppInfo); 846 if (abi == null) { 847 if (DEBUG) { 848 // This is the normal case for the pre-installed empty driver package, don't spam 849 if (driverAppInfo.isUpdatedSystemApp()) { 850 Log.w(TAG, "updated driver package has no compatible native libraries"); 851 } 852 } 853 return false; 854 } 855 856 final StringBuilder sb = new StringBuilder(); 857 sb.append(driverAppInfo.nativeLibraryDir) 858 .append(File.pathSeparator); 859 sb.append(driverAppInfo.sourceDir) 860 .append("!/lib/") 861 .append(abi); 862 final String paths = sb.toString(); 863 final String sphalLibraries = getSphalLibraries(context, driverPackageName); 864 if (DEBUG) { 865 Log.v(TAG, 866 "gfx driver package search path: " + paths 867 + ", required sphal libraries: " + sphalLibraries); 868 } 869 setDriverPathAndSphalLibraries(paths, sphalLibraries); 870 871 if (driverAppInfo.metaData == null) { 872 throw new NullPointerException("apk's meta-data cannot be null"); 873 } 874 875 final String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME); 876 if (driverBuildTime == null || driverBuildTime.isEmpty()) { 877 throw new IllegalArgumentException("com.android.gamedriver.build_time is not set"); 878 } 879 // driver_build_time in the meta-data is in "L<Unix epoch timestamp>" format. e.g. L123456. 880 // Long.parseLong will throw if the meta-data "driver_build_time" is not set properly. 881 setGpuStats(driverPackageName, driverPackageInfo.versionName, driverAppInfo.longVersionCode, 882 Long.parseLong(driverBuildTime.substring(1)), packageName, 0); 883 884 return true; 885 } 886 chooseAbi(ApplicationInfo ai)887 private static String chooseAbi(ApplicationInfo ai) { 888 final String isa = VMRuntime.getCurrentInstructionSet(); 889 if (ai.primaryCpuAbi != null && 890 isa.equals(VMRuntime.getInstructionSet(ai.primaryCpuAbi))) { 891 return ai.primaryCpuAbi; 892 } 893 if (ai.secondaryCpuAbi != null && 894 isa.equals(VMRuntime.getInstructionSet(ai.secondaryCpuAbi))) { 895 return ai.secondaryCpuAbi; 896 } 897 return null; 898 } 899 getSphalLibraries(Context context, String driverPackageName)900 private static String getSphalLibraries(Context context, String driverPackageName) { 901 try { 902 final Context driverContext = 903 context.createPackageContext(driverPackageName, Context.CONTEXT_RESTRICTED); 904 final BufferedReader reader = new BufferedReader(new InputStreamReader( 905 driverContext.getAssets().open(GAME_DRIVER_SPHAL_LIBRARIES_FILENAME))); 906 final ArrayList<String> assetStrings = new ArrayList<>(); 907 for (String assetString; (assetString = reader.readLine()) != null;) { 908 assetStrings.add(assetString); 909 } 910 return String.join(":", assetStrings); 911 } catch (PackageManager.NameNotFoundException e) { 912 if (DEBUG) { 913 Log.w(TAG, "Driver package '" + driverPackageName + "' not installed"); 914 } 915 } catch (IOException e) { 916 if (DEBUG) { 917 Log.w(TAG, "Failed to load '" + GAME_DRIVER_SPHAL_LIBRARIES_FILENAME + "'"); 918 } 919 } 920 return ""; 921 } 922 isDebuggable()923 private static native boolean isDebuggable(); setLayerPaths(ClassLoader classLoader, String layerPaths)924 private static native void setLayerPaths(ClassLoader classLoader, String layerPaths); setDebugLayers(String layers)925 private static native void setDebugLayers(String layers); setDebugLayersGLES(String layers)926 private static native void setDebugLayersGLES(String layers); setDriverPathAndSphalLibraries(String path, String sphalLibraries)927 private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries); setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion)928 private static native void setGpuStats(String driverPackageName, String driverVersionName, 929 long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion); setAngleInfo(String path, String appPackage, String devOptIn, FileDescriptor rulesFd, long rulesOffset, long rulesLength)930 private static native void setAngleInfo(String path, String appPackage, String devOptIn, 931 FileDescriptor rulesFd, long rulesOffset, long rulesLength); getShouldUseAngle(String packageName)932 private static native boolean getShouldUseAngle(String packageName); setInjectLayersPrSetDumpable()933 private static native boolean setInjectLayersPrSetDumpable(); 934 } 935