// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.base; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.os.Build.VERSION; import android.text.TextUtils; import org.chromium.base.annotations.CalledByNative; /** * BuildInfo is a utility class providing easy access to {@link PackageInfo} information. This is * primarily of use for accessing package information from native code. */ public class BuildInfo { private static final String TAG = "BuildInfo"; private static final int MAX_FINGERPRINT_LENGTH = 128; private static PackageInfo sBrowserPackageInfo; private static boolean sInitialized; /** The application name (e.g. "Chrome"). For WebView, this is name of the embedding app. */ public final String hostPackageLabel; /** By default: same as versionCode. For WebView: versionCode of the embedding app. */ public final int hostVersionCode; /** The packageName of Chrome/WebView. Use application context for host app packageName. */ public final String packageName; /** The versionCode of the apk. */ public final int versionCode; /** The versionName of Chrome/WebView. Use application context for host app versionName. */ public final String versionName; /** Result of PackageManager.getInstallerPackageName(). Never null, but may be "". */ public final String installerPackageName; /** The versionCode of Play Services (for crash reporting). */ public final String gmsVersionCode; /** Formatted ABI string (for crash reporting). */ public final String abiString; /** Truncated version of Build.FINGERPRINT (for crash reporting). */ public final String androidBuildFingerprint; /** A string that is different each time the apk changes. */ public final String extractedFileSuffix; /** Whether or not the device has apps installed for using custom themes. */ public final String customThemes; /** Product version as stored in Android resources. */ public final String resourcesVersion; private static class Holder { private static BuildInfo sInstance = new BuildInfo(); } @CalledByNative private static String[] getAll() { BuildInfo buildInfo = getInstance(); String hostPackageName = ContextUtils.getApplicationContext().getPackageName(); return new String[] { Build.BRAND, Build.DEVICE, Build.ID, Build.MANUFACTURER, Build.MODEL, String.valueOf(Build.VERSION.SDK_INT), Build.TYPE, Build.BOARD, hostPackageName, String.valueOf(buildInfo.hostVersionCode), buildInfo.hostPackageLabel, buildInfo.packageName, String.valueOf(buildInfo.versionCode), buildInfo.versionName, buildInfo.androidBuildFingerprint, buildInfo.gmsVersionCode, buildInfo.installerPackageName, buildInfo.abiString, BuildConfig.FIREBASE_APP_ID, buildInfo.customThemes, buildInfo.resourcesVersion, buildInfo.extractedFileSuffix, }; } private static String nullToEmpty(CharSequence seq) { return seq == null ? "" : seq.toString(); } /** * @param packageInfo Package for Chrome/WebView (as opposed to host app). */ public static void setBrowserPackageInfo(PackageInfo packageInfo) { assert !sInitialized; sBrowserPackageInfo = packageInfo; } public static BuildInfo getInstance() { return Holder.sInstance; } private BuildInfo() { sInitialized = true; try { Context appContext = ContextUtils.getApplicationContext(); String hostPackageName = appContext.getPackageName(); PackageManager pm = appContext.getPackageManager(); PackageInfo pi = pm.getPackageInfo(hostPackageName, 0); hostVersionCode = pi.versionCode; if (sBrowserPackageInfo != null) { packageName = sBrowserPackageInfo.packageName; versionCode = sBrowserPackageInfo.versionCode; versionName = nullToEmpty(sBrowserPackageInfo.versionName); sBrowserPackageInfo = null; } else { packageName = hostPackageName; versionCode = hostVersionCode; versionName = nullToEmpty(pi.versionName); } hostPackageLabel = nullToEmpty(pm.getApplicationLabel(pi.applicationInfo)); installerPackageName = nullToEmpty(pm.getInstallerPackageName(packageName)); PackageInfo gmsPackageInfo = null; try { gmsPackageInfo = pm.getPackageInfo("com.google.android.gms", 0); } catch (NameNotFoundException e) { Log.d(TAG, "GMS package is not found.", e); } gmsVersionCode = gmsPackageInfo != null ? String.valueOf(gmsPackageInfo.versionCode) : "gms versionCode not available."; String hasCustomThemes = "true"; try { // Substratum is a theme engine that enables users to use custom themes provided // by theme apps. Sometimes these can cause crashs if not installed correctly. // These crashes can be difficult to debug, so knowing if the theme manager is // present on the device is useful (http://crbug.com/820591). pm.getPackageInfo("projekt.substratum", 0); } catch (NameNotFoundException e) { hasCustomThemes = "false"; } customThemes = hasCustomThemes; String currentResourcesVersion = "Not Enabled"; // Controlled by target specific build flags. if (BuildConfig.R_STRING_PRODUCT_VERSION != 0) { try { // This value can be compared with the actual product version to determine if // corrupted resources were the cause of a crash. This can happen if the app // loads resources from the outdated package during an update // (http://crbug.com/820591). currentResourcesVersion = ContextUtils.getApplicationContext().getString( BuildConfig.R_STRING_PRODUCT_VERSION); } catch (Exception e) { currentResourcesVersion = "Not found"; } } resourcesVersion = currentResourcesVersion; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { abiString = TextUtils.join(", ", Build.SUPPORTED_ABIS); } else { abiString = String.format("ABI1: %s, ABI2: %s", Build.CPU_ABI, Build.CPU_ABI2); } // Use lastUpdateTime when developing locally, since versionCode does not normally // change in this case. long version = versionCode > 10 ? versionCode : pi.lastUpdateTime; extractedFileSuffix = String.format("@%x", version); // The value is truncated, as this is used for crash and UMA reporting. androidBuildFingerprint = Build.FINGERPRINT.substring( 0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH)); } catch (NameNotFoundException e) { throw new RuntimeException(e); } } /** * Check if this is a debuggable build of Android. Use this to enable developer-only features. * This is a rough approximation of the hidden API {@code Build.IS_DEBUGGABLE}. */ public static boolean isDebugAndroid() { return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE); } // The markers Begin:BuildCompat and End:BuildCompat delimit code // that is autogenerated from Android sources. // Begin:BuildCompat Q /** * Checks if the device is running on a pre-release version of Android Q or newer. * <p> * <strong>Note:</strong> This method will return {@code false} on devices running release * versions of Android. When Android Q is finalized for release, this method will be deprecated * and all calls should be replaced with {@code Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q}. * * @return {@code true} if Q APIs are available for use, {@code false} otherwise */ public static boolean isAtLeastQ() { return VERSION.CODENAME.length() == 1 && VERSION.CODENAME.charAt(0) >= 'Q' && VERSION.CODENAME.charAt(0) <= 'Z'; } /** * Checks if the application targets pre-release SDK Q */ public static boolean targetsAtLeastQ() { return isAtLeastQ() && ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion == Build.VERSION_CODES.CUR_DEVELOPMENT; } // End:BuildCompat }