/* * Copyright (C) 2007 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_UNRESIZEABLE; 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.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_CERTIFICATE_ENCODING; 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_NO_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; 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_ROTATE; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.split.SplitAssetDependencyLoader; import android.content.pm.split.SplitAssetLoader; import android.content.pm.split.DefaultSplitAssetLoader; 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.FileUtils; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.OsConstants; import android.system.StructStat; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; import android.util.apk.ApkSignatureSchemeV2Verifier; import android.util.jar.StrictJarFile; import android.view.Gravity; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; 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.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; /** * 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 sanity 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)
*/
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, 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");
}
String packageName = null;
int versionCode = 0;
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
final ArrayMap
* This performs sanity 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, int)}.
*
* 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)
*/
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
if (parsed != null) {
return parsed;
}
if (packageFile.isDirectory()) {
parsed = parseClusterPackage(packageFile, flags);
} else {
parsed = parseMonolithicPackage(packageFile, flags);
}
cacheResult(packageFile, flags, parsed);
return parsed;
}
/**
* Equivalent to {@link #parsePackage(File, int, boolean)} with {@code useCaches == false}.
*/
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
return parsePackage(packageFile, flags, false /* useCaches */);
}
/**
* Returns the cache key for a specificied {@code packageFile} and {@code flags}.
*/
private String getCacheKey(File packageFile, int flags) {
StringBuilder sb = new StringBuilder(packageFile.getName());
sb.append('-');
sb.append(flags);
return sb.toString();
}
@VisibleForTesting
protected Package fromCacheEntry(byte[] bytes) throws IOException {
Parcel p = Parcel.obtain();
p.unmarshall(bytes, 0, bytes.length);
p.setDataPosition(0);
PackageParser.Package pkg = new PackageParser.Package(p);
p.recycle();
return pkg;
}
@VisibleForTesting
protected byte[] toCacheEntry(Package pkg) throws IOException {
Parcel p = Parcel.obtain();
pkg.writeToParcel(p, 0 /* flags */);
byte[] serialized = p.marshall();
p.recycle();
return serialized;
}
/**
* Given a {@code packageFile} and a {@code cacheFile} returns whether the
* cache file is up to date based on the mod-time of both files.
*/
private static boolean isCacheUpToDate(File packageFile, File cacheFile) {
try {
// NOTE: We don't use the File.lastModified API because it has the very
// non-ideal failure mode of returning 0 with no excepions thrown.
// The nio2 Files API is a little better but is considerably more expensive.
final StructStat pkg = android.system.Os.stat(packageFile.getAbsolutePath());
final StructStat cache = android.system.Os.stat(cacheFile.getAbsolutePath());
return pkg.st_mtime < cache.st_mtime;
} catch (ErrnoException ee) {
// The most common reason why stat fails is that a given cache file doesn't
// exist. We ignore that here. It's easy to reason that it's safe to say the
// cache isn't up to date if we see any sort of exception here.
//
// (1) Exception while stating the package file : This should never happen,
// and if it does, we do a full package parse (which is likely to throw the
// same exception).
// (2) Exception while stating the cache file : If the file doesn't exist, the
// cache is obviously out of date. If the file *does* exist, we can't read it.
// We will attempt to delete and recreate it after parsing the package.
if (ee.errno != OsConstants.ENOENT) {
Slog.w("Error while stating package cache : ", ee);
}
return false;
}
}
/**
* Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
* or {@code null} if no cached result exists.
*/
private Package getCachedResult(File packageFile, int flags) {
if (mCacheDir == null) {
return null;
}
final String cacheKey = getCacheKey(packageFile, flags);
final File cacheFile = new File(mCacheDir, cacheKey);
// If the cache is not up to date, return null.
if (!isCacheUpToDate(packageFile, cacheFile)) {
return null;
}
try {
final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());
Package p = fromCacheEntry(bytes);
if (mCallback != null) {
String[] overlayApks = mCallback.getOverlayApks(p.packageName);
if (overlayApks != null && overlayApks.length > 0) {
for (String overlayApk : overlayApks) {
// If a static RRO is updated, return null.
if (!isCacheUpToDate(new File(overlayApk), cacheFile)) {
return null;
}
}
}
}
return p;
} catch (Exception e) {
Slog.w(TAG, "Error reading package cache: ", e);
// If something went wrong while reading the cache entry, delete the cache file
// so that we regenerate it the next time.
cacheFile.delete();
return null;
}
}
/**
* Caches the parse result for {@code packageFile} with flags {@code flags}.
*/
private void cacheResult(File packageFile, int flags, Package parsed) {
if (mCacheDir == null) {
return;
}
final String cacheKey = getCacheKey(packageFile, flags);
final File cacheFile = new File(mCacheDir, cacheKey);
if (cacheFile.exists()) {
if (!cacheFile.delete()) {
Slog.e(TAG, "Unable to delete cache file: " + cacheFile);
}
}
final byte[] cacheEntry;
try {
cacheEntry = toCacheEntry(parsed);
} catch (IOException ioe) {
Slog.e(TAG, "Unable to serialize parsed package for: " + packageFile);
return;
}
if (cacheEntry == null) {
return;
}
try (FileOutputStream fos = new FileOutputStream(cacheFile)) {
fos.write(cacheEntry);
} catch (IOException ioe) {
Slog.w(TAG, "Error writing cache entry.", ioe);
cacheFile.delete();
}
}
/**
* Parse all APKs contained in the given directory, treating them as a
* single package. This also performs sanity 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, int)}.
*/
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, int)}.
*
* @deprecated external callers should move to
* {@link #parsePackage(File, int)}. Eventually this method will
* be marked private.
*/
@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
final AssetManager assets = newConfiguredAssetManager();
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (mOnlyCoreApps) {
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
}
}
try {
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.setCodePath(apkFile.getAbsolutePath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}
private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
throws PackageParserException {
if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Invalid package file: " + apkPath);
}
// The AssetManager guarantees uniqueness for asset paths, so if this asset path
// already exists in the AssetManager, addAssetPath will only return the cookie
// assigned to it.
int cookie = assets.addAssetPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
return cookie;
}
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);
final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
Resources res = null;
XmlResourceParser parser = null;
try {
res = new Resources(assets, mMetrics, null);
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
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.setSignatures(null);
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 int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
final Resources res;
XmlResourceParser parser = null;
try {
res = new Resources(assets, mMetrics, null);
assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
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;
mParseActivityArgs = null;
mParseServiceArgs = null;
mParseProviderArgs = 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] = "
* This is useful when we've already processed the certificates [such as during package
* installation through an installer session]. We don't re-process the archive and
* simply populate the correct fields.
*/
public static void populateCertificates(Package pkg, Certificate[][] certificates)
throws PackageParserException {
pkg.mCertificates = null;
pkg.mSignatures = null;
pkg.mSigningKeys = null;
pkg.mCertificates = certificates;
try {
pkg.mSignatures = convertToSignatures(certificates);
} catch (CertificateEncodingException e) {
// certificates weren't encoded properly; something went wrong
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Failed to collect certificates from " + pkg.baseCodePath, e);
}
pkg.mSigningKeys = new ArraySet<>(certificates.length);
for (int i = 0; i < certificates.length; i++) {
Certificate[] signerCerts = certificates[i];
Certificate signerCert = signerCerts[0];
pkg.mSigningKeys.add(signerCert.getPublicKey());
}
// add signatures to child packages
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
Package childPkg = pkg.childPackages.get(i);
childPkg.mCertificates = pkg.mCertificates;
childPkg.mSignatures = pkg.mSignatures;
childPkg.mSigningKeys = pkg.mSigningKeys;
}
}
/**
* Collect certificates from all the APKs described in the given package,
* populating {@link Package#mSignatures}. Also asserts that all APK
* contents are signed correctly and consistently.
*/
public static void collectCertificates(Package pkg, int parseFlags)
throws PackageParserException {
collectCertificatesInternal(pkg, parseFlags);
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
Package childPkg = pkg.childPackages.get(i);
childPkg.mCertificates = pkg.mCertificates;
childPkg.mSignatures = pkg.mSignatures;
childPkg.mSigningKeys = pkg.mSigningKeys;
}
}
private static void collectCertificatesInternal(Package pkg, int parseFlags)
throws PackageParserException {
pkg.mCertificates = null;
pkg.mSignatures = null;
pkg.mSigningKeys = null;
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
try {
collectCertificates(pkg, new File(pkg.baseCodePath), parseFlags);
if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
for (int i = 0; i < pkg.splitCodePaths.length; i++) {
collectCertificates(pkg, new File(pkg.splitCodePaths[i]), parseFlags);
}
}
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private static void collectCertificates(Package pkg, File apkFile, int parseFlags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
// Try to verify the APK using APK Signature Scheme v2.
boolean verified = false;
{
Certificate[][] allSignersCerts = null;
Signature[] signatures = null;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
allSignersCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
signatures = convertToSignatures(allSignersCerts);
// APK verified using APK Signature Scheme v2.
verified = true;
} catch (ApkSignatureSchemeV2Verifier.SignatureNotFoundException e) {
// No APK Signature Scheme v2 signature found
if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v2 signature in ephemeral package " + apkPath,
e);
}
// Static shared libraries must use only the V2 signing scheme
if (pkg.applicationInfo.isStaticSharedLibrary()) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Static shared libs must use v2 signature scheme " + apkPath);
}
} catch (Exception e) {
// APK Signature Scheme v2 signature was found but did not verify
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Failed to collect certificates from " + apkPath
+ " using APK Signature Scheme v2",
e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
if (verified) {
if (pkg.mCertificates == null) {
pkg.mCertificates = allSignersCerts;
pkg.mSignatures = signatures;
pkg.mSigningKeys = new ArraySet<>(allSignersCerts.length);
for (int i = 0; i < allSignersCerts.length; i++) {
Certificate[] signerCerts = allSignersCerts[i];
Certificate signerCert = signerCerts[0];
pkg.mSigningKeys.add(signerCert.getPublicKey());
}
} else {
if (!Signature.areExactMatch(pkg.mSignatures, signatures)) {
throw new PackageParserException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
apkPath + " has mismatched certificates");
}
}
// Not yet done, because we need to confirm that AndroidManifest.xml exists and,
// if requested, that classes.dex exists.
}
}
StrictJarFile jarFile = null;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
// Ignore signature stripping protections when verifying APKs from system partition.
// For those APKs we only care about extracting signer certificates, and don't care
// about verifying integrity.
boolean signatureSchemeRollbackProtectionsEnforced =
(parseFlags & PARSE_IS_SYSTEM_DIR) == 0;
jarFile = new StrictJarFile(
apkPath,
!verified, // whether to verify JAR signature
signatureSchemeRollbackProtectionsEnforced);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
// Always verify manifest, regardless of source
final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
if (manifestEntry == null) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Package " + apkPath + " has no manifest");
}
// Optimization: early termination when APK already verified
if (verified) {
return;
}
// APK's integrity needs to be verified using JAR signature scheme.
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV1");
final List
* 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.
*/
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);
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 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;
String tagName = parser.getName();
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, flags, outError, false,
owner.baseHardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.activities.add(a);
parsedComponent = a.info;
} else if (tagName.equals("receiver")) {
Activity a = parseActivity(owner, res, parser, flags, outError, true, false);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.receivers.add(a);
parsedComponent = a.info;
} else if (tagName.equals("service")) {
Service s = parseService(owner, res, parser, flags, outError);
if (s == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.services.add(s);
parsedComponent = s.info;
} else if (tagName.equals("provider")) {
Provider p = parseProvider(owner, res, parser, flags, outError);
if (p == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.providers.add(p);
parsedComponent = p.info;
} else if (tagName.equals("activity-alias")) {
Activity a = parseActivityAlias(owner, res, parser, flags, outError);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.activities.add(a);
parsedComponent = a.info;
} else if (parser.getName().equals("meta-data")) {
// note: application meta-data is stored off to the side, so it can
// remain null in the primary copy (we like to avoid extra copies because
// it can be large)
if ((owner.mAppMetaData = parseMetaData(res, parser, owner.mAppMetaData,
outError)) == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
} else if (tagName.equals("uses-static-library")) {
if (!parseUsesStaticLibrary(owner, res, parser, outError)) {
return false;
}
} else if (tagName.equals("uses-library")) {
sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestUsesLibrary);
// 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);
boolean req = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestUsesLibrary_required,
true);
sa.recycle();
if (lname != null) {
lname = lname.intern();
if (req) {
// Upgrade to treat as stronger constraint
owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, lname);
owner.usesOptionalLibraries = ArrayUtils.remove(
owner.usesOptionalLibraries, lname);
} else {
// Ignore if someone already defined as required
if (!ArrayUtils.contains(owner.usesLibraries, lname)) {
owner.usesOptionalLibraries = ArrayUtils.add(
owner.usesOptionalLibraries, lname);
}
}
}
XmlUtils.skipCurrentTag(parser);
} else if (tagName.equals("uses-package")) {
// Dependencies for app installers; we don't currently try to
// enforce this.
XmlUtils.skipCurrentTag(parser);
} else {
if (!RIGID_PARSER) {
Slog.w(TAG, "Unknown element under
* 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
*
*
* @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 platformSdkVersion platform SDK version number, typically
* Build.VERSION.SDK_INT
* @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.
*/
@TestApi
public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
@Nullable String targetCode, @IntRange(from = 1) int platformSdkVersion,
@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 (ArrayUtils.contains(platformSdkCodenames, targetCode)) {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
// 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.
*/
@TestApi
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 (ArrayUtils.contains(platformSdkCodenames, minCode)) {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
// 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 certSha256 = 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 || certSha256 == null) {
outError[0] = "Bad uses-static-library declaration name: " + lname + " version: "
+ version + " certDigest" + certSha256;
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.
certSha256 = certSha256.replace(":", "").toLowerCase();
pkg.usesStaticLibraries = ArrayUtils.add(pkg.usesStaticLibraries, lname);
pkg.usesStaticLibrariesVersions = ArrayUtils.appendInt(
pkg.usesStaticLibrariesVersions, version, true);
pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String.class,
pkg.usesStaticLibrariesCertDigests, certSha256, true);
XmlUtils.skipCurrentTag(parser);
return true;
}
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;
}
private 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;
}
private 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 buildCompoundName(pkg, procSeq, "process", outError);
}
private 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