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