diff options
author | Jeff Sharkey <jsharkey@android.com> | 2014-11-22 16:49:34 -0800 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2014-11-24 12:13:11 -0800 |
commit | 88d2a3c0e1b4a8c53a489db5d627beb80b1b9957 (patch) | |
tree | 18761183dea3ba5e0c63283feb938934a3454294 | |
parent | 4dfce43e976a294b3b575564c855214a8e5cef58 (diff) | |
download | frameworks_base-88d2a3c0e1b4a8c53a489db5d627beb80b1b9957.zip frameworks_base-88d2a3c0e1b4a8c53a489db5d627beb80b1b9957.tar.gz frameworks_base-88d2a3c0e1b4a8c53a489db5d627beb80b1b9957.tar.bz2 |
Introduce revision codes for split APKs.
Apps delivered as multiple split APKs must have identical package
names, version code, and signatures. However, developers may want
to iterate quickly on a subset of splits without having to increment
the version code, which would require delivery of the entire app.
This change introduces "revision codes" which can vary between
split APKs belonging to the same app. An install is valid as long
as the normal version code is identical across all splits. Splits
can be added/removed to an app over time, but if a split is present
across an upgrade the revision code must not decrease.
Since system apps could have been updated with splits, only revert
to the built-in APKs if the version code is strictly greater than the
data version. Also fix bug to enable inheriting from system apps
when adding splits.
Bug: 18481866
Change-Id: I34d8e14c141a8eb95c33ffe24b4e52d6af5c8260
-rw-r--r-- | api/current.txt | 3 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageInfo.java | 31 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageInfoLite.java | 16 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageParser.java | 50 | ||||
-rw-r--r-- | core/java/android/net/VpnService.java | 6 | ||||
-rw-r--r-- | core/res/res/values/attrs_manifest.xml | 14 | ||||
-rw-r--r-- | core/res/res/values/public.xml | 1 | ||||
-rw-r--r-- | packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java | 3 | ||||
-rw-r--r-- | services/core/java/com/android/server/pm/PackageInstallerSession.java | 39 | ||||
-rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 48 |
10 files changed, 175 insertions, 36 deletions
diff --git a/api/current.txt b/api/current.txt index 490b309..7508a8d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1017,6 +1017,7 @@ package android { field public static final int restrictionType = 16843923; // 0x1010493 field public static final int resumeWhilePausing = 16843954; // 0x10104b2 field public static final int reversible = 16843851; // 0x101044b + field public static final int revisionCode = 16843989; // 0x10104d5 field public static final int right = 16843183; // 0x10101af field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093 field public static final int ringtoneType = 16843257; // 0x10101f9 @@ -8545,6 +8546,7 @@ package android.content.pm { field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1 field public android.content.pm.ActivityInfo[] activities; field public android.content.pm.ApplicationInfo applicationInfo; + field public int baseRevisionCode; field public android.content.pm.ConfigurationInfo[] configPreferences; field public android.content.pm.FeatureGroupInfo[] featureGroups; field public long firstInstallTime; @@ -8564,6 +8566,7 @@ package android.content.pm { field public int sharedUserLabel; field public android.content.pm.Signature[] signatures; field public java.lang.String[] splitNames; + field public int[] splitRevisionCodes; field public int versionCode; field public java.lang.String versionName; } diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index b66bd01..9223269 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -41,14 +41,30 @@ public class PackageInfo implements Parcelable { * attribute. */ public int versionCode; - + /** * The version name of this package, as specified by the <manifest> * tag's {@link android.R.styleable#AndroidManifest_versionName versionName} * attribute. */ public String versionName; - + + /** + * The revision number of the base APK for this package, as specified by the + * <manifest> tag's + * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode} + * attribute. + */ + public int baseRevisionCode; + + /** + * The revision number of any split APKs for this package, as specified by + * the <manifest> tag's + * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode} + * attribute. Indexes are a 1:1 mapping against {@link #splitNames}. + */ + public int[] splitRevisionCodes; + /** * The shared user ID name of this package, as specified by the <manifest> * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId} @@ -257,21 +273,26 @@ public class PackageInfo implements Parcelable { public PackageInfo() { } + @Override public String toString() { return "PackageInfo{" + Integer.toHexString(System.identityHashCode(this)) + " " + packageName + "}"; } + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeString(packageName); dest.writeStringArray(splitNames); dest.writeInt(versionCode); dest.writeString(versionName); + dest.writeInt(baseRevisionCode); + dest.writeIntArray(splitRevisionCodes); dest.writeString(sharedUserId); dest.writeInt(sharedUserLabel); if (applicationInfo != null) { @@ -305,10 +326,12 @@ public class PackageInfo implements Parcelable { public static final Parcelable.Creator<PackageInfo> CREATOR = new Parcelable.Creator<PackageInfo>() { + @Override public PackageInfo createFromParcel(Parcel source) { return new PackageInfo(source); } + @Override public PackageInfo[] newArray(int size) { return new PackageInfo[size]; } @@ -316,9 +339,11 @@ public class PackageInfo implements Parcelable { private PackageInfo(Parcel source) { packageName = source.readString(); - splitNames = source.readStringArray(); + splitNames = source.createStringArray(); versionCode = source.readInt(); versionName = source.readString(); + baseRevisionCode = source.readInt(); + splitRevisionCodes = source.createIntArray(); sharedUserId = source.readString(); sharedUserLabel = source.readInt(); int hasApp = source.readInt(); diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java index e336c5f..1efe082 100644 --- a/core/java/android/content/pm/PackageInfoLite.java +++ b/core/java/android/content/pm/PackageInfoLite.java @@ -19,6 +19,8 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.content.PackageHelper; + /** * Basic information about a package as specified in its manifest. * Utility class used in PackageManager methods @@ -31,11 +33,19 @@ public class PackageInfoLite implements Parcelable { */ public String packageName; + /** Names of any split APKs, ordered by parsed splitName */ + public String[] splitNames; + /** * The android:versionCode of the package. */ public int versionCode; + /** Revision code of base APK */ + public int baseRevisionCode; + /** Revision codes of any split APKs, ordered by parsed splitName */ + public int[] splitRevisionCodes; + /** * The android:multiArch flag from the package manifest. If set, * we will extract all native libraries for the given app, not just those @@ -70,7 +80,10 @@ public class PackageInfoLite implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeString(packageName); + dest.writeStringArray(splitNames); dest.writeInt(versionCode); + dest.writeInt(baseRevisionCode); + dest.writeIntArray(splitRevisionCodes); dest.writeInt(recommendedInstallLocation); dest.writeInt(installLocation); dest.writeInt(multiArch ? 1 : 0); @@ -96,7 +109,10 @@ public class PackageInfoLite implements Parcelable { private PackageInfoLite(Parcel source) { packageName = source.readString(); + splitNames = source.createStringArray(); versionCode = source.readInt(); + baseRevisionCode = source.readInt(); + splitRevisionCodes = source.createIntArray(); recommendedInstallLocation = source.readInt(); installLocation = source.readInt(); multiArch = (source.readInt() != 0); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 8515520..82da7c5 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -261,11 +261,16 @@ public class PackageParser { /** Paths of any split APKs, ordered by parsed splitName */ public final String[] splitCodePaths; + /** Revision code of base APK */ + public final int baseRevisionCode; + /** Revision codes of any split APKs, ordered by parsed splitName */ + public final int[] splitRevisionCodes; + public final boolean coreApp; public final boolean multiArch; public PackageLite(String codePath, ApkLite baseApk, String[] splitNames, - String[] splitCodePaths) { + String[] splitCodePaths, int[] splitRevisionCodes) { this.packageName = baseApk.packageName; this.versionCode = baseApk.versionCode; this.installLocation = baseApk.installLocation; @@ -274,6 +279,8 @@ public class PackageParser { this.codePath = codePath; this.baseCodePath = baseApk.codePath; this.splitCodePaths = splitCodePaths; + this.baseRevisionCode = baseApk.revisionCode; + this.splitRevisionCodes = splitRevisionCodes; this.coreApp = baseApk.coreApp; this.multiArch = baseApk.multiArch; } @@ -296,6 +303,7 @@ public class PackageParser { public final String packageName; public final String splitName; public final int versionCode; + public final int revisionCode; public final int installLocation; public final VerifierInfo[] verifiers; public final Signature[] signatures; @@ -303,12 +311,13 @@ public class PackageParser { public final boolean multiArch; public ApkLite(String codePath, String packageName, String splitName, int versionCode, - int installLocation, List<VerifierInfo> verifiers, Signature[] signatures, - boolean coreApp, boolean multiArch) { + int revisionCode, int installLocation, List<VerifierInfo> verifiers, + Signature[] signatures, boolean coreApp, boolean multiArch) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; this.versionCode = versionCode; + this.revisionCode = revisionCode; this.installLocation = installLocation; this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]); this.signatures = signatures; @@ -409,6 +418,8 @@ public class PackageParser { pi.packageName = p.packageName; pi.splitNames = p.splitNames; pi.versionCode = p.mVersionCode; + pi.baseRevisionCode = p.baseRevisionCode; + pi.splitRevisionCodes = p.splitRevisionCodes; pi.versionName = p.mVersionName; pi.sharedUserId = p.mSharedUserId; pi.sharedUserLabel = p.mSharedUserLabel; @@ -647,7 +658,7 @@ public class PackageParser { throws PackageParserException { final ApkLite baseApk = parseApkLite(packageFile, flags); final String packagePath = packageFile.getAbsolutePath(); - return new PackageLite(packagePath, baseApk, null, null); + return new PackageLite(packagePath, baseApk, null, null, null); } private static PackageLite parseClusterPackageLite(File packageDir, int flags) @@ -704,20 +715,24 @@ public class PackageParser { String[] splitNames = null; String[] splitCodePaths = null; + int[] splitRevisionCodes = null; if (size > 0) { splitNames = new String[size]; splitCodePaths = new String[size]; + splitRevisionCodes = new int[size]; splitNames = apks.keySet().toArray(splitNames); Arrays.sort(splitNames, sSplitNameComparator); for (int i = 0; i < size; i++) { splitCodePaths[i] = apks.get(splitNames[i]).codePath; + splitRevisionCodes[i] = apks.get(splitNames[i]).revisionCode; } } final String codePath = packageDir.getAbsolutePath(); - return new PackageLite(codePath, baseApk, splitNames, splitCodePaths); + return new PackageLite(codePath, baseApk, splitNames, splitCodePaths, + splitRevisionCodes); } /** @@ -782,6 +797,7 @@ public class PackageParser { final int num = lite.splitNames.length; pkg.splitNames = lite.splitNames; pkg.splitCodePaths = lite.splitCodePaths; + pkg.splitRevisionCodes = lite.splitRevisionCodes; pkg.splitFlags = new int[num]; for (int i = 0; i < num; i++) { @@ -1249,25 +1265,21 @@ public class PackageParser { int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; int versionCode = 0; + int revisionCode = 0; boolean coreApp = false; boolean multiArch = false; - int numFound = 0; for (int i = 0; i < attrs.getAttributeCount(); i++) { - String attr = attrs.getAttributeName(i); + final String attr = attrs.getAttributeName(i); if (attr.equals("installLocation")) { installLocation = attrs.getAttributeIntValue(i, PARSE_DEFAULT_INSTALL_LOCATION); - numFound++; } else if (attr.equals("versionCode")) { versionCode = attrs.getAttributeIntValue(i, 0); - numFound++; + } else if (attr.equals("revisionCode")) { + revisionCode = attrs.getAttributeIntValue(i, 0); } else if (attr.equals("coreApp")) { coreApp = attrs.getAttributeBooleanValue(i, false); - numFound++; - } - if (numFound >= 3) { - break; } } @@ -1301,7 +1313,7 @@ public class PackageParser { } return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode, - installLocation, verifiers, signatures, coreApp, multiArch); + revisionCode, installLocation, verifiers, signatures, coreApp, multiArch); } /** @@ -1359,6 +1371,8 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifest); pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger( com.android.internal.R.styleable.AndroidManifest_versionCode, 0); + pkg.baseRevisionCode = sa.getInteger( + com.android.internal.R.styleable.AndroidManifest_revisionCode, 0); pkg.mVersionName = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifest_versionName, 0); if (pkg.mVersionName != null) { @@ -4168,6 +4182,7 @@ public class PackageParser { public final static class Package { public String packageName; + /** Names of any split APKs, ordered by parsed splitName */ public String[] splitNames; @@ -4185,6 +4200,11 @@ public class PackageParser { /** Paths of any split APKs, ordered by parsed splitName */ public String[] splitCodePaths; + /** Revision code of base APK */ + public int baseRevisionCode; + /** Revision codes of any split APKs, ordered by parsed splitName */ + public int[] splitRevisionCodes; + /** Flags of any split APKs; ordered by parsed splitName */ public int[] splitFlags; @@ -4222,7 +4242,7 @@ public class PackageParser { // The version code declared for this package. public int mVersionCode; - + // The version name declared for this package. public String mVersionName; diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index ad54912..78b9c18 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -296,9 +296,9 @@ public class VpnService extends Service { * * This method only needs to be called if the VPN has explicitly bound its underlying * communications channels — such as the socket(s) passed to {@link #protect(int)} — - * to a {@code Network} using APIs such as {@link Network#bindSocket} or {@link - * Network#bindDatagramSocket}. The VPN should call this method every time the set of {@code - * Network}s it is using changes. + * to a {@code Network} using APIs such as {@link Network#bindSocket(Socket)} or + * {@link Network#bindSocket(DatagramSocket)}. The VPN should call this method every time + * the set of {@code Network}s it is using changes. * * {@code networks} is one of the following: * <ul> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 10c2518..0c3fb9a 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -260,9 +260,18 @@ released, or define it however else you want, as long as each successive version has a higher number. This is not a version number generally shown to the user, that is usually supplied - with {@link android.R.attr#versionName}. --> + with {@link android.R.attr#versionName}. When an app is delivered + as multiple split APKs, each APK must have the exact same versionCode. --> <attr name="versionCode" format="integer" /> - + + <!-- Internal revision code. This number is the number used to determine + whether one APK is more recent than another: it has no other meaning + than that higher numbers are more recent. This value is only meaningful + when the two {@link android.R.attr#versionCode} values are already + identical. When an app is delivered as multiple split APKs, each + APK may have a different revisionCode value. --> + <attr name="revisionCode" format="integer" /> + <!-- The text shown to the user to indicate the version they have. This is used for no other purpose than display to the user; the actual significant version number is given by {@link android.R.attr#versionCode}. --> @@ -1025,6 +1034,7 @@ <declare-styleable name="AndroidManifest"> <attr name="versionCode" /> <attr name="versionName" /> + <attr name="revisionCode" /> <attr name="sharedUserId" /> <attr name="sharedUserLabel" /> <attr name="installLocation" /> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index def5444..2bb9aa8 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2593,6 +2593,7 @@ <public type="attr" name="accessibilityTraversalAfter" id="0x010104d2" /> <public type="attr" name="dialogPreferredPadding" id="0x010104d3" /> <public type="attr" name="searchHintIcon" id="0x010104d4" /> + <public type="attr" name="revisionCode" /> <public type="style" name="Theme.DeviceDefault.Dialog.Alert" /> <public type="style" name="Theme.DeviceDefault.Light.Dialog.Alert" /> diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 1f28324..cc1e01a 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -180,7 +180,10 @@ public class DefaultContainerService extends IntentService { } ret.packageName = pkg.packageName; + ret.splitNames = pkg.splitNames; ret.versionCode = pkg.versionCode; + ret.baseRevisionCode = pkg.baseRevisionCode; + ret.splitRevisionCodes = pkg.splitRevisionCodes; ret.installLocation = pkg.installLocation; ret.verifiers = pkg.verifiers; ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index fb0e357..cc1b3ad 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -497,12 +497,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // haven't been overridden. if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { try { - if (stageCid != null) { + final List<File> fromFiles = mResolvedInheritedFiles; + final File toDir = resolveStageDir(); + + if (isLinkPossible(fromFiles, toDir)) { + linkFiles(fromFiles, toDir); + } else { // TODO: this should delegate to DCS so the system process // avoids holding open FDs into containers. - copyFiles(mResolvedInheritedFiles, resolveStageDir()); - } else { - linkFiles(mResolvedInheritedFiles, resolveStageDir()); + copyFiles(fromFiles, toDir); } } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, @@ -721,7 +724,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // This is kind of hacky; we're creating a half-parsed package that is // straddled between the inherited and staged APKs. final PackageLite pkg = new PackageLite(null, baseApk, null, - splitPaths.toArray(new String[splitPaths.size()])); + splitPaths.toArray(new String[splitPaths.size()]), null); final boolean isForwardLocked = (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0; @@ -733,6 +736,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * Determine if creating hard links between source and destination is + * possible. That is, do they all live on the same underlying device. + */ + private boolean isLinkPossible(List<File> fromFiles, File toDir) { + try { + final StructStat toStat = Os.stat(toDir.getAbsolutePath()); + for (File fromFile : fromFiles) { + final StructStat fromStat = Os.stat(fromFile.getAbsolutePath()); + if (fromStat.st_dev != toStat.st_dev) { + return false; + } + } + } catch (ErrnoException e) { + Slog.w(TAG, "Failed to detect if linking possible: " + e); + return false; + } + return true; + } + private static void linkFiles(List<File> fromFiles, File toDir) throws IOException { for (File fromFile : fromFiles) { final File toFile = new File(toDir, fromFile.getName()); @@ -760,7 +783,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.copyFile(fromFile, tmpFile)) { throw new IOException("Failed to copy " + fromFile + " to " + tmpFile); } - + try { + Os.chmod(tmpFile.getAbsolutePath(), 0644); + } catch (ErrnoException e) { + throw new IOException("Failed to chmod " + tmpFile); + } final File toFile = new File(toDir, fromFile.getName()); if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile); if (!tmpFile.renameTo(toFile)) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b891d0c..32e9dae 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -41,6 +41,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY; import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED; +import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageParser.isApkFile; @@ -4275,7 +4276,7 @@ public class PackageManagerService extends IPackageManager.Stub { // version of the new path against what we have stored to determine // what to do. if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath); - if (pkg.mVersionCode < ps.versionCode) { + if (pkg.mVersionCode <= ps.versionCode) { // The system package has been updated and the code path does not match // Ignore entry. Skip it. Slog.i(TAG, "Package " + ps.name + " at " + scanFile @@ -4366,7 +4367,7 @@ public class PackageManagerService extends IPackageManager.Stub { * already installed version, hide it. It will be scanned later * and re-added like an update. */ - if (pkg.mVersionCode < ps.versionCode) { + if (pkg.mVersionCode <= ps.versionCode) { shouldHideSystemApp = true; logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile + " but new version " + pkg.mVersionCode + " better than installed " @@ -8857,11 +8858,10 @@ public class PackageManagerService extends IPackageManager.Stub { if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { // Check for downgrading. if ((installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) == 0) { - if (pkgLite.versionCode < pkg.mVersionCode) { - Slog.w(TAG, "Can't install update of " + packageName - + " update version " + pkgLite.versionCode - + " is older than installed version " - + pkg.mVersionCode); + try { + checkDowngrade(pkg, pkgLite); + } catch (PackageManagerException e) { + Slog.w(TAG, "Downgrade detected: " + e.getMessage()); return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE; } } @@ -13539,4 +13539,38 @@ public class PackageManagerService extends IPackageManager.Stub { } } } + + /** + * Check and throw if the given before/after packages would be considered a + * downgrade. + */ + private static void checkDowngrade(PackageParser.Package before, PackageInfoLite after) + throws PackageManagerException { + if (after.versionCode < before.mVersionCode) { + throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE, + "Update version code " + after.versionCode + " is older than current " + + before.mVersionCode); + } else if (after.versionCode == before.mVersionCode) { + if (after.baseRevisionCode < before.baseRevisionCode) { + throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE, + "Update base revision code " + after.baseRevisionCode + + " is older than current " + before.baseRevisionCode); + } + + if (!ArrayUtils.isEmpty(after.splitNames)) { + for (int i = 0; i < after.splitNames.length; i++) { + final String splitName = after.splitNames[i]; + final int j = ArrayUtils.indexOf(before.splitNames, splitName); + if (j != -1) { + if (after.splitRevisionCodes[i] < before.splitRevisionCodes[j]) { + throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE, + "Update split " + splitName + " revision code " + + after.splitRevisionCodes[i] + " is older than current " + + before.splitRevisionCodes[j]); + } + } + } + } + } + } } |