diff --git a/api/current.txt b/api/current.txt index 2e25e33f6d895..5eea910d6b91f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8911,7 +8911,7 @@ package android.content.res { ctor public AssetFileDescriptor.AutoCloseOutputStream(android.content.res.AssetFileDescriptor) throws java.io.IOException; } - public final class AssetManager { + public final class AssetManager implements java.lang.AutoCloseable { method public void close(); method public final java.lang.String[] getLocales(); method public final java.lang.String[] list(java.lang.String) throws java.io.IOException; diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 8f0c2497fe5a9..49ffef23afcfa 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -185,7 +185,9 @@ public class PackageInfo implements Parcelable { public ConfigurationInfo[] configPreferences; /** - * The features that this application has said it requires. + * Features that this application has requested. + * + * @see FeatureInfo#FLAG_REQUIRED */ public FeatureInfo[] reqFeatures; diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 348a7adb4c1c4..df82d264d1bba 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -42,17 +42,17 @@ import java.util.List; * intervention to complete the installation. *
* Sessions can install brand new apps, upgrade existing apps, or add new splits - * onto an existing app. + * into an existing app. *
- * Apps packaged into multiple split APKs always consist of a single "base" APK + * Apps packaged as multiple split 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 these APKs can be installed together, as long as * the following constraints are met: *
+ * 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()) { @@ -677,6 +711,20 @@ public class PackageParser { return new PackageLite(codePath, baseApk, splitNames, splitCodePaths); } + /** + * Parse the package at the given location. Automatically detects if the + * package is a monolithic style (single APK file) or cluster style + * (directory of APKs). + *
+ * 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)}.
+ *
+ * @see #parsePackageLite(File, int)
+ */
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
@@ -697,6 +745,11 @@ public class PackageParser {
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);
+ }
+
final File baseApk = new File(lite.baseCodePath);
final Package pkg = parseBaseApk(baseApk, flags);
if (pkg == null) {
@@ -705,12 +758,13 @@ public class PackageParser {
}
if (!ArrayUtils.isEmpty(lite.splitNames)) {
+ final int num = lite.splitNames.length;
pkg.splitNames = lite.splitNames;
pkg.splitCodePaths = lite.splitCodePaths;
+ pkg.splitFlags = new int[num];
- for (String splitCodePath : lite.splitCodePaths) {
- final File splitApk = new File(splitCodePath);
- parseSplitApk(pkg, splitApk, flags);
+ for (int i = 0; i < num; i++) {
+ parseSplitApk(pkg, i, flags);
}
}
@@ -730,115 +784,191 @@ public class PackageParser {
*/
@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
- final Package pkg = parseBaseApk(apkFile, flags);
- if (pkg == null) {
- throw new PackageParserException(mParseError, "Failed to parse " + apkFile);
+ if (mOnlyCoreApps) {
+ final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
+ if (!lite.coreApp) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Not a coreApp: " + apkFile);
+ }
}
+ final Package pkg = parseBaseApk(apkFile, flags);
pkg.codePath = apkFile.getAbsolutePath();
return pkg;
}
- private Package parseBaseApk(File apkFile, int flags) {
- final boolean trustedOverlay = (flags & PARSE_TRUSTED_OVERLAY) != 0;
+ private Package parseBaseApk(File apkFile, int flags) throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
mParseError = PackageManager.INSTALL_SUCCEEDED;
-
- final String apkPath = apkFile.getAbsolutePath();
mArchiveSourcePath = apkFile.getAbsolutePath();
- if (!apkFile.isFile()) {
- Slog.w(TAG, "Skipping dir: " + apkPath);
- mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
- return null;
- }
- if (!isApkFile(apkFile) && (flags & PARSE_MUST_BE_APK) != 0) {
- if ((flags&PARSE_IS_SYSTEM) == 0) {
- // We expect to have non-.apk files in the system dir,
- // so don't warn about them.
- Slog.w(TAG, "Skipping non-package file: " + apkPath);
- }
- mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
- return null;
+
+ if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkFile(apkFile)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Invalid package file: " + apkPath);
}
- if (DEBUG_JAR)
- Slog.d(TAG, "Scanning package: " + apkPath);
+ if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
- XmlResourceParser parser = null;
- AssetManager assmgr = null;
+ AssetManager assets = null;
Resources res = null;
- boolean assetError = true;
+ XmlResourceParser parser = null;
try {
- assmgr = new AssetManager();
- int cookie = assmgr.addAssetPath(apkPath);
- if (cookie != 0) {
- res = new Resources(assmgr, mMetrics, null);
- assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
- parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
- assetError = false;
- } else {
- Slog.w(TAG, "Failed adding asset path:" + apkPath);
+ assets = new AssetManager();
+ int cookie = assets.addAssetPath(apkPath);
+ if (cookie == 0) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Failed adding asset path: " + apkPath);
}
- } catch (Exception e) {
- Slog.w(TAG, "Unable to read AndroidManifest.xml of " + apkPath, e);
- }
- if (assetError) {
- if (assmgr != null) assmgr.close();
- mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
- return null;
- }
- String[] errorText = new String[1];
- Package pkg = null;
- Exception errorException = null;
- try {
- // XXXX todo: need to figure out correct configuration.
- pkg = parseBaseApk(res, parser, flags, trustedOverlay, errorText);
- } catch (Exception e) {
- errorException = e;
- mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
- }
- if (pkg == null) {
- // If we are only parsing core apps, then a null with INSTALL_SUCCEEDED
- // just means to skip this app so don't make a fuss about it.
- if (!mOnlyCoreApps || mParseError != PackageManager.INSTALL_SUCCEEDED) {
- if (errorException != null) {
- Slog.w(TAG, apkPath, errorException);
- } else {
- Slog.w(TAG, apkPath + " (at "
- + parser.getPositionDescription()
- + "): " + errorText[0]);
- }
- if (mParseError == PackageManager.INSTALL_SUCCEEDED) {
- mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- }
+ res = new Resources(assets, mMetrics, null);
+ assets.setConfiguration(0, 0, null, 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];
+ final Package pkg = parseBaseApk(res, parser, flags, outError);
+ if (pkg == null) {
+ throw new PackageParserException(mParseError,
+ apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
}
- parser.close();
- assmgr.close();
- return null;
+
+ pkg.baseCodePath = apkPath;
+ pkg.mSignatures = null;
+
+ // TODO: Remove this when the WebView can load resources dynamically. b/11505352
+ pkg.usesOptionalLibraries = ArrayUtils.add(pkg.usesOptionalLibraries,
+ "com.android.webview");
+
+ return pkg;
+
+ } catch (PackageParserException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Unable to read AndroidManifest.xml of " + apkPath);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ IoUtils.closeQuietly(assets);
}
-
- parser.close();
- assmgr.close();
-
- pkg.baseCodePath = apkPath;
- pkg.mSignatures = null;
-
- // TODO: Remove this when the WebView can load resources dynamically. b/11505352
- if (pkg.usesOptionalLibraries == null) {
- pkg.usesOptionalLibraries = new ArrayList
+ * 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 {
+ AttributeSet attrs = parser;
+
+ 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("application")) {
+ if (foundApp) {
+ if (RIGID_PARSER) {
+ outError[0] = "
+ * When adding new features, carefully consider if they should also be
+ * supported by split APKs.
+ */
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
- boolean trustedOverlay, String[] outError) throws XmlPullParserException, IOException {
+ String[] outError) throws XmlPullParserException, IOException {
+ final boolean trustedOverlay = (flags & PARSE_TRUSTED_OVERLAY) != 0;
+
AttributeSet attrs = parser;
mParseInstrumentationArgs = null;
@@ -1202,14 +1345,6 @@ public class PackageParser {
int type;
- if (mOnlyCoreApps) {
- boolean core = attrs.getAttributeBooleanValue(null, "coreApp", false);
- if (!core) {
- mParseError = PackageManager.INSTALL_SUCCEEDED;
- return null;
- }
- }
-
if (!TextUtils.isEmpty(splitName)) {
outError[0] = "Expected base APK, but found split " + splitName;
mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
@@ -1290,7 +1425,7 @@ public class PackageParser {
}
foundApp = true;
- if (!parseApplication(pkg, res, parser, attrs, flags, outError)) {
+ if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
return null;
}
} else if (tagName.equals("overlay")) {
@@ -1362,7 +1497,7 @@ public class PackageParser {
cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
}
sa.recycle();
- pkg.configPreferences.add(cPref);
+ pkg.configPreferences = ArrayUtils.add(pkg.configPreferences, cPref);
XmlUtils.skipCurrentTag(parser);
@@ -1385,15 +1520,12 @@ public class PackageParser {
fi.flags |= FeatureInfo.FLAG_REQUIRED;
}
sa.recycle();
- if (pkg.reqFeatures == null) {
- pkg.reqFeatures = new ArrayList
+ * When adding new features, carefully consider if they should also be
+ * supported by split APKs.
+ */
+ private boolean parseBaseApplication(Package owner, Resources res,
XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
throws XmlPullParserException, IOException {
final ApplicationInfo ai = owner.applicationInfo;
@@ -2324,7 +2463,7 @@ public class PackageParser {
ai.flags |= ApplicationInfo.FLAG_VM_SAFE_MODE;
}
- boolean hardwareAccelerated = sa.getBoolean(
+ owner.baseHardwareAccelerated = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);
@@ -2449,7 +2588,7 @@ public class PackageParser {
String tagName = parser.getName();
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
- hardwareAccelerated);
+ owner.baseHardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
@@ -2515,11 +2654,9 @@ public class PackageParser {
sa.recycle();
if (lname != null) {
- if (owner.libraryNames == null) {
- owner.libraryNames = new ArrayList
+ * 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, XmlPullParser parser,
+ AttributeSet attrs, int flags, int splitIndex, String[] outError)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(attrs,
+ 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;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("activity")) {
+ Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
+ owner.baseHardwareAccelerated);
+ if (a == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.activities.add(a);
+
+ } else if (tagName.equals("receiver")) {
+ Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
+ if (a == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.receivers.add(a);
+
+ } else if (tagName.equals("service")) {
+ Service s = parseService(owner, res, parser, attrs, flags, outError);
+ if (s == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.services.add(s);
+
+ } else if (tagName.equals("provider")) {
+ Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
+ if (p == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.providers.add(p);
+
+ } else if (tagName.equals("activity-alias")) {
+ Activity a = parseActivityAlias(owner, res, parser, attrs, flags, outError);
+ if (a == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ owner.activities.add(a);
+
+ } 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, attrs, owner.mAppMetaData,
+ outError)) == null) {
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+
+ } else if (tagName.equals("uses-library")) {
+ sa = res.obtainAttributes(attrs,
+ 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);
}
}
}
@@ -3873,6 +4141,11 @@ public class PackageParser {
/** Paths of any split APKs, ordered by parsed splitName */
public String[] splitCodePaths;
+ /** Flags of any split APKs; ordered by parsed splitName */
+ public int[] splitFlags;
+
+ public boolean baseHardwareAccelerated;
+
// For now we only support one application per package.
public final ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -3941,15 +4214,10 @@ public class PackageParser {
// Whether an operation is currently pending on this package
public boolean mOperationPending;
- /*
- * Applications hardware preferences
- */
- public final ArrayList