• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             return "";
252         }
253 
254         final String abi = chooseAbi(appInfo);
255         final StringBuilder sb = new StringBuilder();
256         sb.append(appInfo.nativeLibraryDir)
257             .append(File.pathSeparator)
258             .append(appInfo.sourceDir)
259             .append("!/lib/")
260             .append(abi);
261         final String paths = sb.toString();
262         if (DEBUG) Log.v(TAG, "Debug layer app libs: " + paths);
263 
264         return paths;
265     }
266 
debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai)267     private boolean debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai) {
268         // Only enable additional debug functionality if the following conditions are met:
269         // 1. App is debuggable or device is rooted or layer injection metadata flag is true
270         // 2. ENABLE_GPU_DEBUG_LAYERS is true
271         // 3. Package name is equal to GPU_DEBUG_APP
272         if (!isDebuggable() && !canInjectLayers(ai)) {
273             return false;
274         }
275         final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0);
276         if (enable == 0) {
277             return false;
278         }
279         final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP, "");
280         if (packageName == null
281                 || (gpuDebugApp.isEmpty() || packageName.isEmpty())
282                 || !gpuDebugApp.equals(packageName)) {
283             return false;
284         }
285         return true;
286     }
287 
288     /**
289      * Set up layer search paths for all apps
290      */
setupGpuLayers( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai)291     private void setupGpuLayers(
292             Context context, Bundle coreSettings, PackageManager pm, String packageName,
293             ApplicationInfo ai) {
294         final boolean enabled = debugLayerEnabled(coreSettings, packageName, ai);
295         String layerPaths = "";
296         if (enabled) {
297             layerPaths = mLibraryPermittedPaths;
298 
299             final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS);
300             Log.i(TAG, "Vulkan debug layer list: " + layers);
301             if (layers != null && !layers.isEmpty()) {
302                 setDebugLayers(layers);
303             }
304 
305             final String layersGLES =
306                     coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES);
307             Log.i(TAG, "GLES debug layer list: " + layersGLES);
308             if (layersGLES != null && !layersGLES.isEmpty()) {
309                 setDebugLayersGLES(layersGLES);
310             }
311         }
312 
313         // Include the app's lib directory in all cases
314         layerPaths += mLibrarySearchPaths;
315         setLayerPaths(mClassLoader, layerPaths);
316     }
317 
318     enum OpenGlDriverChoice {
319         DEFAULT,
320         NATIVE,
321         ANGLE
322     }
323 
324     private static final Map<OpenGlDriverChoice, String> sDriverMap = buildMap();
buildMap()325     private static Map<OpenGlDriverChoice, String> buildMap() {
326         final Map<OpenGlDriverChoice, String> map = new HashMap<>();
327         map.put(OpenGlDriverChoice.DEFAULT, "default");
328         map.put(OpenGlDriverChoice.ANGLE, "angle");
329         map.put(OpenGlDriverChoice.NATIVE, "native");
330 
331         return map;
332     }
333 
334 
getGlobalSettingsString(ContentResolver contentResolver, Bundle bundle, String globalSetting)335     private static List<String> getGlobalSettingsString(ContentResolver contentResolver,
336                                                         Bundle bundle,
337                                                         String globalSetting) {
338         final List<String> valueList;
339         final String settingsValue;
340 
341         if (bundle != null) {
342             settingsValue = bundle.getString(globalSetting);
343         } else {
344             settingsValue = Settings.Global.getString(contentResolver, globalSetting);
345         }
346 
347         if (settingsValue != null) {
348             valueList = new ArrayList<>(Arrays.asList(settingsValue.split(",")));
349         } else {
350             valueList = new ArrayList<>();
351         }
352 
353         return valueList;
354     }
355 
getGlobalSettingsPkgIndex(String pkgName, List<String> globalSettingsDriverPkgs)356     private static int getGlobalSettingsPkgIndex(String pkgName,
357                                                  List<String> globalSettingsDriverPkgs) {
358         for (int pkgIndex = 0; pkgIndex < globalSettingsDriverPkgs.size(); pkgIndex++) {
359             if (globalSettingsDriverPkgs.get(pkgIndex).equals(pkgName)) {
360                 return pkgIndex;
361             }
362         }
363 
364         return -1;
365     }
366 
getAppInfoWithMetadata(Context context, PackageManager pm, String packageName)367     private static ApplicationInfo getAppInfoWithMetadata(Context context,
368                                                           PackageManager pm, String packageName) {
369         ApplicationInfo ai;
370         try {
371             // Get the ApplicationInfo from PackageManager so that metadata fields present.
372             ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
373         } catch (PackageManager.NameNotFoundException e) {
374             // Unlikely to fail for applications, but in case of failure, fall back to use the
375             // ApplicationInfo from context directly.
376             ai = context.getApplicationInfo();
377         }
378         return ai;
379     }
380 
getDriverForPkg(Context context, Bundle bundle, String packageName)381     private static String getDriverForPkg(Context context, Bundle bundle, String packageName) {
382         final String allUseAngle;
383         if (bundle != null) {
384             allUseAngle =
385                     bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
386         } else {
387             ContentResolver contentResolver = context.getContentResolver();
388             allUseAngle = Settings.Global.getString(contentResolver,
389                     Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
390         }
391         if ((allUseAngle != null) && allUseAngle.equals("1")) {
392             return sDriverMap.get(OpenGlDriverChoice.ANGLE);
393         }
394 
395         final ContentResolver contentResolver = context.getContentResolver();
396         final List<String> globalSettingsDriverPkgs =
397                 getGlobalSettingsString(contentResolver, bundle,
398                         Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS);
399         final List<String> globalSettingsDriverValues =
400                 getGlobalSettingsString(contentResolver, bundle,
401                         Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES);
402 
403         // Make sure we have a good package name
404         if ((packageName == null) || (packageName.isEmpty())) {
405             return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
406         }
407         // Make sure we have good settings to use
408         if (globalSettingsDriverPkgs.size() != globalSettingsDriverValues.size()) {
409             Log.w(TAG,
410                     "Global.Settings values are invalid: "
411                         + "globalSettingsDriverPkgs.size = "
412                             + globalSettingsDriverPkgs.size() + ", "
413                         + "globalSettingsDriverValues.size = "
414                             + globalSettingsDriverValues.size());
415             return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
416         }
417 
418         final int pkgIndex = getGlobalSettingsPkgIndex(packageName, globalSettingsDriverPkgs);
419 
420         if (pkgIndex < 0) {
421             return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
422         }
423 
424         return globalSettingsDriverValues.get(pkgIndex);
425     }
426 
427     /**
428      * Get the ANGLE package name.
429      */
getAnglePackageName(PackageManager pm)430     private String getAnglePackageName(PackageManager pm) {
431         final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID);
432 
433         final List<ResolveInfo> resolveInfos =
434                 pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
435         if (resolveInfos.size() != 1) {
436             Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
437                     + resolveInfos.size());
438             for (ResolveInfo resolveInfo : resolveInfos) {
439                 Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
440             }
441             return "";
442         }
443 
444         // Must be exactly 1 ANGLE PKG found to get here.
445         return resolveInfos.get(0).activityInfo.packageName;
446     }
447 
448     /**
449      * Check for ANGLE debug package, but only for apps that can load them (dumpable)
450      */
getAngleDebugPackage(Context context, Bundle coreSettings)451     private String getAngleDebugPackage(Context context, Bundle coreSettings) {
452         if (isDebuggable()) {
453             String debugPackage;
454 
455             if (coreSettings != null) {
456                 debugPackage =
457                         coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
458             } else {
459                 ContentResolver contentResolver = context.getContentResolver();
460                 debugPackage = Settings.Global.getString(contentResolver,
461                         Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
462             }
463 
464             if ((debugPackage != null) && (!debugPackage.isEmpty())) {
465                 return debugPackage;
466             }
467         }
468 
469         return "";
470     }
471 
472     /**
473      * Attempt to setup ANGLE with a temporary rules file.
474      * True: Temporary rules file was loaded.
475      * False: Temporary rules file was *not* loaded.
476      */
setupAngleWithTempRulesFile(Context context, String packageName, String paths, String devOptIn)477     private static boolean setupAngleWithTempRulesFile(Context context,
478                                                 String packageName,
479                                                 String paths,
480                                                 String devOptIn) {
481         /**
482          * We only want to load a temp rules file for:
483          *  - apps that are marked 'debuggable' in their manifest
484          *  - devices that are running a userdebug build (ro.debuggable) or can inject libraries for
485          *    debugging (PR_SET_DUMPABLE).
486          */
487         if (!isDebuggable()) {
488             Log.v(TAG, "Skipping loading temporary rules file");
489             return false;
490         }
491 
492         final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES);
493 
494         if ((angleTempRules == null) || angleTempRules.isEmpty()) {
495             Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty");
496             return false;
497         }
498 
499         Log.i(TAG, "Detected system property " + ANGLE_TEMP_RULES + ": " + angleTempRules);
500 
501         final File tempRulesFile = new File(angleTempRules);
502         if (tempRulesFile.exists()) {
503             Log.i(TAG, angleTempRules + " exists, loading file.");
504             try {
505                 final FileInputStream stream = new FileInputStream(angleTempRules);
506 
507                 try {
508                     final FileDescriptor rulesFd = stream.getFD();
509                     final long rulesOffset = 0;
510                     final long rulesLength = stream.getChannel().size();
511                     Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules);
512 
513                     setAngleInfo(paths, packageName, devOptIn, rulesFd, rulesOffset, rulesLength);
514 
515                     stream.close();
516 
517                     // We successfully setup ANGLE, so return with good status
518                     return true;
519                 } catch (IOException e) {
520                     Log.w(TAG, "Hit IOException thrown by FileInputStream: " + e);
521                 }
522             } catch (FileNotFoundException e) {
523                 Log.w(TAG, "Temp ANGLE rules file not found: " + e);
524             } catch (SecurityException e) {
525                 Log.w(TAG, "Temp ANGLE rules file not accessible: " + e);
526             }
527         }
528 
529         return false;
530     }
531 
532     /**
533      * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK.
534      * True: APK rules file was loaded.
535      * False: APK rules file was *not* loaded.
536      */
setupAngleRulesApk(String anglePkgName, ApplicationInfo angleInfo, PackageManager pm, String packageName, String paths, String devOptIn)537     private static boolean setupAngleRulesApk(String anglePkgName,
538             ApplicationInfo angleInfo,
539             PackageManager pm,
540             String packageName,
541             String paths,
542             String devOptIn) {
543         // Pass the rules file to loader for ANGLE decisions
544         try {
545             final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets();
546 
547             try {
548                 final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE);
549 
550                 setAngleInfo(paths, packageName, devOptIn, assetsFd.getFileDescriptor(),
551                         assetsFd.getStartOffset(), assetsFd.getLength());
552 
553                 assetsFd.close();
554 
555                 return true;
556             } catch (IOException e) {
557                 Log.w(TAG, "Failed to get AssetFileDescriptor for " + ANGLE_RULES_FILE
558                         + " from '" + anglePkgName + "': " + e);
559             }
560         } catch (PackageManager.NameNotFoundException e) {
561             Log.w(TAG, "Failed to get AssetManager for '" + anglePkgName + "': " + e);
562         }
563 
564         return false;
565     }
566 
567     /**
568      * Pull ANGLE whitelist from GlobalSettings and compare against current package
569      */
checkAngleWhitelist(Context context, Bundle bundle, String packageName)570     private static boolean checkAngleWhitelist(Context context, Bundle bundle, String packageName) {
571         final ContentResolver contentResolver = context.getContentResolver();
572         final List<String> angleWhitelist =
573                 getGlobalSettingsString(contentResolver, bundle,
574                     Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST);
575 
576         if (DEBUG) Log.v(TAG, "ANGLE whitelist: " + angleWhitelist);
577 
578         return angleWhitelist.contains(packageName);
579     }
580 
581     /**
582      * Pass ANGLE details down to trigger enable logic
583      *
584      * @param context
585      * @param bundle
586      * @param packageName
587      * @return true: ANGLE setup successfully
588      *         false: ANGLE not setup (not on whitelist, ANGLE not present, etc.)
589      */
setupAngle(Context context, Bundle bundle, PackageManager pm, String packageName)590     public boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
591             String packageName) {
592 
593         if (!shouldUseAngle(context, bundle, packageName)) {
594             return false;
595         }
596 
597         ApplicationInfo angleInfo = null;
598 
599         // If the developer has specified a debug package over ADB, attempt to find it
600         String anglePkgName = getAngleDebugPackage(context, bundle);
601         if (!anglePkgName.isEmpty()) {
602             Log.i(TAG, "ANGLE debug package enabled: " + anglePkgName);
603             try {
604                 // Note the debug package does not have to be pre-installed
605                 angleInfo = pm.getApplicationInfo(anglePkgName, 0);
606             } catch (PackageManager.NameNotFoundException e) {
607                 Log.w(TAG, "ANGLE debug package '" + anglePkgName + "' not installed");
608                 return false;
609             }
610         }
611 
612         // Otherwise, check to see if ANGLE is properly installed
613         if (angleInfo == null) {
614             anglePkgName = getAnglePackageName(pm);
615             if (!anglePkgName.isEmpty()) {
616                 Log.i(TAG, "ANGLE package enabled: " + anglePkgName);
617                 try {
618                     // Production ANGLE libraries must be pre-installed as a system app
619                     angleInfo = pm.getApplicationInfo(anglePkgName,
620                             PackageManager.MATCH_SYSTEM_ONLY);
621                 } catch (PackageManager.NameNotFoundException e) {
622                     Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed");
623                     return false;
624                 }
625             } else {
626                 Log.e(TAG, "Failed to find ANGLE package.");
627                 return false;
628             }
629         }
630 
631         final String abi = chooseAbi(angleInfo);
632 
633         // Build a path that includes installed native libs and APK
634         final String paths = angleInfo.nativeLibraryDir
635                 + File.pathSeparator
636                 + angleInfo.sourceDir
637                 + "!/lib/"
638                 + abi;
639 
640         if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths);
641 
642         // If the user has set the developer option to something other than default,
643         // we need to call setupAngleRulesApk() with the package name and the developer
644         // option value (native/angle/other). Then later when we are actually trying to
645         // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
646         // and can confidently answer yes/no based on the previously set developer
647         // option value.
648         final String devOptIn = getDriverForPkg(context, bundle, packageName);
649 
650         if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) {
651             // We setup ANGLE with a temp rules file, so we're done here.
652             return true;
653         }
654 
655         if (setupAngleRulesApk(anglePkgName, angleInfo, pm, packageName, paths, devOptIn)) {
656             // We setup ANGLE with rules from the APK, so we're done here.
657             return true;
658         }
659 
660         return false;
661     }
662 
663     /**
664      * Determine if the "ANGLE In Use" dialog box should be shown.
665      */
shouldShowAngleInUseDialogBox(Context context)666     private boolean shouldShowAngleInUseDialogBox(Context context) {
667         try {
668             ContentResolver contentResolver = context.getContentResolver();
669             final int showDialogBox = Settings.Global.getInt(contentResolver,
670                     Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX);
671 
672             return (showDialogBox == 1);
673         } catch (Settings.SettingNotFoundException | SecurityException e) {
674             // Do nothing and move on
675         }
676 
677         // No setting, so assume false
678         return false;
679     }
680 
681     /**
682      * Determine if ANGLE will be used and setup the environment
683      */
setupAndUseAngle(Context context, String packageName)684     private boolean setupAndUseAngle(Context context, String packageName) {
685         // Need to make sure we are evaluating ANGLE usage for the correct circumstances
686         if (!setupAngle(context, null, context.getPackageManager(), packageName)) {
687             Log.v(TAG, "Package '" + packageName + "' should not use ANGLE");
688             return false;
689         }
690 
691         final boolean useAngle = getShouldUseAngle(packageName);
692         Log.v(TAG, "Package '" + packageName + "' should use ANGLE = '" + useAngle + "'");
693 
694         return useAngle;
695     }
696 
697     /**
698      * Show the ANGLE in Use Dialog Box
699      * @param context
700      */
showAngleInUseDialogBox(Context context)701     public void showAngleInUseDialogBox(Context context) {
702         final String packageName = context.getPackageName();
703 
704         if (shouldShowAngleInUseDialogBox(context) && setupAndUseAngle(context, packageName)) {
705             final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE);
706             String anglePkg = getAnglePackageName(context.getPackageManager());
707             intent.setPackage(anglePkg);
708 
709             context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
710                 @Override
711                 public void onReceive(Context context, Intent intent) {
712                     Bundle results = getResultExtras(true);
713 
714                     String toastMsg = results.getString(INTENT_KEY_A4A_TOAST_MESSAGE);
715                     final Toast toast = Toast.makeText(context, toastMsg, Toast.LENGTH_LONG);
716                     toast.show();
717                 }
718             }, null, Activity.RESULT_OK, null, null);
719         }
720     }
721 
722     /**
723      * Return the driver package name to use. Return null for system driver.
724      */
chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai)725     private static String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) {
726         final String gameDriver = SystemProperties.get(PROPERTY_GFX_DRIVER);
727         final boolean hasGameDriver = gameDriver != null && !gameDriver.isEmpty();
728 
729         final String prereleaseDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRERELEASE);
730         final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty();
731 
732         if (!hasGameDriver && !hasPrereleaseDriver) {
733             if (DEBUG) Log.v(TAG, "Neither Game Driver nor prerelease driver is supported.");
734             return null;
735         }
736 
737         // To minimize risk of driver updates crippling the device beyond user repair, never use an
738         // updated driver for privileged or non-updated system apps. Presumably pre-installed apps
739         // were tested thoroughly with the pre-installed driver.
740         if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) {
741             if (DEBUG) Log.v(TAG, "Ignoring driver package for privileged/non-updated system app.");
742             return null;
743         }
744 
745         final boolean enablePrereleaseDriver =
746                 (ai.metaData != null && ai.metaData.getBoolean(METADATA_DEVELOPER_DRIVER_ENABLE))
747                 || isDebuggable();
748 
749         // Priority for Game Driver settings global on confliction (Higher priority comes first):
750         // 1. GAME_DRIVER_ALL_APPS
751         // 2. GAME_DRIVER_OPT_OUT_APPS
752         // 3. GAME_DRIVER_PRERELEASE_OPT_IN_APPS
753         // 4. GAME_DRIVER_OPT_IN_APPS
754         // 5. GAME_DRIVER_BLACKLIST
755         // 6. GAME_DRIVER_WHITELIST
756         switch (coreSettings.getInt(Settings.Global.GAME_DRIVER_ALL_APPS, 0)) {
757             case GAME_DRIVER_GLOBAL_OPT_IN_OFF:
758                 if (DEBUG) Log.v(TAG, "Game Driver is turned off on this device.");
759                 return null;
760             case GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER:
761                 if (DEBUG) Log.v(TAG, "All apps opt in to use Game Driver.");
762                 return hasGameDriver ? gameDriver : null;
763             case GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER:
764                 if (DEBUG) Log.v(TAG, "All apps opt in to use prerelease driver.");
765                 return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
766             case GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT:
767             default:
768                 break;
769         }
770 
771         final String appPackageName = ai.packageName;
772         if (getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_OUT_APPS)
773                         .contains(appPackageName)) {
774             if (DEBUG) Log.v(TAG, "App opts out for Game Driver.");
775             return null;
776         }
777 
778         if (getGlobalSettingsString(
779                     null, coreSettings, Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS)
780                         .contains(appPackageName)) {
781             if (DEBUG) Log.v(TAG, "App opts in for prerelease Game Driver.");
782             return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
783         }
784 
785         // Early return here since the rest logic is only for Game Driver.
786         if (!hasGameDriver) {
787             if (DEBUG) Log.v(TAG, "Game Driver is not supported on the device.");
788             return null;
789         }
790 
791         final boolean isOptIn =
792                 getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_IN_APPS)
793                         .contains(appPackageName);
794         final List<String> whitelist =
795                 getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_WHITELIST);
796         if (!isOptIn && whitelist.indexOf(GAME_DRIVER_WHITELIST_ALL) != 0
797                 && !whitelist.contains(appPackageName)) {
798             if (DEBUG) Log.v(TAG, "App is not on the whitelist for Game Driver.");
799             return null;
800         }
801 
802         // If the application is not opted-in, then check whether it's on the blacklist,
803         // terminate early if it's on the blacklist and fallback to system driver.
804         if (!isOptIn
805                 && getGlobalSettingsString(
806                         null, coreSettings, Settings.Global.GAME_DRIVER_BLACKLIST)
807                            .contains(appPackageName)) {
808             if (DEBUG) Log.v(TAG, "App is on the blacklist for Game Driver.");
809             return null;
810         }
811 
812         return gameDriver;
813     }
814 
815     /**
816      * Choose whether the current process should use the builtin or an updated driver.
817      */
chooseDriver( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai)818     private static boolean chooseDriver(
819             Context context, Bundle coreSettings, PackageManager pm, String packageName,
820             ApplicationInfo ai) {
821         final String driverPackageName = chooseDriverInternal(coreSettings, ai);
822         if (driverPackageName == null) {
823             return false;
824         }
825 
826         final PackageInfo driverPackageInfo;
827         try {
828             driverPackageInfo = pm.getPackageInfo(driverPackageName,
829                     PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA);
830         } catch (PackageManager.NameNotFoundException e) {
831             Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
832             return false;
833         }
834 
835         // O drivers are restricted to the sphal linker namespace, so don't try to use
836         // packages unless they declare they're compatible with that restriction.
837         final ApplicationInfo driverAppInfo = driverPackageInfo.applicationInfo;
838         if (driverAppInfo.targetSdkVersion < Build.VERSION_CODES.O) {
839             if (DEBUG) {
840                 Log.w(TAG, "updated driver package is not known to be compatible with O");
841             }
842             return false;
843         }
844 
845         final String abi = chooseAbi(driverAppInfo);
846         if (abi == null) {
847             if (DEBUG) {
848                 // This is the normal case for the pre-installed empty driver package, don't spam
849                 if (driverAppInfo.isUpdatedSystemApp()) {
850                     Log.w(TAG, "updated driver package has no compatible native libraries");
851                 }
852             }
853             return false;
854         }
855 
856         final StringBuilder sb = new StringBuilder();
857         sb.append(driverAppInfo.nativeLibraryDir)
858           .append(File.pathSeparator);
859         sb.append(driverAppInfo.sourceDir)
860           .append("!/lib/")
861           .append(abi);
862         final String paths = sb.toString();
863         final String sphalLibraries = getSphalLibraries(context, driverPackageName);
864         if (DEBUG) {
865             Log.v(TAG,
866                     "gfx driver package search path: " + paths
867                             + ", required sphal libraries: " + sphalLibraries);
868         }
869         setDriverPathAndSphalLibraries(paths, sphalLibraries);
870 
871         if (driverAppInfo.metaData == null) {
872             throw new NullPointerException("apk's meta-data cannot be null");
873         }
874 
875         final String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME);
876         if (driverBuildTime == null || driverBuildTime.isEmpty()) {
877             throw new IllegalArgumentException("com.android.gamedriver.build_time is not set");
878         }
879         // driver_build_time in the meta-data is in "L<Unix epoch timestamp>" format. e.g. L123456.
880         // Long.parseLong will throw if the meta-data "driver_build_time" is not set properly.
881         setGpuStats(driverPackageName, driverPackageInfo.versionName, driverAppInfo.longVersionCode,
882                 Long.parseLong(driverBuildTime.substring(1)), packageName, 0);
883 
884         return true;
885     }
886 
chooseAbi(ApplicationInfo ai)887     private static String chooseAbi(ApplicationInfo ai) {
888         final String isa = VMRuntime.getCurrentInstructionSet();
889         if (ai.primaryCpuAbi != null &&
890                 isa.equals(VMRuntime.getInstructionSet(ai.primaryCpuAbi))) {
891             return ai.primaryCpuAbi;
892         }
893         if (ai.secondaryCpuAbi != null &&
894                 isa.equals(VMRuntime.getInstructionSet(ai.secondaryCpuAbi))) {
895             return ai.secondaryCpuAbi;
896         }
897         return null;
898     }
899 
getSphalLibraries(Context context, String driverPackageName)900     private static String getSphalLibraries(Context context, String driverPackageName) {
901         try {
902             final Context driverContext =
903                     context.createPackageContext(driverPackageName, Context.CONTEXT_RESTRICTED);
904             final BufferedReader reader = new BufferedReader(new InputStreamReader(
905                     driverContext.getAssets().open(GAME_DRIVER_SPHAL_LIBRARIES_FILENAME)));
906             final ArrayList<String> assetStrings = new ArrayList<>();
907             for (String assetString; (assetString = reader.readLine()) != null;) {
908                 assetStrings.add(assetString);
909             }
910             return String.join(":", assetStrings);
911         } catch (PackageManager.NameNotFoundException e) {
912             if (DEBUG) {
913                 Log.w(TAG, "Driver package '" + driverPackageName + "' not installed");
914             }
915         } catch (IOException e) {
916             if (DEBUG) {
917                 Log.w(TAG, "Failed to load '" + GAME_DRIVER_SPHAL_LIBRARIES_FILENAME + "'");
918             }
919         }
920         return "";
921     }
922 
isDebuggable()923     private static native boolean isDebuggable();
setLayerPaths(ClassLoader classLoader, String layerPaths)924     private static native void setLayerPaths(ClassLoader classLoader, String layerPaths);
setDebugLayers(String layers)925     private static native void setDebugLayers(String layers);
setDebugLayersGLES(String layers)926     private static native void setDebugLayersGLES(String layers);
setDriverPathAndSphalLibraries(String path, String sphalLibraries)927     private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion)928     private static native void setGpuStats(String driverPackageName, String driverVersionName,
929             long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
setAngleInfo(String path, String appPackage, String devOptIn, FileDescriptor rulesFd, long rulesOffset, long rulesLength)930     private static native void setAngleInfo(String path, String appPackage, String devOptIn,
931             FileDescriptor rulesFd, long rulesOffset, long rulesLength);
getShouldUseAngle(String packageName)932     private static native boolean getShouldUseAngle(String packageName);
setInjectLayersPrSetDumpable()933     private static native boolean setInjectLayersPrSetDumpable();
934 }
935