433 lines
18 KiB
Java
433 lines
18 KiB
Java
// 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;
|
|
}
|
|
}
|