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