summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2014-11-22 16:49:34 -0800
committerJeff Sharkey <jsharkey@android.com>2014-11-24 12:13:11 -0800
commit88d2a3c0e1b4a8c53a489db5d627beb80b1b9957 (patch)
tree18761183dea3ba5e0c63283feb938934a3454294
parent4dfce43e976a294b3b575564c855214a8e5cef58 (diff)
downloadframeworks_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.txt3
-rw-r--r--core/java/android/content/pm/PackageInfo.java31
-rw-r--r--core/java/android/content/pm/PackageInfoLite.java16
-rw-r--r--core/java/android/content/pm/PackageParser.java50
-rw-r--r--core/java/android/net/VpnService.java6
-rw-r--r--core/res/res/values/attrs_manifest.xml14
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java39
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java48
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 &lt;manifest&gt;
* 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
+ * &lt;manifest&gt; 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 &lt;manifest&gt; 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 &lt;manifest&gt;
* 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 &mdash; such as the socket(s) passed to {@link #protect(int)} &mdash;
- * 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]);
+ }
+ }
+ }
+ }
+ }
+ }
}