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