diff options
6 files changed, 189 insertions, 18 deletions
diff --git a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java index c268ff4291e4..6c25f2849cea 100644 --- a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java +++ b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java @@ -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); } diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 885f561d2a19..9171501270d0 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -39,6 +39,7 @@ import android.os.UserManager; 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; @@ -174,6 +175,12 @@ class Rollback { private int mNumPackageSessionsWithSuccess; /** + * The extension versions supported at the time of rollback creation. May be null if not set + * at creation time. + */ + @Nullable private final SparseIntArray mExtensionVersions; + + /** * Constructs a new, empty Rollback instance. * * @param rollbackId the id of the rollback. @@ -182,9 +189,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 +206,12 @@ 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, null); } /** @@ -209,7 +219,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 +229,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 @@ -283,6 +294,14 @@ class Rollback { } /** + * Returns the extension versions that were supported at the time that the rollback was created, + * as a mapping from SdkVersion to ExtensionVersion. + */ + @Nullable SparseIntArray getExtensionVersions() { + return mExtensionVersions; + } + + /** * Returns true if the rollback is in the ENABLING state. */ boolean isEnabling() { diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 83e99b008b68..6726cc829c81 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -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. diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 7b046c1aa3a6..f186d65baaa1 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -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,35 @@ class RollbackStore { return ceSnapshotInodes; } + private static @Nullable JSONArray extensionVersionsToJson( + @Nullable SparseIntArray extensionVersions) throws JSONException { + if (extensionVersions == null) { + return null; + } + 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(@Nullable JSONArray json) + throws JSONException { + if (json == null) { + return null; + } + 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 +231,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 +242,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 +304,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 +350,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 { diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java index 64d05f07e64e..6afdfba70f58 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java @@ -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)); @@ -196,8 +233,62 @@ public class RollbackStoreTest { } @Test + public void loadFromJsonNoExtensionVersions() throws Exception { + Rollback expectedRb = + mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null, null); + + 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,7 @@ public class RollbackStoreTest { @Test public void saveAndDelete() { - Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null); + Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER, null, null); RollbackStore.saveRollback(rollback); @@ -294,6 +385,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) { diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java index 1a6c6b4011cd..e74891c7b3f8 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -317,7 +317,7 @@ public class RollbackUnitTest { public void notifySessionWithSuccess() { int[] sessionIds = new int[]{ 7777, 8888 }; Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER, - sessionIds); + sessionIds, null); // The 1st invocation returns false because not all child sessions are notified. assertThat(rollback.notifySessionWithSuccess()).isFalse(); // The 2nd invocation returns true because now all child sessions are notified. @@ -328,7 +328,7 @@ public class RollbackUnitTest { public void allPackagesEnabled() { int[] sessionIds = new int[]{ 7777, 8888 }; Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER, - sessionIds); + sessionIds, null); // #allPackagesEnabled returns false when 1 out of 2 packages is enabled. rollback.info.getPackages().add(newPkgInfoFor(PKG_1, 12, 10, false)); assertThat(rollback.allPackagesEnabled()).isFalse(); |