// Copyright 2012 The Chromium Authors // 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 static android.content.Context.UI_MODE_SERVICE; import android.app.UiModeManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Process; import android.text.TextUtils; import org.jni_zero.CalledByNative; import org.chromium.base.compat.ApiHelperForP; import org.chromium.build.BuildConfig; /** * 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 ApplicationInfo sBrowserApplicationInfo; private static boolean sInitialized; /** * The package name of the host app which has loaded WebView, retrieved from the application * context. In the context of the SDK Runtime, the package name of the app that owns this * particular instance of the SDK Runtime will also be included. * e.g. com.google.android.sdksandbox:com:com.example.myappwithads */ public final String hostPackageName; /** * The application name (e.g. "Chrome"). For WebView, this is name of the embedding app. * In the context of the SDK Runtime, this is the name of the app that owns this particular * instance of the SDK Runtime. */ public final String hostPackageLabel; /** * By default: same as versionCode. For WebView: versionCode of the embedding app. * In the context of the SDK Runtime, this is the versionCode of the app that owns this * particular instance of the SDK Runtime. */ public final long hostVersionCode; /** * The packageName of Chrome/WebView. Use application context for host app packageName. * Same as the host information within any child process. */ public final String packageName; /** The versionCode of the apk. */ public final long 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; /** 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; /** Whether we're running on Android TV or not */ public final boolean isTV; /** Whether we're running on an Android Automotive OS device or not. */ public final boolean isAutomotive; /** Whether we're running on an Android Foldable OS device or not. */ public final boolean isFoldable; /** * version of the FEATURE_VULKAN_DEQP_LEVEL, if available. Queried only on Android T or above */ public final int vulkanDeqpLevel; private static class Holder { private static final BuildInfo INSTANCE = new BuildInfo(); } @CalledByNative private static String[] getAll() { return BuildInfo.getInstance().getAllProperties(); } /** Returns a serialized string array of all properties of this class. */ private String[] getAllProperties() { // This implementation needs to be kept in sync with the native BuildInfo constructor. 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(hostVersionCode), hostPackageLabel, packageName, String.valueOf(versionCode), versionName, androidBuildFingerprint, gmsVersionCode, installerPackageName, abiString, customThemes, resourcesVersion, String.valueOf( ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion), isDebugAndroid() ? "1" : "0", isTV ? "1" : "0", Build.VERSION.INCREMENTAL, Build.HARDWARE, Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ? "1" : "0", isAutomotive ? "1" : "0", Build.VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE ? "1" : "0", targetsAtLeastU() ? "1" : "0", Build.VERSION.CODENAME, String.valueOf(vulkanDeqpLevel), isFoldable ? "1" : "0", }; } private static String nullToEmpty(CharSequence seq) { return seq == null ? "" : seq.toString(); } /** * Return the "long" version code of the given PackageInfo. * Does the right thing for before/after Android P when this got wider. */ public static long packageVersionCode(PackageInfo pi) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { return ApiHelperForP.getLongVersionCode(pi); } else { return pi.versionCode; } } /** * @param packageInfo Package for Chrome/WebView (as opposed to host app). */ public static void setBrowserPackageInfo(PackageInfo packageInfo) { assert !sInitialized; sBrowserPackageInfo = packageInfo; } /** * @return ApplicationInfo for Chrome/WebView (as opposed to host app). */ public ApplicationInfo getBrowserApplicationInfo() { return sBrowserApplicationInfo; } public static BuildInfo getInstance() { // Some tests mock out things BuildInfo is based on, so disable caching in tests to ensure // such mocking is not defeated by caching. if (BuildConfig.IS_FOR_TEST) { return new BuildInfo(); } return Holder.INSTANCE; } private BuildInfo() { sInitialized = true; Context appContext = ContextUtils.getApplicationContext(); String appContextPackageName = appContext.getPackageName(); PackageManager pm = appContext.getPackageManager(); String providedHostPackageName = null; String providedHostPackageLabel = null; String providedPackageName = null; String providedPackageVersionName = null; Long providedHostVersionCode = null; Long providedPackageVersionCode = null; // The child processes are running in an isolated process so they can't grab a lot of // package information in the same way that we normally would retrieve them. To get around // this, we feed the information as command line switches. if (CommandLine.isInitialized()) { CommandLine commandLine = CommandLine.getInstance(); providedHostPackageName = commandLine.getSwitchValue(BaseSwitches.HOST_PACKAGE_NAME); providedHostPackageLabel = commandLine.getSwitchValue(BaseSwitches.HOST_PACKAGE_LABEL); providedPackageName = commandLine.getSwitchValue(BaseSwitches.PACKAGE_NAME); providedPackageVersionName = commandLine.getSwitchValue(BaseSwitches.PACKAGE_VERSION_NAME); if (commandLine.hasSwitch(BaseSwitches.HOST_VERSION_CODE)) { providedHostVersionCode = Long.parseLong(commandLine.getSwitchValue(BaseSwitches.HOST_VERSION_CODE)); } if (commandLine.hasSwitch(BaseSwitches.PACKAGE_VERSION_CODE)) { providedPackageVersionCode = Long.parseLong( commandLine.getSwitchValue(BaseSwitches.PACKAGE_VERSION_CODE)); } } boolean hostInformationProvided = providedHostPackageName != null && providedHostPackageLabel != null && providedHostVersionCode != null && providedPackageName != null && providedPackageVersionName != null && providedPackageVersionCode != null; // We want to retrieve the original package installed to verify to host package name. // In the case of the SDK Runtime, we would like to retrieve the package name loading the // SDK. String appInstalledPackageName = appContextPackageName; if (hostInformationProvided) { hostPackageName = providedHostPackageName; hostPackageLabel = providedHostPackageLabel; hostVersionCode = providedHostVersionCode; versionName = providedPackageVersionName; packageName = providedPackageName; versionCode = providedPackageVersionCode; sBrowserApplicationInfo = appContext.getApplicationInfo(); } else { // The SDK Qualified package name will retrieve the same information as // appInstalledPackageName but prefix it with the SDK Sandbox process so that we can // tell SDK Runtime data apart from regular data in our logs and metrics. String sdkQualifiedName = appInstalledPackageName; // TODO(bewise): There isn't currently an official API to grab the host package name // with the SDK Runtime. We can work around this because SDKs loaded in the SDK // Runtime have the host UID + 10000. This should be updated if a public API comes // along that we can use. // You can see more about this in the Android source: // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/Process.java;l=292;drc=47fffdd53115a9af1820e3f89d8108745be4b55d if (ContextUtils.isSdkSandboxProcess()) { final int hostId = Process.myUid() - 10000; final String[] packageNames = pm.getPackagesForUid(hostId); if (packageNames.length > 0) { // We could end up with more than one package name if the app used a // sharedUserId but these are deprecated so this is a safe bet to rely on the // first package name. appInstalledPackageName = packageNames[0]; sdkQualifiedName += ":" + appInstalledPackageName; } } PackageInfo pi = PackageUtils.getPackageInfo(appInstalledPackageName, 0); hostPackageName = sdkQualifiedName; hostPackageLabel = nullToEmpty(pm.getApplicationLabel(pi.applicationInfo)); hostVersionCode = packageVersionCode(pi); if (sBrowserPackageInfo != null) { packageName = sBrowserPackageInfo.packageName; versionCode = packageVersionCode(sBrowserPackageInfo); versionName = nullToEmpty(sBrowserPackageInfo.versionName); sBrowserApplicationInfo = sBrowserPackageInfo.applicationInfo; sBrowserPackageInfo = null; } else { packageName = appContextPackageName; versionCode = hostVersionCode; versionName = nullToEmpty(pi.versionName); sBrowserApplicationInfo = appContext.getApplicationInfo(); } } installerPackageName = nullToEmpty(pm.getInstallerPackageName(appInstalledPackageName)); PackageInfo gmsPackageInfo = PackageUtils.getPackageInfo("com.google.android.gms", 0); gmsVersionCode = gmsPackageInfo != null ? String.valueOf(packageVersionCode(gmsPackageInfo)) : "gms versionCode not available."; // 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). customThemes = String.valueOf(PackageUtils.isPackageInstalled("projekt.substratum")); 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; abiString = TextUtils.join(", ", Build.SUPPORTED_ABIS); // 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)); // See https://developer.android.com/training/tv/start/hardware.html#runtime-check. UiModeManager uiModeManager = (UiModeManager) appContext.getSystemService(UI_MODE_SERVICE); isTV = uiModeManager != null && uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; boolean isAutomotive; try { isAutomotive = pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } catch (SecurityException e) { Log.e(TAG, "Unable to query for Automotive system feature", e); // `hasSystemFeature` can possibly throw an exception on modified instances of // Android. In this case, assume the device is not a car since automotive vehicles // should not have such a modification. isAutomotive = false; } this.isAutomotive = isAutomotive; // Detect whether device is foldable. this.isFoldable = Build.VERSION.SDK_INT >= VERSION_CODES.R && pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_HINGE_ANGLE); int vulkanLevel = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { FeatureInfo[] features = pm.getSystemAvailableFeatures(); if (features != null) { for (FeatureInfo feature : features) { if (PackageManager.FEATURE_VULKAN_DEQP_LEVEL.equals(feature.name)) { vulkanLevel = feature.version; break; } } } } vulkanDeqpLevel = vulkanLevel; } /** * Check if this is a debuggable build of Android. * 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); } /* * Check if the app is declared debuggable in its manifest. * In WebView, this refers to the host app. */ public static boolean isDebugApp() { int appFlags = ContextUtils.getApplicationContext().getApplicationInfo().flags; return (appFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; } /** * Check if this is either a debuggable build of Android or of the host app. * Use this to enable developer-only features. */ public static boolean isDebugAndroidOrApp() { return isDebugAndroid() || isDebugApp(); } /** * Checks if the application targets the T SDK or later. * @deprecated Chrome callers should just remove this test - Chrome targets T or later now. * WebView callers should just inline the logic below to check the target level of the embedding * App when necessary. */ @Deprecated public static boolean targetsAtLeastT() { int target = ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion; // Now that the public SDK is upstreamed we can use the defined constant. return target >= VERSION_CODES.TIRAMISU; } /** * Checks if the application targets pre-release SDK U. * This must be manually maintained as the SDK goes through finalization! * Avoid depending on this if possible; this is only intended for WebView. */ public static boolean targetsAtLeastU() { int target = ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion; // Logic for pre-API-finalization: // return BuildCompat.isAtLeastU() && target == Build.VERSION_CODES.CUR_DEVELOPMENT; // Logic for after API finalization but before public SDK release has to just hardcode the // appropriate SDK integer. This will include Android builds with the finalized SDK, and // also pre-API-finalization builds (because CUR_DEVELOPMENT == 10000). // return target >= 34; // Now that the public SDK is upstreamed we can use the defined constant. All users of this // should now just inline this check themselves. return target >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE; } }