1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base;
6 
7 import android.content.Context;
8 import android.content.pm.PackageInfo;
9 import android.content.pm.PackageManager;
10 import android.content.pm.PackageManager.NameNotFoundException;
11 import android.os.Build;
12 import android.os.Build.VERSION;
13 import android.text.TextUtils;
14 
15 import org.chromium.base.annotations.CalledByNative;
16 
17 /**
18  * BuildInfo is a utility class providing easy access to {@link PackageInfo} information. This is
19  * primarily of use for accessing package information from native code.
20  */
21 public class BuildInfo {
22     private static final String TAG = "BuildInfo";
23     private static final int MAX_FINGERPRINT_LENGTH = 128;
24 
25     private static PackageInfo sBrowserPackageInfo;
26     private static boolean sInitialized;
27 
28     /** The application name (e.g. "Chrome"). For WebView, this is name of the embedding app. */
29     public final String hostPackageLabel;
30     /** By default: same as versionCode. For WebView: versionCode of the embedding app. */
31     public final int hostVersionCode;
32     /** The packageName of Chrome/WebView. Use application context for host app packageName. */
33     public final String packageName;
34     /** The versionCode of the apk. */
35     public final int versionCode;
36     /** The versionName of Chrome/WebView. Use application context for host app versionName. */
37     public final String versionName;
38     /** Result of PackageManager.getInstallerPackageName(). Never null, but may be "". */
39     public final String installerPackageName;
40     /** The versionCode of Play Services (for crash reporting). */
41     public final String gmsVersionCode;
42     /** Formatted ABI string (for crash reporting). */
43     public final String abiString;
44     /** Truncated version of Build.FINGERPRINT (for crash reporting). */
45     public final String androidBuildFingerprint;
46     /** A string that is different each time the apk changes. */
47     public final String extractedFileSuffix;
48     /** Whether or not the device has apps installed for using custom themes. */
49     public final String customThemes;
50     /** Product version as stored in Android resources. */
51     public final String resourcesVersion;
52 
53     private static class Holder { private static BuildInfo sInstance = new BuildInfo(); }
54 
55     @CalledByNative
getAll()56     private static String[] getAll() {
57         BuildInfo buildInfo = getInstance();
58         String hostPackageName = ContextUtils.getApplicationContext().getPackageName();
59         return new String[] {
60                 Build.BRAND, Build.DEVICE, Build.ID, Build.MANUFACTURER, Build.MODEL,
61                 String.valueOf(Build.VERSION.SDK_INT), Build.TYPE, Build.BOARD, hostPackageName,
62                 String.valueOf(buildInfo.hostVersionCode), buildInfo.hostPackageLabel,
63                 buildInfo.packageName, String.valueOf(buildInfo.versionCode), buildInfo.versionName,
64                 buildInfo.androidBuildFingerprint, buildInfo.gmsVersionCode,
65                 buildInfo.installerPackageName, buildInfo.abiString, BuildConfig.FIREBASE_APP_ID,
66                 buildInfo.customThemes, buildInfo.resourcesVersion, buildInfo.extractedFileSuffix,
67                 isAtLeastP() ? "1" : "0",
68         };
69     }
70 
nullToEmpty(CharSequence seq)71     private static String nullToEmpty(CharSequence seq) {
72         return seq == null ? "" : seq.toString();
73     }
74 
75     /**
76      * @param packageInfo Package for Chrome/WebView (as opposed to host app).
77      */
setBrowserPackageInfo(PackageInfo packageInfo)78     public static void setBrowserPackageInfo(PackageInfo packageInfo) {
79         assert !sInitialized;
80         sBrowserPackageInfo = packageInfo;
81     }
82 
getInstance()83     public static BuildInfo getInstance() {
84         return Holder.sInstance;
85     }
86 
BuildInfo()87     private BuildInfo() {
88         sInitialized = true;
89         try {
90             Context appContext = ContextUtils.getApplicationContext();
91             String hostPackageName = appContext.getPackageName();
92             PackageManager pm = appContext.getPackageManager();
93             PackageInfo pi = pm.getPackageInfo(hostPackageName, 0);
94             hostVersionCode = pi.versionCode;
95             if (sBrowserPackageInfo != null) {
96                 packageName = sBrowserPackageInfo.packageName;
97                 versionCode = sBrowserPackageInfo.versionCode;
98                 versionName = nullToEmpty(sBrowserPackageInfo.versionName);
99                 sBrowserPackageInfo = null;
100             } else {
101                 packageName = hostPackageName;
102                 versionCode = hostVersionCode;
103                 versionName = nullToEmpty(pi.versionName);
104             }
105 
106             hostPackageLabel = nullToEmpty(pm.getApplicationLabel(pi.applicationInfo));
107             installerPackageName = nullToEmpty(pm.getInstallerPackageName(packageName));
108 
109             PackageInfo gmsPackageInfo = null;
110             try {
111                 gmsPackageInfo = pm.getPackageInfo("com.google.android.gms", 0);
112             } catch (NameNotFoundException e) {
113                 Log.d(TAG, "GMS package is not found.", e);
114             }
115             gmsVersionCode = gmsPackageInfo != null ? String.valueOf(gmsPackageInfo.versionCode)
116                                                     : "gms versionCode not available.";
117 
118             String hasCustomThemes = "true";
119             try {
120                 // Substratum is a theme engine that enables users to use custom themes provided
121                 // by theme apps. Sometimes these can cause crashs if not installed correctly.
122                 // These crashes can be difficult to debug, so knowing if the theme manager is
123                 // present on the device is useful (http://crbug.com/820591).
124                 pm.getPackageInfo("projekt.substratum", 0);
125             } catch (NameNotFoundException e) {
126                 hasCustomThemes = "false";
127             }
128             customThemes = hasCustomThemes;
129 
130             String currentResourcesVersion = "Not Enabled";
131             // Controlled by target specific build flags.
132             if (BuildConfig.R_STRING_PRODUCT_VERSION != 0) {
133                 try {
134                     // This value can be compared with the actual product version to determine if
135                     // corrupted resources were the cause of a crash. This can happen if the app
136                     // loads resources from the outdated package  during an update
137                     // (http://crbug.com/820591).
138                     currentResourcesVersion = ContextUtils.getApplicationContext().getString(
139                             BuildConfig.R_STRING_PRODUCT_VERSION);
140                 } catch (Exception e) {
141                     currentResourcesVersion = "Not found";
142                 }
143             }
144             resourcesVersion = currentResourcesVersion;
145 
146             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
147                 abiString = TextUtils.join(", ", Build.SUPPORTED_ABIS);
148             } else {
149                 abiString = String.format("ABI1: %s, ABI2: %s", Build.CPU_ABI, Build.CPU_ABI2);
150             }
151 
152             // Use lastUpdateTime when developing locally, since versionCode does not normally
153             // change in this case.
154             long version = versionCode > 10 ? versionCode : pi.lastUpdateTime;
155             extractedFileSuffix = String.format("@%x", version);
156 
157             // The value is truncated, as this is used for crash and UMA reporting.
158             androidBuildFingerprint = Build.FINGERPRINT.substring(
159                     0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH));
160         } catch (NameNotFoundException e) {
161             throw new RuntimeException(e);
162         }
163     }
164 
165     /**
166      * Check if this is a debuggable build of Android. Use this to enable developer-only features.
167      */
isDebugAndroid()168     public static boolean isDebugAndroid() {
169         return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE);
170     }
171 
172     // The markers Begin:BuildCompat and End:BuildCompat delimit code
173     // that is autogenerated from Android sources.
174     // Begin:BuildCompat P
175 
176     /**
177      * Checks if the device is running on a pre-release version of Android P or newer.
178      * <p>
179      * @return {@code true} if P APIs are available for use, {@code false} otherwise
180      */
isAtLeastP()181     public static boolean isAtLeastP() {
182         return VERSION.SDK_INT >= 28;
183     }
184 
185     /**
186      * Checks if the application targets at least released SDK P
187      */
targetsAtLeastP()188     public static boolean targetsAtLeastP() {
189         return ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion >= 28;
190     }
191 
192     // End:BuildCompat
193 }
194