Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 266fba16 authored by Oli Lan's avatar Oli Lan Committed by Android (Google) Code Review
Browse files

Merge changes I7fd7d98a,If770a615 into rvc-dev

* changes:
  Block rollback commit if it may violate min extension constraint.
  Record supported extension versions at rollback creation.
parents 33cf9d09 ac7bc339
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -38,6 +38,9 @@ public class SdkExtensions {

    private static final int R_EXTENSION_INT;
    static {
        // Note: when adding more extension versions, the logic that records current
        // extension versions when saving a rollback must also be updated.
        // At the time of writing this is in RollbackManagerServiceImpl#getExtensionVersions()
        R_EXTENSION_INT = SystemProperties.getInt("build.version.extensions.r", 0);
    }

+92 −3
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
@@ -36,14 +37,19 @@ import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.ext.SdkExtensions;
import android.text.TextUtils;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.SparseLongArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.pm.parsing.pkg.AndroidPackage;

import java.io.File;
import java.io.IOException;
@@ -173,6 +179,11 @@ class Rollback {
    @GuardedBy("mLock")
    private int mNumPackageSessionsWithSuccess;

    /**
     * The extension versions supported at the time of rollback creation.
     */
    private final SparseIntArray mExtensionVersions;

    /**
     * Constructs a new, empty Rollback instance.
     *
@@ -182,9 +193,11 @@ class Rollback {
     * @param userId the user that performed the install with rollback enabled.
     * @param installerPackageName the installer package name from the original install session.
     * @param packageSessionIds the session ids for all packages in the install.
     * @param extensionVersions the extension versions supported at the time of rollback creation
     */
    Rollback(int rollbackId, File backupDir, int stagedSessionId, int userId,
            String installerPackageName, int[] packageSessionIds) {
            String installerPackageName, int[] packageSessionIds,
            SparseIntArray extensionVersions) {
        this.info = new RollbackInfo(rollbackId,
                /* packages */ new ArrayList<>(),
                /* isStaged */ stagedSessionId != -1,
@@ -197,11 +210,13 @@ class Rollback {
        mState = ROLLBACK_STATE_ENABLING;
        mTimestamp = Instant.now();
        mPackageSessionIds = packageSessionIds != null ? packageSessionIds : new int[0];
        mExtensionVersions = extensionVersions;
    }

    Rollback(int rollbackId, File backupDir, int stagedSessionId, int userId,
             String installerPackageName) {
        this(rollbackId, backupDir, stagedSessionId, userId, installerPackageName, null);
        this(rollbackId, backupDir, stagedSessionId, userId, installerPackageName, null,
                new SparseIntArray(0));
    }

    /**
@@ -209,7 +224,7 @@ class Rollback {
     */
    Rollback(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
            @RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress,
            int userId, String installerPackageName) {
            int userId, String installerPackageName, SparseIntArray extensionVersions) {
        this.info = info;
        mUserId = userId;
        mInstallerPackageName = installerPackageName;
@@ -219,6 +234,7 @@ class Rollback {
        mState = state;
        mApkSessionId = apkSessionId;
        mRestoreUserDataInProgress = restoreUserDataInProgress;
        mExtensionVersions = extensionVersions;
        // TODO(b/120200473): Include this field during persistence. This field will be used to
        // decide which rollback to expire when ACTION_PACKAGE_REPLACED is received. Note persisting
        // this field is not backward compatible. We won't fix b/120200473 until S to minimize the
@@ -282,6 +298,14 @@ class Rollback {
        return mInstallerPackageName;
    }

    /**
     * Returns the extension versions that were supported at the time that the rollback was created,
     * as a mapping from SdkVersion to ExtensionVersion.
     */
    SparseIntArray getExtensionVersions() {
        return mExtensionVersions;
    }

    /**
     * Returns true if the rollback is in the ENABLING state.
     */
@@ -451,6 +475,15 @@ class Rollback {
                return;
            }

            if (containsApex() && wasCreatedAtLowerExtensionVersion()) {
                PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
                if (extensionVersionReductionWouldViolateConstraint(mExtensionVersions, pmi)) {
                    sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
                            "Rollback may violate a minExtensionVersion constraint");
                    return;
                }
            }

            // Get a context to use to install the downgraded version of the package.
            Context pkgContext;
            try {
@@ -826,6 +859,56 @@ class Rollback {
        }
    }

    /**
     * Returns true if there is an app installed that specifies a minExtensionVersion greater
     * than what was present at the time this Rollback was created.
     */
    @VisibleForTesting
    static boolean extensionVersionReductionWouldViolateConstraint(
            SparseIntArray rollbackExtVers, PackageManagerInternal pmi) {
        if (rollbackExtVers.size() == 0) {
            return false;
        }
        List<String> packages = pmi.getPackageList().getPackageNames();
        for (int i = 0; i < packages.size(); i++) {
            AndroidPackage pkg = pmi.getPackage(packages.get(i));
            SparseIntArray minExtVers = pkg.getMinExtensionVersions();
            if (minExtVers == null) {
                continue;
            }
            for (int j = 0; j < rollbackExtVers.size(); j++) {
                int minExt = minExtVers.get(rollbackExtVers.keyAt(j), -1);
                if (rollbackExtVers.valueAt(j) < minExt) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns true if for any SDK version, the extension version recorded at the time of rollback
     * creation is lower than the current extension version.
     */
    private boolean wasCreatedAtLowerExtensionVersion() {
        for (int i = 0; i < mExtensionVersions.size(); i++) {
            if (SdkExtensions.getExtensionVersion(mExtensionVersions.keyAt(i))
                    > mExtensionVersions.valueAt(i)) {
                return true;
            }
        }
        return false;
    }

    private boolean containsApex() {
        for (PackageRollbackInfo pkgInfo : info.getPackages()) {
            if (pkgInfo.isApex()) {
                return true;
            }
        }
        return false;
    }

    void dump(IndentingPrintWriter ipw) {
        synchronized (mLock) {
            ipw.println(info.getRollbackId() + ":");
@@ -852,6 +935,12 @@ class Rollback {
                ipw.decreaseIndent();
                ipw.println("-committedSessionId: " + info.getCommittedSessionId());
            }
            if (mExtensionVersions.size() > 0) {
                ipw.println("-extensionVersions:");
                ipw.increaseIndent();
                ipw.println(mExtensionVersions.toString());
                ipw.decreaseIndent();
            }
            ipw.decreaseIndent();
        }
    }
+18 −2
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.content.rollback.IRollbackManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -49,12 +50,14 @@ import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.ext.SdkExtensions;
import android.provider.DeviceConfig;
import android.util.IntArray;
import android.util.Log;
import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
@@ -1274,16 +1277,29 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {

        if (parentSession.isStaged()) {
            rollback = mRollbackStore.createStagedRollback(rollbackId, parentSessionId, userId,
                    installerPackageName, packageSessionIds);
                    installerPackageName, packageSessionIds, getExtensionVersions());
        } else {
            rollback = mRollbackStore.createNonStagedRollback(rollbackId, userId,
                    installerPackageName, packageSessionIds);
                    installerPackageName, packageSessionIds, getExtensionVersions());
        }

        mRollbacks.add(rollback);
        return rollback;
    }

    private SparseIntArray getExtensionVersions() {
        // This list must be updated whenever the current API level is increased, or should be
        // replaced when we have another way of determining the relevant SDK versions.
        final int[] relevantSdkVersions = { Build.VERSION_CODES.R };

        SparseIntArray result = new SparseIntArray(relevantSdkVersions.length);
        for (int i = 0; i < relevantSdkVersions.length; i++) {
            result.put(relevantSdkVersions[i],
                    SdkExtensions.getExtensionVersion(relevantSdkVersions[i]));
        }
        return result;
    }

    /**
     * Returns the Rollback associated with the given session if parent or child session id matches.
     * Returns null if not found.
+37 −5
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.os.UserHandle.USER_SYSTEM;
import static com.android.server.rollback.Rollback.rollbackStateFromString;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
@@ -28,6 +29,7 @@ import android.content.rollback.PackageRollbackInfo.RestoreInfo;
import android.content.rollback.RollbackInfo;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.SparseLongArray;

import com.android.internal.annotations.GuardedBy;
@@ -176,6 +178,32 @@ class RollbackStore {
        return ceSnapshotInodes;
    }

    private static @Nullable JSONArray extensionVersionsToJson(
            SparseIntArray extensionVersions) throws JSONException {
        JSONArray array = new JSONArray();
        for (int i = 0; i < extensionVersions.size(); i++) {
            JSONObject entryJson = new JSONObject();
            entryJson.put("sdkVersion", extensionVersions.keyAt(i));
            entryJson.put("extensionVersion", extensionVersions.valueAt(i));
            array.put(entryJson);
        }
        return array;
    }

    private static @Nullable SparseIntArray extensionVersionsFromJson(JSONArray json)
            throws JSONException {
        if (json == null) {
            return new SparseIntArray(0);
        }
        SparseIntArray extensionVersions = new SparseIntArray(json.length());
        for (int i = 0; i < json.length(); i++) {
            JSONObject entry = json.getJSONObject(i);
            extensionVersions.append(
                    entry.getInt("sdkVersion"), entry.getInt("extensionVersion"));
        }
        return extensionVersions;
    }

    private static JSONObject rollbackInfoToJson(RollbackInfo rollback) throws JSONException {
        JSONObject json = new JSONObject();
        json.put("rollbackId", rollback.getRollbackId());
@@ -200,10 +228,10 @@ class RollbackStore {
     * backupDir assigned.
     */
    Rollback createNonStagedRollback(int rollbackId, int userId, String installerPackageName,
            int[] packageSessionIds) {
            int[] packageSessionIds, SparseIntArray extensionVersions) {
        File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
        return new Rollback(rollbackId, backupDir, -1, userId, installerPackageName,
                packageSessionIds);
                packageSessionIds, extensionVersions);
    }

    /**
@@ -211,10 +239,11 @@ class RollbackStore {
     * backupDir assigned.
     */
    Rollback createStagedRollback(int rollbackId, int stagedSessionId, int userId,
            String installerPackageName, int[] packageSessionIds) {
            String installerPackageName, int[] packageSessionIds,
            SparseIntArray extensionVersions) {
        File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
        return new Rollback(rollbackId, backupDir, stagedSessionId, userId, installerPackageName,
                packageSessionIds);
                packageSessionIds, extensionVersions);
    }

    /**
@@ -272,6 +301,8 @@ class RollbackStore {
            dataJson.put("restoreUserDataInProgress", rollback.isRestoreUserDataInProgress());
            dataJson.put("userId", rollback.getUserId());
            dataJson.putOpt("installerPackageName", rollback.getInstallerPackageName());
            dataJson.putOpt(
                    "extensionVersions", extensionVersionsToJson(rollback.getExtensionVersions()));

            PrintWriter pw = new PrintWriter(new File(rollback.getBackupDir(), "rollback.json"));
            pw.println(dataJson.toString());
@@ -316,7 +347,8 @@ class RollbackStore {
                dataJson.getInt("apkSessionId"),
                dataJson.getBoolean("restoreUserDataInProgress"),
                dataJson.optInt("userId", USER_SYSTEM),
                dataJson.optString("installerPackageName", ""));
                dataJson.optString("installerPackageName", ""),
                extensionVersionsFromJson(dataJson.optJSONArray("extensionVersions")));
    }

    private static JSONObject toJson(VersionedPackage pkg) throws JSONException {
+105 −6
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.util.IntArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;

import com.google.common.truth.Correspondence;
@@ -83,7 +84,7 @@ public class RollbackStoreTest {
                }
            };

    private static final String JSON_ROLLBACK = "{'info':{'rollbackId':123,'packages':"
    private static final String JSON_ROLLBACK_NO_EXT = "{'info':{'rollbackId':123,'packages':"
            + "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
            + "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':"
            + "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'},"
@@ -103,6 +104,28 @@ public class RollbackStoreTest {
            + "'restoreUserDataInProgress':true, 'userId':0,"
            + "'installerPackageName':'some.installer'}";

    private static final String JSON_ROLLBACK = "{'info':{'rollbackId':123,'packages':"
            + "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
            + "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':"
            + "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'},"
            + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false,"
            + "'installedUsers':"
            + "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6},"
            + "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546,"
            + "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips',"
            + "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test',"
            + "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18,"
            + "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false,"
            + "'installedUsers':[55,79],"
            + "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
            + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
            + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
            + "'stagedSessionId':-1,'state':'enabling','apkSessionId':-1,"
            + "'restoreUserDataInProgress':true, 'userId':0,"
            + "'installerPackageName':'some.installer',"
            + "'extensionVersions':[{'sdkVersion':5,'extensionVersion':25},"
            + "{'sdkVersion':30,'extensionVersion':71}]}";

    @Rule
    public TemporaryFolder mFolder = new TemporaryFolder();

@@ -119,7 +142,10 @@ public class RollbackStoreTest {

    @Test
    public void createNonStaged() {
        Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);
        SparseIntArray extensionVersions = new SparseIntArray();
        extensionVersions.put(30, 71);
        Rollback rollback = mRollbackStore.createNonStagedRollback(
                ID, USER, INSTALLER, null, extensionVersions);

        assertThat(rollback.getBackupDir().getAbsolutePath())
                .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID);
@@ -128,11 +154,16 @@ public class RollbackStoreTest {
        assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
        assertThat(rollback.info.getPackages()).isEmpty();
        assertThat(rollback.isEnabling()).isTrue();
        assertThat(rollback.getExtensionVersions().toString())
                .isEqualTo(extensionVersions.toString());
    }

    @Test
    public void createStaged() {
        Rollback rollback = mRollbackStore.createStagedRollback(ID, 897, USER, INSTALLER, null);
        SparseIntArray extensionVersions = new SparseIntArray();
        extensionVersions.put(30, 71);
        Rollback rollback = mRollbackStore.createStagedRollback(
                ID, 897, USER, INSTALLER, null, extensionVersions);

        assertThat(rollback.getBackupDir().getAbsolutePath())
                .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID);
@@ -143,11 +174,17 @@ public class RollbackStoreTest {
        assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
        assertThat(rollback.info.getPackages()).isEmpty();
        assertThat(rollback.isEnabling()).isTrue();
        assertThat(rollback.getExtensionVersions().toString())
                .isEqualTo(extensionVersions.toString());
    }

    @Test
    public void saveAndLoadRollback() {
        Rollback origRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);
        SparseIntArray extensionVersions = new SparseIntArray();
        extensionVersions.put(5, 25);
        extensionVersions.put(30, 71);
        Rollback origRb = mRollbackStore.createNonStagedRollback(
                ID, USER, INSTALLER, null, extensionVersions);

        origRb.setRestoreUserDataInProgress(true);
        origRb.info.getCausePackages().add(new VersionedPackage("com.made.up", 2));
@@ -195,9 +232,63 @@ public class RollbackStoreTest {
        assertRollbacksAreEquivalent(loadedRb, origRb);
    }

    @Test
    public void loadFromJsonNoExtensionVersions() throws Exception {
        Rollback expectedRb = mRollbackStore.createNonStagedRollback(
                ID, USER, INSTALLER, null, new SparseIntArray(0));

        expectedRb.setTimestamp(Instant.parse("2019-10-01T12:29:08.855Z"));
        expectedRb.setRestoreUserDataInProgress(true);
        expectedRb.info.getCausePackages().add(new VersionedPackage("hello", 23));
        expectedRb.info.getCausePackages().add(new VersionedPackage("something", 999));
        expectedRb.info.setCommittedSessionId(45654465);

        PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
                new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(),
                false, false, new IntArray(), new SparseLongArray());
        pkgInfo1.getPendingBackups().add(59);
        pkgInfo1.getPendingBackups().add(1245);
        pkgInfo1.getPendingBackups().add(124544);
        pkgInfo1.getCeSnapshotInodes().put(546546, 345689375);
        pkgInfo1.getCeSnapshotInodes().put(2222, 81641654445L);
        pkgInfo1.getCeSnapshotInodes().put(1, -6);

        pkgInfo1.getPendingRestores().add(
                new PackageRollbackInfo.RestoreInfo(498, 32322, "wombles"));
        pkgInfo1.getPendingRestores().add(
                new PackageRollbackInfo.RestoreInfo(-895, 1, "pingu"));

        pkgInfo1.getSnapshottedUsers().add(498468432);
        pkgInfo1.getSnapshottedUsers().add(1111);
        pkgInfo1.getSnapshottedUsers().add(98464);

        PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28),
                new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(),
                false, false, new IntArray(), new SparseLongArray());
        pkgInfo2.getPendingBackups().add(5);

        pkgInfo2.getPendingRestores().add(
                new PackageRollbackInfo.RestoreInfo(18, -12, ""));

        pkgInfo2.getSnapshottedUsers().add(55);
        pkgInfo2.getSnapshottedUsers().add(79);

        expectedRb.info.getPackages().add(pkgInfo1);
        expectedRb.info.getPackages().add(pkgInfo2);

        Rollback parsedRb = RollbackStore.rollbackFromJson(
                new JSONObject(JSON_ROLLBACK_NO_EXT), expectedRb.getBackupDir());

        assertRollbacksAreEquivalent(parsedRb, expectedRb);
    }

    @Test
    public void loadFromJson() throws Exception {
        Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);
        SparseIntArray extensionVersions = new SparseIntArray();
        extensionVersions.put(5, 25);
        extensionVersions.put(30, 71);
        Rollback expectedRb = mRollbackStore.createNonStagedRollback(
                ID, USER, INSTALLER, null, extensionVersions);

        expectedRb.setTimestamp(Instant.parse("2019-10-01T12:29:08.855Z"));
        expectedRb.setRestoreUserDataInProgress(true);
@@ -246,7 +337,8 @@ public class RollbackStoreTest {

    @Test
    public void saveAndDelete() {
        Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null);
        Rollback rollback = mRollbackStore.createNonStagedRollback(
                ID, USER, INSTALLER, null, new SparseIntArray(0));

        RollbackStore.saveRollback(rollback);

@@ -294,6 +386,13 @@ public class RollbackStoreTest {

        assertThat(a.getUserId()).isEqualTo(b.getUserId());
        assertThat(a.getInstallerPackageName()).isEqualTo(b.getInstallerPackageName());

        if (a.getExtensionVersions() == null) {
            assertThat(b.getExtensionVersions()).isNull();
        } else {
            assertThat(b.getExtensionVersions().toString())
                    .isEqualTo(a.getExtensionVersions().toString());
        }
    }

    private void assertPackageRollbacksAreEquivalent(PackageRollbackInfo b, PackageRollbackInfo a) {
Loading