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