Merge "[PM] Add downgrade check for split apps when data exists" into main
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 303371b..8d3f07e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2261,6 +2261,12 @@
                         installRequest.getNewUsers());
                 mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
                 mPm.updateInstantAppInstallerLocked(packageName);
+
+                // The installation is success, remove the split info copy stored in package
+                // setting for the downgrade version check of DELETE_KEEP_DATA and archived app
+                // cases.
+                ps.setSplitNames(null);
+                ps.setSplitRevisionCodes(null);
             }
             installRequest.onCommitFinished();
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index c3cac20..a1dffc6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1423,11 +1423,8 @@
      */
     public static void checkDowngrade(@NonNull PackageSetting before,
             @NonNull PackageInfoLite after) throws PackageManagerException {
-        if (after.getLongVersionCode() < before.getVersionCode()) {
-            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                    "Update version code " + after.versionCode + " is older than current "
-                            + before.getVersionCode());
-        }
+        checkDowngrade(before.getVersionCode(), before.getBaseRevisionCode(),
+                before.getSplitNames(), before.getSplitRevisionCodes(), after);
     }
 
     /**
@@ -1436,28 +1433,35 @@
      */
     public static void checkDowngrade(@NonNull AndroidPackage before,
             @NonNull PackageInfoLite after) throws PackageManagerException {
-        if (after.getLongVersionCode() < before.getLongVersionCode()) {
+        checkDowngrade(before.getLongVersionCode(), before.getBaseRevisionCode(),
+                before.getSplitNames(), before.getSplitRevisionCodes(), after);
+    }
+
+    private static void checkDowngrade(long beforeVersionCode, int beforeBaseRevisionCode,
+            @NonNull String[] beforeSplitNames, @NonNull int[] beforeSplitRevisionCodes,
+            @NonNull PackageInfoLite after) throws PackageManagerException {
+        if (after.getLongVersionCode() < beforeVersionCode) {
             throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                     "Update version code " + after.versionCode + " is older than current "
-                            + before.getLongVersionCode());
-        } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
-            if (after.baseRevisionCode < before.getBaseRevisionCode()) {
+                            + beforeVersionCode);
+        } else if (after.getLongVersionCode() == beforeVersionCode) {
+            if (after.baseRevisionCode < beforeBaseRevisionCode) {
                 throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                         "Update base revision code " + after.baseRevisionCode
-                                + " is older than current " + before.getBaseRevisionCode());
+                                + " is older than current " + beforeBaseRevisionCode);
             }
 
             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.getSplitNames(), splitName);
+                    final int j = ArrayUtils.indexOf(beforeSplitNames, splitName);
                     if (j != -1) {
-                        if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
+                        if (after.splitRevisionCodes[i] < beforeSplitRevisionCodes[j]) {
                             throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                                     "Update split " + splitName + " revision code "
                                             + after.splitRevisionCodes[i]
                                             + " is older than current "
-                                            + before.getSplitRevisionCodes()[j]);
+                                            + beforeSplitRevisionCodes[j]);
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 82df527..9f10e01 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -234,6 +234,22 @@
     @Nullable
     private byte[] mRestrictUpdateHash;
 
+    // This is the copy of the same data stored in AndroidPackage. It is not null if the
+    // AndroidPackage is deleted in cases of DELETE_KEEP_DATA. When AndroidPackage is not null,
+    // the field will be null, and the getter method will return the data from AndroidPackage
+    // instead.
+    @Nullable
+    private String[] mSplitNames;
+
+    // This is the copy of the same data stored in AndroidPackage. It is not null if the
+    // AndroidPackage is deleted in cases of DELETE_KEEP_DATA. When AndroidPackage is not null,
+    // the field will be null, and the getter method will return the data from AndroidPackage
+    // instead.
+    @Nullable
+    private int[] mSplitRevisionCodes;
+
+    private int mBaseRevisionCode;
+
     /**
      * Snapshot support.
      */
@@ -578,6 +594,62 @@
         return getBoolean(Booleans.DEBUGGABLE);
     }
 
+    /**
+     * @see AndroidPackage#getBaseRevisionCode
+     */
+    public PackageSetting setBaseRevisionCode(int value) {
+        mBaseRevisionCode = value;
+        onChanged();
+        return this;
+    }
+
+    /**
+     * @see AndroidPackage#getBaseRevisionCode
+     */
+    public int getBaseRevisionCode() {
+        return mBaseRevisionCode;
+    }
+
+    /**
+     * @see AndroidPackage#getSplitNames
+     */
+    public PackageSetting setSplitNames(String[] value) {
+        mSplitNames = value;
+        onChanged();
+        return this;
+    }
+
+    /**
+     * @see AndroidPackage#getSplitNames
+     */
+    @NonNull
+    public String[] getSplitNames() {
+        if (pkg != null) {
+            return pkg.getSplitNames();
+        }
+        return mSplitNames == null ? EmptyArray.STRING : mSplitNames;
+    }
+
+    /**
+     * @see AndroidPackage#getSplitRevisionCodes
+     */
+    public PackageSetting setSplitRevisionCodes(int[] value) {
+        mSplitRevisionCodes = value;
+        onChanged();
+        return this;
+    }
+
+    /**
+     * @see AndroidPackage#getSplitRevisionCodes
+     */
+    @NonNull
+    public int[] getSplitRevisionCodes() {
+        if (pkg != null) {
+            return pkg.getSplitRevisionCodes();
+        }
+        return mSplitRevisionCodes == null ? EmptyArray.INT : mSplitRevisionCodes;
+    }
+
     @Override
     public String toString() {
         return "PackageSetting{"
@@ -739,6 +811,11 @@
         mTargetSdkVersion = other.mTargetSdkVersion;
         mRestrictUpdateHash = other.mRestrictUpdateHash == null
                 ? null : other.mRestrictUpdateHash.clone();
+        mBaseRevisionCode = other.mBaseRevisionCode;
+        mSplitNames = other.mSplitNames != null
+                ? Arrays.copyOf(other.mSplitNames, other.mSplitNames.length) : null;
+        mSplitRevisionCodes = other.mSplitRevisionCodes != null
+                ? Arrays.copyOf(other.mSplitRevisionCodes, other.mSplitRevisionCodes.length) : null;
 
         usesSdkLibraries = other.usesSdkLibraries != null
                 ? Arrays.copyOf(other.usesSdkLibraries,
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 2f2c451..7afc358 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -432,6 +432,13 @@
                 }
                 deletedPs.setInstalled(/* installed= */ false, userId);
             }
+
+            // Preserve split apk information for downgrade check with DELETE_KEEP_DATA and archived
+            // app cases
+            if (deletedPkg.getSplitNames() != null) {
+                deletedPs.setSplitNames(deletedPkg.getSplitNames());
+                deletedPs.setSplitRevisionCodes(deletedPkg.getSplitRevisionCodes());
+            }
         }
 
         // make sure to preserve per-user installed state if this removal was just
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index d8ce38e..95561f5f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -437,8 +437,9 @@
             pkgSetting.setIsOrphaned(true);
         }
 
-        // update debuggable to packageSetting
+        // update debuggable and BaseRevisionCode to packageSetting
         pkgSetting.setDebuggable(parsedPackage.isDebuggable());
+        pkgSetting.setBaseRevisionCode(parsedPackage.getBaseRevisionCode());
 
         // Take care of first install / last update times.
         final long scanFileTime = getLastModifiedTime(parsedPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0d16b00..9177e2b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -325,6 +325,7 @@
     private static final String TAG_MIME_TYPE = "mime-type";
     private static final String TAG_ARCHIVE_STATE = "archive-state";
     private static final String TAG_ARCHIVE_ACTIVITY_INFO = "archive-activity-info";
+    private static final String TAG_SPLIT_VERSION = "split-version";
 
     public static final String ATTR_NAME = "name";
     public static final String ATTR_PACKAGE = "package";
@@ -3261,6 +3262,9 @@
         if (pkg.isLoading()) {
             serializer.attributeBoolean(null, "isLoading", true);
         }
+        if (pkg.getBaseRevisionCode() != 0) {
+            serializer.attributeInt(null, "baseRevisionCode", pkg.getBaseRevisionCode());
+        }
         serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
         serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
 
@@ -3289,7 +3293,12 @@
         writeUpgradeKeySetsLPr(serializer, pkg.getKeySetData());
         writeKeySetAliasesLPr(serializer, pkg.getKeySetData());
         writeMimeGroupLPr(serializer, pkg.getMimeGroups());
-
+        // If getPkg is not NULL, these values are from the getPkg. And these values are preserved
+        // for the downgrade check for DELETE_KEEP_DATA and archived app cases. If the getPkg is
+        // not NULL, we don't need to preserve it.
+        if (pkg.getPkg() == null) {
+            writeSplitVersionsLPr(serializer, pkg.getSplitNames(), pkg.getSplitRevisionCodes());
+        }
         serializer.endTag(null, "package");
     }
 
@@ -4071,6 +4080,7 @@
         byte[] restrictUpdateHash = null;
         boolean isScannedAsStoppedSystemApp = false;
         boolean isSdkLibrary = false;
+        int baseRevisionCode = 0;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
@@ -4116,6 +4126,7 @@
             appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
             appMetadataSource = parser.getAttributeInt(null, "appMetadataSource",
                     PackageManager.APP_METADATA_SOURCE_UNKNOWN);
+            baseRevisionCode = parser.getAttributeInt(null, "baseRevisionCode", 0);
 
             isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
                 "scannedAsStoppedSystemApp", false);
@@ -4269,6 +4280,7 @@
                     .setAppMetadataFilePath(appMetadataFilePath)
                     .setAppMetadataSource(appMetadataSource)
                     .setTargetSdkVersion(targetSdkVersion)
+                    .setBaseRevisionCode(baseRevisionCode)
                     .setRestrictUpdateHash(restrictUpdateHash)
                     .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
             // Handle legacy string here for single-user mode
@@ -4374,6 +4386,8 @@
                     readUsesStaticLibLPw(parser, packageSetting);
                 } else if (tagName.equals(TAG_USES_SDK_LIB)) {
                     readUsesSdkLibLPw(parser, packageSetting);
+                } else if (tagName.equals(TAG_SPLIT_VERSION)) {
+                    readSplitVersionsLPw(parser, packageSetting);
                 } else {
                     PackageManagerService.reportSettingsProblem(Log.WARN,
                             "Unknown element under <package>: " + parser.getName());
@@ -4470,6 +4484,37 @@
         }
     }
 
+    private void readSplitVersionsLPw(TypedXmlPullParser parser, PackageSetting outPs)
+            throws IOException, XmlPullParserException {
+        String splitName = parser.getAttributeValue(null, ATTR_NAME);
+        int splitRevision = parser.getAttributeInt(null, ATTR_VERSION, -1);
+        if (splitName != null && splitRevision >= 0) {
+            outPs.setSplitNames(ArrayUtils.appendElement(String.class,
+                    outPs.getSplitNames(), splitName));
+            outPs.setSplitRevisionCodes(ArrayUtils.appendInt(
+                    outPs.getSplitRevisionCodes(), splitRevision));
+        }
+
+        XmlUtils.skipCurrentTag(parser);
+    }
+
+    private void writeSplitVersionsLPr(TypedXmlSerializer serializer, String[] splitNames,
+            int[] splitRevisionCodes) throws IOException {
+        if (ArrayUtils.isEmpty(splitNames) || ArrayUtils.isEmpty(splitRevisionCodes)
+                || splitNames.length != splitRevisionCodes.length) {
+            return;
+        }
+        final int libLength = splitNames.length;
+        for (int i = 0; i < libLength; i++) {
+            final String splitName = splitNames[i];
+            final int splitRevision = splitRevisionCodes[i];
+            serializer.startTag(null, TAG_SPLIT_VERSION);
+            serializer.attribute(null, ATTR_NAME, splitName);
+            serializer.attributeInt(null, ATTR_VERSION, splitRevision);
+            serializer.endTag(null, TAG_SPLIT_VERSION);
+        }
+    }
+
     private void readDisabledComponentsLPw(PackageSetting packageSetting, TypedXmlPullParser parser,
             int userId) throws IOException, XmlPullParserException {
         int outerDepth = parser.getDepth();
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 7138306..dec4634 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1046,6 +1046,77 @@
     }
 
     @Test
+    public void testWriteReadBaseRevisionCode() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+        packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+                .setUid(packageSetting.getAppId())
+                .hideAsFinal());
+
+        final int revisionCode = 311;
+        packageSetting.setBaseRevisionCode(revisionCode);
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        assertThat(settings.getPackageLPr(PACKAGE_NAME_1).getBaseRevisionCode(), is(revisionCode));
+    }
+
+    @Test
+    public void testHasPkg_writeReadSplitVersions() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+        packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+                .setUid(packageSetting.getAppId())
+                .hideAsFinal());
+
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        packageSetting.setSplitNames(new String[] { splitOne, splitTwo});
+        packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionTwo});
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getSplitNames().length, is(0));
+        assertThat(resultSetting.getSplitRevisionCodes().length, is(0));
+    }
+
+    @Test
+    public void testNoPkg_writeReadSplitVersions() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        packageSetting.setSplitNames(new String[] { splitOne, splitTwo});
+        packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionTwo});
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getSplitNames()[0], is(splitOne));
+        assertThat(resultSetting.getSplitNames()[1], is(splitTwo));
+        assertThat(resultSetting.getSplitRevisionCodes()[0], is(revisionOne));
+        assertThat(resultSetting.getSplitRevisionCodes()[1], is(revisionTwo));
+    }
+
+    @Test
     public void testWriteReadArchiveState() {
         Settings settings = makeSettings();
         PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);