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