diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 1cf07cb47f2f6..8b79c3ff66c6e 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -21,6 +21,7 @@ import static com.android.server.rollback.RollbackManagerServiceImpl.sendFailure import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.IntentSender; @@ -33,7 +34,9 @@ import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; import android.os.Binder; import android.os.ParcelFileDescriptor; +import android.os.UserHandle; import android.os.UserManager; +import android.text.TextUtils; import android.util.IntArray; import android.util.Slog; import android.util.SparseLongArray; @@ -141,19 +144,38 @@ class Rollback { */ private final Object mLock = new Object(); + /** + * The user that performed the install with rollback enabled. + */ + public final int mUserId; + + /** + * The installer package name from the install session that enabled the rollback. May be null if + * that session did not set this value. + * + * If this is an empty string then the installer package name will be resolved by + * PackageManager. + */ + @Nullable public final String mInstallerPackageName; + /** * Constructs a new, empty Rollback instance. * * @param rollbackId the id of the rollback. * @param backupDir the directory where the rollback data is stored. * @param stagedSessionId the session id if this is a staged rollback, -1 otherwise. + * @param userId the user that performed the install with rollback enabled. + * @param installerPackageName the installer package name from the original install session. */ - Rollback(int rollbackId, File backupDir, int stagedSessionId) { + Rollback(int rollbackId, File backupDir, int stagedSessionId, int userId, + String installerPackageName) { this.info = new RollbackInfo(rollbackId, /* packages */ new ArrayList<>(), /* isStaged */ stagedSessionId != -1, /* causePackages */ new ArrayList<>(), /* committedSessionId */ -1); + mUserId = userId; + mInstallerPackageName = installerPackageName; mBackupDir = backupDir; mStagedSessionId = stagedSessionId; mState = ROLLBACK_STATE_ENABLING; @@ -164,8 +186,11 @@ class Rollback { * Constructs a pre-populated Rollback instance. */ Rollback(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId, - @RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress) { + @RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress, + int userId, String installerPackageName) { this.info = info; + mUserId = userId; + mInstallerPackageName = installerPackageName; mBackupDir = backupDir; mTimestamp = timestamp; mStagedSessionId = stagedSessionId; @@ -215,6 +240,21 @@ class Rollback { return mStagedSessionId; } + /** + * Returns the ID of the user that performed the install with rollback enabled. + */ + int getUserId() { + return mUserId; + } + + /** + * Returns the installer package name from the install session that enabled the rollback. In the + * case that this is called on a rollback from an older version, returns the empty string. + */ + @Nullable String getInstallerPackageName() { + return mInstallerPackageName; + } + /** * Returns true if the rollback is in the ENABLING state. */ @@ -360,7 +400,8 @@ class Rollback { // Get a context to use to install the downgraded version of the package. Context pkgContext; try { - pkgContext = context.createPackageContext(callerPackageName, 0); + pkgContext = context.createPackageContextAsUser(callerPackageName, 0, + UserHandle.of(mUserId)); } catch (PackageManager.NameNotFoundException e) { sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE, "Invalid callerPackageName"); @@ -385,15 +426,13 @@ class Rollback { for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) { PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); - // TODO: We can't get the installerPackageName for apex - // (b/123920130). Is it okay to ignore the installer package - // for apex? - if (!pkgRollbackInfo.isApex()) { - String installerPackageName = - pm.getInstallerPackageName(pkgRollbackInfo.getPackageName()); - if (installerPackageName != null) { - params.setInstallerPackageName(installerPackageName); - } + String installerPackageName = mInstallerPackageName; + if (TextUtils.isEmpty(mInstallerPackageName)) { + installerPackageName = pm.getInstallerPackageName( + pkgRollbackInfo.getPackageName()); + } + if (installerPackageName != null) { + params.setInstallerPackageName(installerPackageName); } params.setRequestDowngrade(true); params.setRequiredInstalledVersionCode( diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index cd44f64ada4f5..ef4c12e8145a8 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -1248,13 +1248,22 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { @GuardedBy("mLock") private NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) { int rollbackId = allocateRollbackIdLocked(); + final int userId; + if (parentSession.getUser() == UserHandle.ALL) { + userId = UserHandle.USER_SYSTEM; + } else { + userId = parentSession.getUser().getIdentifier(); + } + String installerPackageName = parentSession.getInstallerPackageName(); final Rollback rollback; int parentSessionId = parentSession.getSessionId(); if (parentSession.isStaged()) { - rollback = mRollbackStore.createStagedRollback(rollbackId, parentSessionId); + rollback = mRollbackStore.createStagedRollback(rollbackId, parentSessionId, userId, + installerPackageName); } else { - rollback = mRollbackStore.createNonStagedRollback(rollbackId); + rollback = mRollbackStore.createNonStagedRollback(rollbackId, userId, + installerPackageName); } int[] packageSessionIds; diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 425da374a5a8f..550c754428925 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -16,6 +16,8 @@ package com.android.server.rollback; +import static android.os.UserHandle.USER_SYSTEM; + import static com.android.server.rollback.Rollback.rollbackStateFromString; import android.annotation.NonNull; @@ -196,18 +198,19 @@ class RollbackStore { * Creates a new Rollback instance for a non-staged rollback with * backupDir assigned. */ - Rollback createNonStagedRollback(int rollbackId) { + Rollback createNonStagedRollback(int rollbackId, int userId, String installerPackageName) { File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId)); - return new Rollback(rollbackId, backupDir, -1); + return new Rollback(rollbackId, backupDir, -1, userId, installerPackageName); } /** * Creates a new Rollback instance for a staged rollback with * backupDir assigned. */ - Rollback createStagedRollback(int rollbackId, int stagedSessionId) { + Rollback createStagedRollback(int rollbackId, int stagedSessionId, int userId, + String installerPackageName) { File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId)); - return new Rollback(rollbackId, backupDir, stagedSessionId); + return new Rollback(rollbackId, backupDir, stagedSessionId, userId, installerPackageName); } /** @@ -263,6 +266,8 @@ class RollbackStore { dataJson.put("state", rollback.getStateAsString()); dataJson.put("apkSessionId", rollback.getApkSessionId()); dataJson.put("restoreUserDataInProgress", rollback.isRestoreUserDataInProgress()); + dataJson.put("userId", rollback.getUserId()); + dataJson.putOpt("installerPackageName", rollback.getInstallerPackageName()); PrintWriter pw = new PrintWriter(new File(rollback.getBackupDir(), "rollback.json")); pw.println(dataJson.toString()); @@ -305,7 +310,9 @@ class RollbackStore { dataJson.getInt("stagedSessionId"), rollbackStateFromString(dataJson.getString("state")), dataJson.getInt("apkSessionId"), - dataJson.getBoolean("restoreUserDataInProgress")); + dataJson.getBoolean("restoreUserDataInProgress"), + dataJson.optInt("userId", USER_SYSTEM), + dataJson.optString("installerPackageName", "")); } private static JSONObject toJson(VersionedPackage pkg) throws JSONException { diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java index 0c5451f4ec314..a83d94001cf82 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java @@ -106,6 +106,11 @@ public class AppDataRollbackHelperTest { return createPackageRollbackInfo(packageName, new int[] {}); } + private static Rollback createRollbackForId(int rollbackId) { + return new Rollback(rollbackId, new File("/does/not/exist"), -1, + 0, "com.xyz"); + } + @Test public void testRestoreAppDataSnapshot_pendingBackupForUser() throws Exception { Installer installer = mock(Installer.class); @@ -232,18 +237,16 @@ public class AppDataRollbackHelperTest { wasRecentlyRestored.getPendingRestores().add( new RestoreInfo(73 /* userId */, 239 /* appId*/, "seInfo")); - Rollback dataWithPendingBackup = new Rollback(101, new File("/does/not/exist"), -1); + Rollback dataWithPendingBackup = createRollbackForId(101); dataWithPendingBackup.info.getPackages().add(pendingBackup); - Rollback dataWithRecentRestore = new Rollback(17239, new File("/does/not/exist"), - -1); + Rollback dataWithRecentRestore = createRollbackForId(17239); dataWithRecentRestore.info.getPackages().add(wasRecentlyRestored); - Rollback dataForDifferentUser = new Rollback(17239, new File("/does/not/exist"), - -1); + Rollback dataForDifferentUser = createRollbackForId(17239); dataForDifferentUser.info.getPackages().add(ignoredInfo); - Rollback dataForRestore = new Rollback(17239, new File("/does/not/exist"), -1); + Rollback dataForRestore = createRollbackForId(17239); dataForRestore.info.getPackages().add(pendingRestore); dataForRestore.info.getPackages().add(wasRecentlyRestored); 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 ee3b15a97f26f..aec489cd1ab4c 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java @@ -43,6 +43,8 @@ import java.util.Objects; public class RollbackStoreTest { private static final int ID = 123; + private static final int USER = 0; + private static final String INSTALLER = "some.installer"; private static final Correspondence VER_PKG_CORR = new Correspondence() { @@ -97,7 +99,8 @@ public class RollbackStoreTest { + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}]," + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z'," + "'stagedSessionId':-1,'state':'enabling','apkSessionId':-1," - + "'restoreUserDataInProgress':true}"; + + "'restoreUserDataInProgress':true, 'userId':0," + + "'installerPackageName':'some.installer'}"; @Rule public TemporaryFolder mFolder = new TemporaryFolder(); @@ -115,7 +118,7 @@ public class RollbackStoreTest { @Test public void createNonStaged() { - Rollback rollback = mRollbackStore.createNonStagedRollback(ID); + Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER); assertThat(rollback.getBackupDir().getAbsolutePath()) .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID); @@ -128,7 +131,7 @@ public class RollbackStoreTest { @Test public void createStaged() { - Rollback rollback = mRollbackStore.createStagedRollback(ID, 897); + Rollback rollback = mRollbackStore.createStagedRollback(ID, 897, USER, INSTALLER); assertThat(rollback.getBackupDir().getAbsolutePath()) .isEqualTo(mFolder.getRoot().getAbsolutePath() + "/" + ID); @@ -143,7 +146,7 @@ public class RollbackStoreTest { @Test public void saveAndLoadRollback() { - Rollback origRb = mRollbackStore.createNonStagedRollback(ID); + Rollback origRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER); origRb.setRestoreUserDataInProgress(true); origRb.info.getCausePackages().add(new VersionedPackage("com.made.up", 2)); @@ -193,7 +196,7 @@ public class RollbackStoreTest { @Test public void loadFromJson() throws Exception { - Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID); + Rollback expectedRb = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER); expectedRb.setTimestamp(Instant.parse("2019-10-01T12:29:08.855Z")); expectedRb.setRestoreUserDataInProgress(true); @@ -242,7 +245,7 @@ public class RollbackStoreTest { @Test public void saveAndDelete() { - Rollback rollback = mRollbackStore.createNonStagedRollback(ID); + Rollback rollback = mRollbackStore.createNonStagedRollback(ID, USER, INSTALLER); RollbackStore.saveRollback(rollback); @@ -287,6 +290,9 @@ public class RollbackStoreTest { assertPackageRollbacksAreEquivalent( b.info.getPackages().get(i), a.info.getPackages().get(i)); } + + assertThat(a.getUserId()).isEqualTo(b.getUserId()); + assertThat(a.getInstallerPackageName()).isEqualTo(b.getInstallerPackageName()); } 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 151b6e2890bba..e368d634b9684 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -52,6 +52,8 @@ public class RollbackUnitTest { private static final String PKG_2 = "test.testpackage.pkg2"; private static final String PKG_3 = "com.blah.hello.three"; private static final String PKG_4 = "com.something.4pack"; + private static final int USER = 0; + private static final String INSTALLER = "some.installer"; @Mock private AppDataRollbackHelper mMockDataHelper; @@ -66,7 +68,7 @@ public class RollbackUnitTest { int sessionId = 567; File file = new File("/test/testing"); - Rollback rollback = new Rollback(rollbackId, file, sessionId); + Rollback rollback = new Rollback(rollbackId, file, sessionId, USER, INSTALLER); assertThat(rollback.isEnabling()).isTrue(); assertThat(rollback.getBackupDir().getAbsolutePath()).isEqualTo("/test/testing"); @@ -79,7 +81,7 @@ public class RollbackUnitTest { int rollbackId = 123; File file = new File("/test/testing"); - Rollback rollback = new Rollback(rollbackId, file, -1); + Rollback rollback = new Rollback(rollbackId, file, -1, USER, INSTALLER); assertThat(rollback.isEnabling()).isTrue(); assertThat(rollback.getBackupDir().getAbsolutePath()).isEqualTo("/test/testing"); @@ -88,7 +90,8 @@ public class RollbackUnitTest { @Test public void rollbackMadeAvailable() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, + INSTALLER); assertThat(rollback.isEnabling()).isTrue(); assertThat(rollback.isAvailable()).isFalse(); @@ -106,7 +109,7 @@ public class RollbackUnitTest { @Test public void deletedRollbackCannotBeMadeAvailable() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); rollback.delete(mMockDataHelper); @@ -120,7 +123,7 @@ public class RollbackUnitTest { @Test public void getPackageNamesAllAndJustApex() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 11, true); PackageRollbackInfo pkgInfo3 = newPkgInfoFor(PKG_3, 19, 1, false); @@ -134,7 +137,7 @@ public class RollbackUnitTest { @Test public void includesPackagesAfterEnable() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); PackageRollbackInfo pkgInfo3 = newPkgInfoFor(PKG_3, 157, 156, false); @@ -162,7 +165,7 @@ public class RollbackUnitTest { @Test public void snapshotWhenEnabling() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); @@ -180,7 +183,7 @@ public class RollbackUnitTest { @Test public void snapshotWhenAvailable() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); @@ -201,7 +204,7 @@ public class RollbackUnitTest { @Test public void snapshotWhenDeleted() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); @@ -222,7 +225,7 @@ public class RollbackUnitTest { @Test public void snapshotThenDelete() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); @@ -242,7 +245,7 @@ public class RollbackUnitTest { @Test public void restoreUserDataDoesNothingIfNotInProgress() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); @@ -257,7 +260,7 @@ public class RollbackUnitTest { @Test public void restoreUserDataDoesNothingIfPackageNotFound() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); @@ -273,7 +276,7 @@ public class RollbackUnitTest { @Test public void restoreUserDataRestoresIfInProgressAndPackageFound() { - Rollback rollback = new Rollback(123, new File("/test/testing"), -1); + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index ed8f272874e15..599ee56afdb38 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -33,6 +33,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; +import android.os.UserManager; import android.provider.DeviceConfig; import android.util.Log; @@ -52,7 +53,9 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * Test system Rollback APIs. @@ -96,7 +99,10 @@ public class RollbackTest { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.MANAGE_ROLLBACKS); + Manifest.permission.MANAGE_ROLLBACKS, + Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS); // Register a broadcast receiver for notification when the // rollback has been committed. @@ -106,7 +112,6 @@ public class RollbackTest { // Uninstall TestApp.A Uninstall.packages(TestApp.A); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - // TODO: There is currently a race condition between when the app is // uninstalled and when rollback manager deletes the rollback. Fix it // so that's not the case! @@ -149,6 +154,12 @@ public class RollbackTest { RollbackUtils.rollback(available.getRollbackId()); assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + UserManager um = (UserManager) context.getSystemService(context.USER_SERVICE); + List userIds = um.getUsers(true) + .stream().map(user -> user.id).collect(Collectors.toList()); + assertThat(InstallUtils.isOnlyInstalledForUser(TestApp.A, + context.getUserId(), userIds)).isTrue(); + // Verify we received a broadcast for the rollback. // TODO: Race condition between the timeout and when the broadcast is // received could lead to test flakiness.