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