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: *

*/ public class PackageInstaller { diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 7bfe55d01ed25..d41cca601ffc4 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -82,9 +82,22 @@ import java.util.jar.StrictJarFile; import java.util.zip.ZipEntry; /** - * Package archive parsing + * 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: + *

* - * {@hide} + * @hide */ public class PackageParser { private static final boolean DEBUG_JAR = false; @@ -92,6 +105,7 @@ public class PackageParser { private static final boolean DEBUG_BACKUP = false; // TODO: switch outError users to PackageParserException + // TODO: refactor "codePath" to "apkPath" /** File name in an APK for the Android manifest. */ private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; @@ -247,6 +261,7 @@ public class PackageParser { /** Paths of any split APKs, ordered by parsed splitName */ public final String[] splitCodePaths; + public final boolean coreApp; public final boolean multiArch; private PackageLite(String codePath, ApkLite baseApk, String[] splitNames, @@ -259,6 +274,7 @@ public class PackageParser { this.codePath = codePath; this.baseCodePath = baseApk.codePath; this.splitCodePaths = splitCodePaths; + this.coreApp = baseApk.coreApp; this.multiArch = baseApk.multiArch; } @@ -283,11 +299,12 @@ public class PackageParser { public final int installLocation; public final VerifierInfo[] verifiers; public final Signature[] signatures; + public final boolean coreApp; public final boolean multiArch; public ApkLite(String codePath, String packageName, String splitName, int versionCode, int installLocation, List verifiers, Signature[] signatures, - boolean multiArch) { + boolean coreApp, boolean multiArch) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; @@ -295,6 +312,7 @@ public class PackageParser { this.installLocation = installLocation; this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]); this.signatures = signatures; + this.coreApp = coreApp; this.multiArch = multiArch; } } @@ -322,6 +340,11 @@ public class PackageParser { mSeparateProcesses = procs; } + /** + * Flag indicating this parser should only consider apps with + * {@code coreApp} manifest attribute to be valid apps. This is useful when + * creating a minimalist boot environment. + */ public void setOnlyCoreApps(boolean onlyCoreApps) { mOnlyCoreApps = onlyCoreApps; } @@ -401,7 +424,7 @@ public class PackageParser { pi.gids = gids; } if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) { - int N = p.configPreferences.size(); + int N = p.configPreferences != null ? p.configPreferences.size() : 0; if (N > 0) { pi.configPreferences = new ConfigurationInfo[N]; p.configPreferences.toArray(pi.configPreferences); @@ -591,6 +614,17 @@ public class PackageParser { } } + /** + * Parse only lightweight details about 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. + * + * @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(); - } - pkg.usesOptionalLibraries.add("com.android.webview"); - - return pkg; } - private void parseSplitApk(Package pkg, File apkFile, int flags) throws PackageParserException { - final String splitCodePath = apkFile.getAbsolutePath(); - mArchiveSourcePath = apkFile.getAbsolutePath(); + private void parseSplitApk(Package pkg, int splitIndex, int flags) + throws PackageParserException { + final String apkPath = pkg.splitCodePaths[splitIndex]; + final File apkFile = new File(apkPath); - // TODO: expand split APK parsing + mParseError = PackageManager.INSTALL_SUCCEEDED; + mArchiveSourcePath = apkPath; + + 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 split APK: " + apkPath); + + AssetManager assets = null; + Resources res = null; + XmlResourceParser parser = null; + try { + assets = new AssetManager(); + int cookie = assets.addAssetPath(apkPath); + if (cookie == 0) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Failed adding asset path: " + apkPath); + } + + 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]; + 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, + "Unable to read AndroidManifest.xml of " + apkPath); + } finally { + IoUtils.closeQuietly(parser); + IoUtils.closeQuietly(assets); + } + } + + /** + * 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 { + 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] = " has more than one "; + mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; + return null; + } else { + Slog.w(TAG, " has more than one "); + XmlUtils.skipCurrentTag(parser); + continue; + } + } + + foundApp = true; + if (!parseSplitApplication(pkg, res, parser, attrs, flags, splitIndex, outError)) { + return null; + } + + } else if (RIGID_PARSER) { + outError[0] = "Bad element under : " + + parser.getName(); + mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; + return null; + + } else { + Slog.w(TAG, "Unknown element under : " + parser.getName() + + " at " + mArchiveSourcePath + " " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + continue; + } + } + + if (!foundApp) { + outError[0] = " does not contain an "; + mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY; + } + + return pkg; } /** @@ -1001,14 +1131,14 @@ public class PackageParser { throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); - AssetManager assmgr = null; + AssetManager assets = null; XmlResourceParser parser = null; try { - assmgr = new AssetManager(); - assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); - int cookie = assmgr.addAssetPath(apkPath); + int cookie = assets.addAssetPath(apkPath); if (cookie == 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Failed to parse " + apkPath); @@ -1017,8 +1147,8 @@ public class PackageParser { final DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); - final Resources res = new Resources(assmgr, metrics, null); - parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + final Resources res = new Resources(assets, metrics, null); + parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); // Only collect certificates on the manifest; does not validate // signatures across remainder of package. @@ -1036,8 +1166,8 @@ public class PackageParser { throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed to parse " + apkPath, e); } finally { - if (parser != null) parser.close(); - if (assmgr != null) assmgr.close(); + IoUtils.closeQuietly(parser); + IoUtils.closeQuietly(assets); } } @@ -1118,8 +1248,10 @@ public class PackageParser { int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; int versionCode = 0; - int numFound = 0; + boolean coreApp = false; boolean multiArch = false; + + int numFound = 0; for (int i = 0; i < attrs.getAttributeCount(); i++) { String attr = attrs.getAttributeName(i); if (attr.equals("installLocation")) { @@ -1129,8 +1261,11 @@ public class PackageParser { } else if (attr.equals("versionCode")) { versionCode = attrs.getAttributeIntValue(i, 0); numFound++; + } else if (attr.equals("coreApp")) { + coreApp = attrs.getAttributeBooleanValue(i, false); + numFound++; } - if (numFound >= 2) { + if (numFound >= 3) { break; } } @@ -1165,7 +1300,7 @@ public class PackageParser { } return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode, - installLocation, verifiers, signatures, multiArch); + installLocation, verifiers, signatures, coreApp, multiArch); } /** @@ -1180,8 +1315,16 @@ public class PackageParser { return new Signature(sig); } + /** + * Parse the manifest of a base APK. + *

+ * 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(); - } - pkg.reqFeatures.add(fi); - + pkg.reqFeatures = ArrayUtils.add(pkg.reqFeatures, fi); + if (fi.name == null) { ConfigurationInfo cPref = new ConfigurationInfo(); cPref.reqGlEsVersion = fi.reqGlEsVersion; - pkg.configPreferences.add(cPref); + pkg.configPreferences = ArrayUtils.add(pkg.configPreferences, cPref); } XmlUtils.skipCurrentTag(parser); @@ -2204,7 +2336,14 @@ public class PackageParser { return a; } - private boolean parseApplication(Package owner, Resources res, + /** + * Parse the {@code application} XML tree at the current parse location in a + * base APK manifest. + *

+ * 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(); - } - if (!owner.libraryNames.contains(lname)) { - owner.libraryNames.add(lname.intern()); + lname = lname.intern(); + if (!ArrayUtils.contains(owner.libraryNames, lname)) { + owner.libraryNames = ArrayUtils.add(owner.libraryNames, lname); } } @@ -2540,19 +2677,150 @@ public class PackageParser { sa.recycle(); if (lname != null) { + lname = lname.intern(); if (req) { - if (owner.usesLibraries == null) { - owner.usesLibraries = new ArrayList(); - } - if (!owner.usesLibraries.contains(lname)) { - owner.usesLibraries.add(lname.intern()); - } + owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, lname); } else { - if (owner.usesOptionalLibraries == null) { - owner.usesOptionalLibraries = new ArrayList(); - } - if (!owner.usesOptionalLibraries.contains(lname)) { - owner.usesOptionalLibraries.add(lname.intern()); + 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 : " + tagName + + " at " + mArchiveSourcePath + " " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + continue; + } else { + outError[0] = "Bad element under : " + tagName; + mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; + return false; + } + } + } + + return true; + } + + /** + * Parse the {@code application} XML tree at the current parse location in a + * split APK manifest. + *

+ * 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 configPreferences = - new ArrayList(); + // Applications hardware preferences + public ArrayList configPreferences = null; - /* - * Applications requested features - */ + // Applications requested features public ArrayList reqFeatures = null; public int installLocation; @@ -3998,6 +4266,25 @@ public class PackageParser { return paths; } + /** + * Filtered set of {@link #getAllCodePaths()} that excludes + * resource-only APKs. + */ + public List getAllCodePathsExcludingResourceOnly() { + ArrayList paths = new ArrayList<>(); + if ((applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) { + paths.add(baseCodePath); + } + if (!ArrayUtils.isEmpty(splitCodePaths)) { + for (int i = 0; i < splitCodePaths.length; i++) { + if ((splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) { + paths.add(splitCodePaths[i]); + } + } + } + return paths; + } + public void setPackageName(String newName) { packageName = newName; applicationInfo.packageName = newName; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 0c0440135fa42..3a30123e8d022 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -33,7 +33,7 @@ import java.util.HashMap; * files that have been bundled with the application as a simple stream of * bytes. */ -public final class AssetManager { +public final class AssetManager implements AutoCloseable { /* modes used when opening an asset */ /** diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 7f6159dec44b6..3ed4d518e1e8d 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -16,10 +16,14 @@ package com.android.internal.util; +import android.util.ArraySet; + import dalvik.system.VMRuntime; + import libcore.util.EmptyArray; import java.lang.reflect.Array; +import java.util.ArrayList; /** * ArrayUtils contains some methods that you can call to find out @@ -332,4 +336,52 @@ public class ArrayUtils public static long[] cloneOrNull(long[] array) { return (array != null) ? array.clone() : null; } + + public static ArraySet add(ArraySet cur, T val) { + if (cur == null) { + cur = new ArraySet<>(); + } + cur.add(val); + return cur; + } + + public static ArraySet remove(ArraySet cur, T val) { + if (cur == null) { + return null; + } + cur.remove(val); + if (cur.isEmpty()) { + return null; + } else { + return cur; + } + } + + public static boolean contains(ArraySet cur, T val) { + return (cur != null) ? cur.contains(val) : false; + } + + public static ArrayList add(ArrayList cur, T val) { + if (cur == null) { + cur = new ArrayList<>(); + } + cur.add(val); + return cur; + } + + public static ArrayList remove(ArrayList cur, T val) { + if (cur == null) { + return null; + } + cur.remove(val); + if (cur.isEmpty()) { + return null; + } else { + return cur; + } + } + + public static boolean contains(ArrayList cur, T val) { + return (cur != null) ? cur.contains(val) : false; + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 74a194546f7da..e17dacbe252e5 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4636,7 +4636,7 @@ public class PackageManagerService extends IPackageManager.Stub { return DEX_OPT_SKIPPED; } - final Collection paths = pkg.getAllCodePaths(); + final List paths = pkg.getAllCodePathsExcludingResourceOnly(); boolean performedDexOpt = false; // There are three basic cases here: // 1.) we need to dexopt, either because we are forced or it is needed @@ -4648,7 +4648,8 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean isDexOptNeeded = DexFile.isDexOptNeededInternal(path, pkg.packageName, instructionSet, defer); if (forceDex || (!defer && isDexOptNeeded)) { - Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName + " isa=" + instructionSet); + Log.i(TAG, "Running dexopt on: " + path + " pkg=" + + pkg.applicationInfo.packageName + " isa=" + instructionSet); final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); final int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg), pkg.packageName, instructionSet);