/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.pm; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; import static android.os.Build.VERSION_CODES.O; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.apex.ApexInfo; import android.app.ActivityTaskManager; import android.app.ActivityThread; import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.overlay.OverlayPaths; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.content.pm.permission.SplitPermissionInfoParcelable; import android.content.pm.pkg.FrameworkPackageUserState; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.FileUtils; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; import android.permission.PermissionManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; import android.util.DebugUtils; import android.util.DisplayMetrics; import android.util.IntArray; import android.util.Log; import android.util.PackageUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; import android.util.apk.ApkSignatureVerifier; import android.view.Gravity; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.ClassLoaderFactory; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; import libcore.util.EmptyArray; import libcore.util.HexEncoding; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; /** * Parser for package files (APKs) on disk. This supports apps packaged either * as a single "monolithic" APK, or apps packaged as a "cluster" of multiple * APKs in a single directory. *
* Apps packaged as multiple APKs always consist of a single "base" APK (with a * {@code null} split name) and zero or more "split" APKs (with unique split * names). Any subset of those split APKs are a valid install, as long as the * following constraints are met: *
* This performs checking on cluster style packages, such as
* requiring identical package name and version codes, a single base APK,
* and unique split names.
*
* @see PackageParser#parsePackage(File, int)
*/
@UnsupportedAppUsage
public static PackageLite parsePackageLite(File packageFile, int flags)
throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackageLite(packageFile, flags);
} else {
return parseMonolithicPackageLite(packageFile, flags);
}
}
private static PackageLite parseMonolithicPackageLite(File packageFile, int flags)
throws PackageParserException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
final ApkLite baseApk = parseApkLite(packageFile, flags);
final String packagePath = packageFile.getAbsolutePath();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
return new PackageLite(packagePath, baseApk.codePath, baseApk, null, null, null, null, null,
null);
}
static PackageLite parseClusterPackageLite(File packageDir, int flags)
throws PackageParserException {
final File[] files = packageDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"No packages found in split");
}
// Apk directory is directly nested under the current directory
if (files.length == 1 && files[0].isDirectory()) {
return parseClusterPackageLite(files[0], flags);
}
String packageName = null;
int versionCode = 0;
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
final ArrayMap
* This performs checking on cluster style packages, such as
* requiring identical package name and version codes, a single base APK,
* and unique split names.
*
* Note that this does not perform signature verification; that
* must be done separately in {@link #collectCertificates(Package, boolean)}.
*
* If {@code useCaches} is true, the package parser might return a cached
* result from a previous parse of the same {@code packageFile} with the same
* {@code flags}. Note that this method does not check whether {@code packageFile}
* has changed since the last parse, it's up to callers to do so.
*
* @see #parsePackageLite(File, int)
*/
@UnsupportedAppUsage
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
/**
* Equivalent to {@link #parsePackage(File, int, boolean)} with {@code useCaches == false}.
*/
@UnsupportedAppUsage
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
return parsePackage(packageFile, flags, false /* useCaches */);
}
/**
* Parse all APKs contained in the given directory, treating them as a
* single package. This also performs checking, such as requiring
* identical package name and version codes, a single base APK, and unique
* split names.
*
* Note that this does not perform signature verification; that
* must be done separately in
* {@link #collectCertificates(Package, boolean)} .
*/
private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
final PackageLite lite = parseClusterPackageLite(packageDir, 0);
if (mOnlyCoreApps && !lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + packageDir);
}
// Build the split dependency tree.
SparseArray
* Note that this does not perform signature verification; that
* must be done separately in
* {@link #collectCertificates(Package, boolean)}.
*/
@UnsupportedAppUsage
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (mOnlyCoreApps) {
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
}
}
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
pkg.setCodePath(apkFile.getCanonicalPath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} catch (IOException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
String volumeUuid = null;
if (apkPath.startsWith(MNT_EXPAND)) {
final int end = apkPath.indexOf('/', MNT_EXPAND.length());
volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
}
mParseError = PackageManager.INSTALL_SUCCEEDED;
mArchiveSourcePath = apkFile.getAbsolutePath();
if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
XmlResourceParser parser = null;
try {
final int cookie = assets.findCookieForPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
final Resources res = new Resources(assets, mMetrics, null);
final String[] outError = new String[1];
final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
if (pkg == null) {
throw new PackageParserException(mParseError,
apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
}
pkg.setVolumeUuid(volumeUuid);
pkg.setApplicationVolumeUuid(volumeUuid);
pkg.setBaseCodePath(apkPath);
pkg.setSigningDetails(SigningDetails.UNKNOWN);
return pkg;
} catch (PackageParserException e) {
throw e;
} catch (Exception e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
}
}
private void parseSplitApk(Package pkg, int splitIndex, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = pkg.splitCodePaths[splitIndex];
mParseError = PackageManager.INSTALL_SUCCEEDED;
mArchiveSourcePath = apkPath;
if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
final Resources res;
XmlResourceParser parser = null;
try {
// This must always succeed, as the path has been added to the AssetManager before.
final int cookie = assets.findCookieForPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
res = new Resources(assets, mMetrics, null);
final String[] outError = new String[1];
pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError);
if (pkg == null) {
throw new PackageParserException(mParseError,
apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
}
} catch (PackageParserException e) {
throw e;
} catch (Exception e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
}
}
/**
* Parse the manifest of a split APK.
*
* Note that split APKs have many more restrictions on what they're capable
* of doing, so many valid features of a base APK have been carefully
* omitted here.
*/
private Package parseSplitApk(Package pkg, Resources res, XmlResourceParser parser, int flags,
int splitIndex, String[] outError) throws XmlPullParserException, IOException,
PackageParserException {
AttributeSet attrs = parser;
// We parsed manifest tag earlier; just skip past it
parsePackageSplitNames(parser, attrs);
mParseInstrumentationArgs = null;
int type;
boolean foundApp = false;
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals(TAG_APPLICATION)) {
if (foundApp) {
if (RIGID_PARSER) {
outError[0] = "
* If {@code targetCode} is not specified, e.g. the value is {@code null},
* then the {@code targetVers} will be returned unmodified.
*
* Otherwise, the behavior varies based on whether the current platform
* is a pre-release version, e.g. the {@code platformSdkCodenames} array
* has length > 0:
*
* If {@code minCode} is not specified, e.g. the value is {@code null},
* then behavior varies based on the {@code platformSdkVersion}:
*
* Otherwise, the behavior varies based on whether the current platform
* is a pre-release version, e.g. the {@code platformSdkCodenames} array
* has length > 0:
*
* When adding new features, carefully consider if they should also be
* supported by split APKs.
*/
@UnsupportedAppUsage
private boolean parseBaseApplication(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
final ApplicationInfo ai = owner.applicationInfo;
final String pkgName = owner.applicationInfo.packageName;
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestApplication);
ai.iconRes = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
ai.roundIconRes = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_roundIcon, 0);
if (!parsePackageItemInfo(owner, ai, outError,
"
* Note that split APKs have many more restrictions on what they're capable
* of doing, so many valid features of a base APK have been carefully
* omitted here.
*/
private boolean parseSplitApplication(Package owner, Resources res, XmlResourceParser parser,
int flags, int splitIndex, String[] outError)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestApplication);
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_hasCode, true)) {
owner.splitFlags[splitIndex] |= ApplicationInfo.FLAG_HAS_CODE;
}
final String classLoaderName = sa.getString(
com.android.internal.R.styleable.AndroidManifestApplication_classLoader);
if (classLoaderName == null || ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
owner.applicationInfo.splitClassLoaderNames[splitIndex] = classLoaderName;
} else {
outError[0] = "Invalid class loader name: " + classLoaderName;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
final int innerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
ComponentInfo parsedComponent = null;
// IMPORTANT: These must only be cached for a single {@link ActivityInfo.WindowLayout#windowLayoutAffinity} has a fallback metadata used in
* Android R and some variants of pre-R.
*/
private void resolveWindowLayout(Activity activity) {
// There isn't a metadata for us to fall back. Whatever is in layout is correct.
if (activity.metaData == null
|| !activity.metaData.containsKey(METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
return;
}
final ActivityInfo aInfo = activity.info;
// Layout already specifies a value. We should just use that one.
if (aInfo.windowLayout != null && aInfo.windowLayout.windowLayoutAffinity != null) {
return;
}
String windowLayoutAffinity = activity.metaData.getString(
METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY);
if (aInfo.windowLayout == null) {
aInfo.windowLayout = new ActivityInfo.WindowLayout(-1 /* width */,
-1 /* widthFraction */, -1 /* height */, -1 /* heightFraction */,
Gravity.NO_GRAVITY, -1 /* minWidth */, -1 /* minHeight */);
}
aInfo.windowLayout.windowLayoutAffinity = windowLayoutAffinity;
}
private Activity parseActivityAlias(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError,
CachedComponentArgs cachedArgs)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestActivityAlias);
String targetActivity = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestActivityAlias_targetActivity,
Configuration.NATIVE_CONFIG_VERSION);
if (targetActivity == null) {
outError[0] = " Merging two signing lineages will result in a new {@code SigningDetails} instance
* containing the longest common lineage with the most restrictive capabilities. If the two
* lineages contain the same signers with the same capabilities then the instance on which
* this was invoked is returned without any changes. Similarly if neither instance has a
* lineage, or if neither has the same or an ancestor signer then this instance is returned.
*
* Following are some example results of this method for lineages with signers A, B, C, D:
* - lineage B merged with lineage A -> B returns lineage A -> B.
* - lineage A -> B merged with lineage B -> C returns lineage A -> B -> C
* - lineage A -> B with the {@code PERMISSION} capability revoked for A merged with
* lineage A -> B with the {@code SHARED_USER_ID} capability revoked for A returns
* lineage A -> B with both capabilities revoked for A.
* - lineage A -> B -> C merged with lineage A -> B -> D would return the original lineage
* A -> B -> C since the current signer of both instances is not the same or in the
* lineage of the other.
*/
public SigningDetails mergeLineageWith(SigningDetails otherSigningDetails) {
if (!hasPastSigningCertificates()) {
return otherSigningDetails.hasPastSigningCertificates()
&& otherSigningDetails.hasAncestorOrSelf(this) ? otherSigningDetails : this;
}
if (!otherSigningDetails.hasPastSigningCertificates()) {
return this;
}
// Use the utility method to determine which SigningDetails instance is the descendant
// and to confirm that the signing lineage does not diverge.
SigningDetails descendantSigningDetails = getDescendantOrSelf(otherSigningDetails);
if (descendantSigningDetails == null) {
return this;
}
return descendantSigningDetails == this ? mergeLineageWithAncestorOrSelf(
otherSigningDetails) : otherSigningDetails.mergeLineageWithAncestorOrSelf(this);
}
/**
* Merges the signing lineage of this instance with the lineage of the ancestor (or same)
* signer in the provided {@code otherSigningDetails}.
*/
private SigningDetails mergeLineageWithAncestorOrSelf(SigningDetails otherSigningDetails) {
// This method should only be called with instances that contain lineages.
int index = pastSigningCertificates.length - 1;
int otherIndex = otherSigningDetails.pastSigningCertificates.length - 1;
if (index < 0 || otherIndex < 0) {
return this;
}
List The two SigningDetails have a common ancestor if any of the following conditions are
* met:
* - If neither has a lineage and their current signer(s) are equal.
* - If only one has a lineage and the signer of the other is the same or in the lineage.
* - If both have a lineage and their current signers are the same or one is in the lineage
* of the other, and their lineages do not diverge to different signers.
*/
public boolean hasCommonAncestor(SigningDetails otherSigningDetails) {
if (!hasPastSigningCertificates()) {
// If this instance does not have a lineage then it must either be in the ancestry
// of or the same signer of the otherSigningDetails.
return otherSigningDetails.hasAncestorOrSelf(this);
}
if (!otherSigningDetails.hasPastSigningCertificates()) {
return hasAncestorOrSelf(otherSigningDetails);
}
// If both have a lineage then use getDescendantOrSelf to obtain the descendant signing
// details; a null return from that method indicates there is no common lineage between
// the two or that they diverge at a point in the lineage.
return getDescendantOrSelf(otherSigningDetails) != null;
}
/**
* Returns whether this instance is currently signed, or has ever been signed, with a
* signing certificate from the provided {@link Set} of {@code certDigests}.
*
* The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding
* of each trusted certificate with the digest characters in upper case. If this instance
* has multiple signers then all signers must be in the provided {@code Set}. If this
* instance has a signing lineage then this method will return true if any of the previous
* signers in the lineage match one of the entries in the {@code Set}.
*/
public boolean hasAncestorOrSelfWithDigest(Set If this instance and the provided {@code otherSigningDetails} do not share an
* ancestry, or if their lineages diverge then null is returned to indicate there is no
* valid descendant SigningDetails.
*/
private SigningDetails getDescendantOrSelf(SigningDetails otherSigningDetails) {
SigningDetails descendantSigningDetails;
SigningDetails ancestorSigningDetails;
if (hasAncestorOrSelf(otherSigningDetails)) {
// If the otherSigningDetails has the same signer or a signer in the lineage of this
// instance then treat this instance as the descendant.
descendantSigningDetails = this;
ancestorSigningDetails = otherSigningDetails;
} else if (otherSigningDetails.hasAncestor(this)) {
// The above check confirmed that the two instances do not have the same signer and
// the signer of otherSigningDetails is not in this instance's lineage; if this
// signer is in the otherSigningDetails lineage then treat this as the ancestor.
descendantSigningDetails = otherSigningDetails;
ancestorSigningDetails = this;
} else {
// The signers are not the same and neither has the current signer of the other in
// its lineage; return null to indicate there is no descendant signer.
return null;
}
// Once the descent (or same) signer is identified iterate through the ancestry until
// the current signer of the ancestor is found.
int descendantIndex = descendantSigningDetails.pastSigningCertificates.length - 1;
int ancestorIndex = ancestorSigningDetails.pastSigningCertificates.length - 1;
while (descendantIndex >= 0
&& !descendantSigningDetails.pastSigningCertificates[descendantIndex].equals(
ancestorSigningDetails.pastSigningCertificates[ancestorIndex])) {
descendantIndex--;
}
// Since the ancestry was verified above the descendant lineage should never be
// exhausted, but if for some reason the ancestor signer is not found then return null.
if (descendantIndex < 0) {
return null;
}
// Once the common ancestor (or same) signer is found iterate over the lineage of both
// to ensure that they are either the same or one is a subset of the other.
do {
descendantIndex--;
ancestorIndex--;
} while (descendantIndex >= 0 && ancestorIndex >= 0
&& descendantSigningDetails.pastSigningCertificates[descendantIndex].equals(
ancestorSigningDetails.pastSigningCertificates[ancestorIndex]));
// If both lineages still have elements then they diverge and cannot be considered a
// valid common lineage.
if (descendantIndex >= 0 && ancestorIndex >= 0) {
return null;
}
// Since one or both of the lineages was exhausted they are either the same or one is a
// subset of the other; return the valid descendant.
return descendantSigningDetails;
}
/** Returns true if the signing details have one or more signatures. */
public boolean hasSignatures() {
return signatures != null && signatures.length > 0;
}
/** Returns true if the signing details have past signing certificates. */
public boolean hasPastSigningCertificates() {
return pastSigningCertificates != null && pastSigningCertificates.length > 0;
}
/**
* Determines if the provided {@code oldDetails} is an ancestor of or the same as this one.
* If the {@code oldDetails} signing certificate appears in our pastSigningCertificates,
* then that means it has authorized a signing certificate rotation, which eventually leads
* to our certificate, and thus can be trusted. If this method evaluates to true, this
* SigningDetails object should be trusted if the previous one is.
*/
public boolean hasAncestorOrSelf(SigningDetails oldDetails) {
if (this == UNKNOWN || oldDetails == UNKNOWN) {
return false;
}
if (oldDetails.signatures.length > 1) {
// multiple-signer packages cannot rotate signing certs, so we just compare current
// signers for an exact match
return signaturesMatchExactly(oldDetails);
} else {
// we may have signing certificate rotation history, check to see if the oldDetails
// was one of our old signing certificates
return hasCertificate(oldDetails.signatures[0]);
}
}
/**
* Similar to {@code hasAncestorOrSelf}. Returns true only if this {@code SigningDetails}
* is a descendant of {@code oldDetails}, not if they're the same. This is used to
* determine if this object is newer than the provided one.
*/
public boolean hasAncestor(SigningDetails oldDetails) {
if (this == UNKNOWN || oldDetails == UNKNOWN) {
return false;
}
if (this.hasPastSigningCertificates() && oldDetails.signatures.length == 1) {
// the last entry in pastSigningCertificates is the current signer, ignore it
for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
if (pastSigningCertificates[i].equals(oldDetails.signatures[0])) {
return true;
}
}
}
return false;
}
/**
* Returns whether this {@code SigningDetails} has a signer in common with the provided
* {@code otherDetails} with the specified {@code flags} capabilities provided by this
* signer.
*
* Note this method allows for the signing lineage to diverge, so this should only be
* used for instances where the only requirement is a common signer in the lineage with
* the specified capabilities. If the current signer of this instance is an ancestor of
* {@code otherDetails} then {@code true} is immediately returned since the current signer
* has all capabilities granted.
*/
public boolean hasCommonSignerWithCapability(SigningDetails otherDetails,
@CertCapabilities int flags) {
if (this == UNKNOWN || otherDetails == UNKNOWN) {
return false;
}
// If either is signed with more than one signer then both must be signed by the same
// signers to consider the capabilities granted.
if (signatures.length > 1 || otherDetails.signatures.length > 1) {
return signaturesMatchExactly(otherDetails);
}
// The Signature class does not use the granted capabilities in the hashCode
// computation, so a Set can be used to check for a common signer.
Set
* Implementation note: The serialized form for the intent list also contains the name
* of the concrete class that's stored in the list, and assumes that every element of the
* list is of the same type. This is very similar to the original parcelable mechanism.
* We cannot use that directly because IntentInfo extends IntentFilter, which is parcelable
* and is public API. It also declares Parcelable related methods as final which means
* we can't extend them. The approach of using composition instead of inheritance leads to
* a large set of cascading changes in the PackageManagerService, which seem undesirable.
*
*
* WARNING: The list of objects returned by this function might need to be fixed up
* to make sure their owner fields are consistent. See {@code fixupOwner}.
*/
private static void writeIntentsList(ArrayList extends IntentInfo> list, Parcel out,
int flags) {
if (list == null) {
out.writeInt(-1);
return;
}
final int N = list.size();
out.writeInt(N);
// Don't bother writing the component name if the list is empty.
if (N > 0) {
IntentInfo info = list.get(0);
out.writeString(info.getClass().getName());
for (int i = 0; i < N;i++) {
list.get(i).writeIntentInfoToParcel(out, flags);
}
}
}
private static
* Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and {@link
* PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}.
*
*
*
* @param targetVers targetSdkVersion number, if specified in the
* application manifest, or 0 otherwise
* @param targetCode targetSdkVersion code, if specified in the application
* manifest, or {@code null} otherwise
* @param platformSdkCodenames array of allowed pre-release SDK codenames
* for this platform
* @param outError output array to populate with error, if applicable
* @return the targetSdkVersion to use at runtime, or -1 if the package is
* not compatible with this platform
* @hide Exposed for unit testing only.
*/
public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
@Nullable String targetCode, @NonNull String[] platformSdkCodenames,
@NonNull String[] outError) {
// If it's a release SDK, return the version number unmodified.
if (targetCode == null) {
return targetVers;
}
// If it's a pre-release SDK and the codename matches this platform, it
// definitely targets this SDK.
if (matchTargetCode(platformSdkCodenames, targetCode)) {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
// STOPSHIP: hack for the pre-release SDK
if (platformSdkCodenames.length == 0
&& Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
targetCode)) {
Slog.w(TAG, "Package requires development platform " + targetCode
+ ", returning current version " + Build.VERSION.SDK_INT);
return Build.VERSION.SDK_INT;
}
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + targetCode
+ " (current platform is any of "
+ Arrays.toString(platformSdkCodenames) + ")";
} else {
outError[0] = "Requires development platform " + targetCode
+ " but this is a release platform.";
}
return -1;
}
/**
* Computes the minSdkVersion to use at runtime. If the package is not
* compatible with this platform, populates {@code outError[0]} with an
* error message.
*
*
*
*
*
* @param minVers minSdkVersion number, if specified in the application
* manifest, or 1 otherwise
* @param minCode minSdkVersion code, if specified in the application
* manifest, or {@code null} otherwise
* @param platformSdkVersion platform SDK version number, typically
* Build.VERSION.SDK_INT
* @param platformSdkCodenames array of allowed prerelease SDK codenames
* for this platform
* @param outError output array to populate with error, if applicable
* @return the minSdkVersion to use at runtime, or -1 if the package is not
* compatible with this platform
* @hide Exposed for unit testing only.
*/
public static int computeMinSdkVersion(@IntRange(from = 1) int minVers,
@Nullable String minCode, @IntRange(from = 1) int platformSdkVersion,
@NonNull String[] platformSdkCodenames, @NonNull String[] outError) {
// If it's a release SDK, make sure we meet the minimum SDK requirement.
if (minCode == null) {
if (minVers <= platformSdkVersion) {
return minVers;
}
// We don't meet the minimum SDK requirement.
outError[0] = "Requires newer sdk version #" + minVers
+ " (current version is #" + platformSdkVersion + ")";
return -1;
}
// If it's a pre-release SDK and the codename matches this platform, we
// definitely meet the minimum SDK requirement.
if (matchTargetCode(platformSdkCodenames, minCode)) {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
// STOPSHIP: hack for the pre-release SDK
if (platformSdkCodenames.length == 0
&& Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
minCode)) {
Slog.w(TAG, "Package requires min development platform " + minCode
+ ", returning current version " + Build.VERSION.SDK_INT);
return Build.VERSION.SDK_INT;
}
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + minCode
+ " (current platform is any of "
+ Arrays.toString(platformSdkCodenames) + ")";
} else {
outError[0] = "Requires development platform " + minCode
+ " but this is a release platform.";
}
return -1;
}
private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs) {
FeatureInfo fi = new FeatureInfo();
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesFeature);
// Note: don't allow this value to be a reference to a resource
// that may change.
fi.name = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestUsesFeature_name);
fi.version = sa.getInt(
com.android.internal.R.styleable.AndroidManifestUsesFeature_version, 0);
if (fi.name == null) {
fi.reqGlEsVersion = sa.getInt(
com.android.internal.R.styleable.AndroidManifestUsesFeature_glEsVersion,
FeatureInfo.GL_ES_VERSION_UNDEFINED);
}
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestUsesFeature_required, true)) {
fi.flags |= FeatureInfo.FLAG_REQUIRED;
}
sa.recycle();
return fi;
}
private boolean parseUsesStaticLibrary(Package pkg, Resources res, XmlResourceParser parser,
String[] outError) throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary);
// Note: don't allow this value to be a reference to a resource that may change.
String lname = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
final int version = sa.getInt(
com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary_version, -1);
String certSha256Digest = sa.getNonResourceString(com.android.internal.R.styleable
.AndroidManifestUsesStaticLibrary_certDigest);
sa.recycle();
// Since an APK providing a static shared lib can only provide the lib - fail if malformed
if (lname == null || version < 0 || certSha256Digest == null) {
outError[0] = "Bad uses-static-library declaration name: " + lname + " version: "
+ version + " certDigest" + certSha256Digest;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
XmlUtils.skipCurrentTag(parser);
return false;
}
// Can depend only on one version of the same library
if (pkg.usesStaticLibraries != null && pkg.usesStaticLibraries.contains(lname)) {
outError[0] = "Depending on multiple versions of static library " + lname;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
XmlUtils.skipCurrentTag(parser);
return false;
}
lname = lname.intern();
// We allow ":" delimiters in the SHA declaration as this is the format
// emitted by the certtool making it easy for developers to copy/paste.
certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
// Fot apps targeting O-MR1 we require explicit enumeration of all certs.
String[] additionalCertSha256Digests = EmptyArray.STRING;
if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O_MR1) {
additionalCertSha256Digests = parseAdditionalCertificates(res, parser, outError);
if (additionalCertSha256Digests == null) {
return false;
}
} else {
XmlUtils.skipCurrentTag(parser);
}
final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1];
certSha256Digests[0] = certSha256Digest;
System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
1, additionalCertSha256Digests.length);
pkg.usesStaticLibraries = ArrayUtils.add(pkg.usesStaticLibraries, lname);
pkg.usesStaticLibrariesVersions = ArrayUtils.appendLong(
pkg.usesStaticLibrariesVersions, version, true);
pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
pkg.usesStaticLibrariesCertDigests, certSha256Digests, true);
return true;
}
private String[] parseAdditionalCertificates(Resources resources, XmlResourceParser parser,
String[] outError) throws XmlPullParserException, IOException {
String[] certSha256Digests = EmptyArray.STRING;
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
final String nodeName = parser.getName();
if (nodeName.equals("additional-certificate")) {
final TypedArray sa = resources.obtainAttributes(parser, com.android.internal.
R.styleable.AndroidManifestAdditionalCertificate);
String certSha256Digest = sa.getNonResourceString(com.android.internal.
R.styleable.AndroidManifestAdditionalCertificate_certDigest);
sa.recycle();
if (TextUtils.isEmpty(certSha256Digest)) {
outError[0] = "Bad additional-certificate declaration with empty"
+ " certDigest:" + certSha256Digest;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
XmlUtils.skipCurrentTag(parser);
sa.recycle();
return null;
}
// We allow ":" delimiters in the SHA declaration as this is the format
// emitted by the certtool making it easy for developers to copy/paste.
certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
certSha256Digests = ArrayUtils.appendElement(String.class,
certSha256Digests, certSha256Digest);
} else {
XmlUtils.skipCurrentTag(parser);
}
}
return certSha256Digests;
}
private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestUsesPermission);
// Note: don't allow this value to be a reference to a resource
// that may change.
String name = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestUsesPermission_name);
int maxSdkVersion = 0;
TypedValue val = sa.peekValue(
com.android.internal.R.styleable.AndroidManifestUsesPermission_maxSdkVersion);
if (val != null) {
if (val.type >= TypedValue.TYPE_FIRST_INT && val.type <= TypedValue.TYPE_LAST_INT) {
maxSdkVersion = val.data;
}
}
final String requiredFeature = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredFeature, 0);
final String requiredNotfeature = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredNotFeature, 0);
sa.recycle();
XmlUtils.skipCurrentTag(parser);
if (name == null) {
return true;
}
if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) {
return true;
}
// Only allow requesting this permission if the platform supports the given feature.
if (requiredFeature != null && mCallback != null && !mCallback.hasFeature(requiredFeature)) {
return true;
}
// Only allow requesting this permission if the platform doesn't support the given feature.
if (requiredNotfeature != null && mCallback != null
&& mCallback.hasFeature(requiredNotfeature)) {
return true;
}
int index = pkg.requestedPermissions.indexOf(name);
if (index == -1) {
pkg.requestedPermissions.add(name.intern());
} else {
Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
+ name + " in package: " + pkg.packageName + " at: "
+ parser.getPositionDescription());
}
return true;
}
public static String buildClassName(String pkg, CharSequence clsSeq,
String[] outError) {
if (clsSeq == null || clsSeq.length() <= 0) {
outError[0] = "Empty class name in package " + pkg;
return null;
}
String cls = clsSeq.toString();
char c = cls.charAt(0);
if (c == '.') {
return pkg + cls;
}
if (cls.indexOf('.') < 0) {
StringBuilder b = new StringBuilder(pkg);
b.append('.');
b.append(cls);
return b.toString();
}
return cls;
}
private static String buildCompoundName(String pkg,
CharSequence procSeq, String type, String[] outError) {
String proc = procSeq.toString();
char c = proc.charAt(0);
if (pkg != null && c == ':') {
if (proc.length() < 2) {
outError[0] = "Bad " + type + " name " + proc + " in package " + pkg
+ ": must be at least two characters";
return null;
}
String subName = proc.substring(1);
String nameError = validateName(subName, false, false);
if (nameError != null) {
outError[0] = "Invalid " + type + " name " + proc + " in package "
+ pkg + ": " + nameError;
return null;
}
return pkg + proc;
}
String nameError = validateName(proc, true, false);
if (nameError != null && !"system".equals(proc)) {
outError[0] = "Invalid " + type + " name " + proc + " in package "
+ pkg + ": " + nameError;
return null;
}
return proc;
}
public static String buildProcessName(String pkg, String defProc,
CharSequence procSeq, int flags, String[] separateProcesses,
String[] outError) {
if ((flags&PARSE_IGNORE_PROCESSES) != 0 && !"system".equals(procSeq)) {
return defProc != null ? defProc : pkg;
}
if (separateProcesses != null) {
for (int i=separateProcesses.length-1; i>=0; i--) {
String sp = separateProcesses[i];
if (sp.equals(pkg) || sp.equals(defProc) || sp.equals(procSeq)) {
return pkg;
}
}
}
if (procSeq == null || procSeq.length() <= 0) {
return defProc;
}
return TextUtils.safeIntern(buildCompoundName(pkg, procSeq, "process", outError));
}
public static String buildTaskAffinityName(String pkg, String defProc,
CharSequence procSeq, String[] outError) {
if (procSeq == null) {
return defProc;
}
if (procSeq.length() <= 0) {
return null;
}
return buildCompoundName(pkg, procSeq, "taskAffinity", outError);
}
private boolean parseKeySets(Package owner, Resources res,
XmlResourceParser parser, String[] outError)
throws XmlPullParserException, IOException {
// we've encountered the 'key-sets' tag
// all the keys and keysets that we want must be defined here
// so we're going to iterate over the parser and pull out the things we want
int outerDepth = parser.getDepth();
int currentKeySetDepth = -1;
int type;
String currentKeySet = null;
ArrayMap