summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/test-current.txt14
-rw-r--r--core/java/android/app/role/OnRoleHoldersChangedListener.java2
-rw-r--r--core/java/android/app/role/RoleManager.java8
-rw-r--r--core/java/android/content/Intent.java2
-rw-r--r--core/java/android/content/rollback/RollbackManager.java3
-rw-r--r--services/core/java/com/android/server/rollback/AppDataRollbackHelper.java45
-rw-r--r--services/core/java/com/android/server/rollback/RollbackData.java50
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java489
-rw-r--r--services/core/java/com/android/server/rollback/RollbackStore.java142
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java23
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java51
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java53
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java3
13 files changed, 413 insertions, 472 deletions
diff --git a/api/test-current.txt b/api/test-current.txt
index 9a2d503d6d52..e38ebe61ddec 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -474,12 +474,22 @@ package android.app.prediction {
package android.app.role {
+ public interface OnRoleHoldersChangedListener {
+ method public void onRoleHoldersChanged(@NonNull String, @NonNull android.os.UserHandle);
+ }
+
public final class RoleManager {
+ method @RequiresPermission("android.permission.OBSERVE_ROLE_HOLDERS") public void addOnRoleHoldersChangedListenerAsUser(@NonNull java.util.concurrent.Executor, @NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
+ method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String);
method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
+ method @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String);
method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
+ method @RequiresPermission("android.permission.OBSERVE_ROLE_HOLDERS") public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
+ method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
+ method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
}
public interface RoleManagerCallback {
@@ -562,6 +572,10 @@ package android.content {
method public android.view.Display getDisplay();
}
+ public class Intent implements java.lang.Cloneable android.os.Parcelable {
+ field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
+ }
+
}
package android.content.pm {
diff --git a/core/java/android/app/role/OnRoleHoldersChangedListener.java b/core/java/android/app/role/OnRoleHoldersChangedListener.java
index 5958debc86dd..d6f76794da20 100644
--- a/core/java/android/app/role/OnRoleHoldersChangedListener.java
+++ b/core/java/android/app/role/OnRoleHoldersChangedListener.java
@@ -18,6 +18,7 @@ package android.app.role;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.UserHandle;
/**
@@ -26,6 +27,7 @@ import android.os.UserHandle;
* @hide
*/
@SystemApi
+@TestApi
public interface OnRoleHoldersChangedListener {
/**
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 7ec21f637e0c..c665cb238028 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -467,6 +467,7 @@ public final class RoleManager {
*/
@RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS)
@SystemApi
+ @TestApi
public void addOnRoleHoldersChangedListenerAsUser(@CallbackExecutor @NonNull Executor executor,
@NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
Preconditions.checkNotNull(executor, "executor cannot be null");
@@ -512,6 +513,7 @@ public final class RoleManager {
*/
@RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS)
@SystemApi
+ @TestApi
public void removeOnRoleHoldersChangedListenerAsUser(
@NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
Preconditions.checkNotNull(listener, "listener cannot be null");
@@ -553,6 +555,7 @@ public final class RoleManager {
*/
@RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
@SystemApi
+ @TestApi
public void setRoleNamesFromController(@NonNull List<String> roleNames) {
Preconditions.checkNotNull(roleNames, "roleNames cannot be null");
try {
@@ -583,6 +586,7 @@ public final class RoleManager {
*/
@RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
@SystemApi
+ @TestApi
public boolean addRoleHolderFromController(@NonNull String roleName,
@NonNull String packageName) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
@@ -615,6 +619,7 @@ public final class RoleManager {
*/
@RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
@SystemApi
+ @TestApi
public boolean removeRoleHolderFromController(@NonNull String roleName,
@NonNull String packageName) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
@@ -634,9 +639,10 @@ public final class RoleManager {
*
* @hide
*/
+ @NonNull
@RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
@SystemApi
- @NonNull
+ @TestApi
public List<String> getHeldRolesFromController(@NonNull String packageName) {
Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
try {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 0589f43aaa13..d3b8e29d7399 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -27,6 +27,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -1902,6 +1903,7 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
@SystemApi
+ @TestApi
public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
/**
diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java
index 2788f821e9f5..c043491e599b 100644
--- a/core/java/android/content/rollback/RollbackManager.java
+++ b/core/java/android/content/rollback/RollbackManager.java
@@ -187,7 +187,8 @@ public final class RollbackManager {
/**
* Expire the rollback data for a given package.
* This API is meant to facilitate testing of rollback logic for
- * expiring rollback data.
+ * expiring rollback data. Removes rollback data for available and
+ * recently committed rollbacks that contain the given package.
*
* @param packageName the name of the package to expire data for.
* @throws SecurityException if the caller does not have the
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index 36f18f05c23e..56e1d080a988 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -18,7 +18,6 @@ package com.android.server.rollback;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
-import android.content.rollback.RollbackInfo;
import android.os.storage.StorageManager;
import android.util.IntArray;
import android.util.Log;
@@ -30,9 +29,11 @@ import com.android.server.pm.Installer.InstallerException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Encapsulates the logic for initiating userdata snapshots and rollbacks via installd.
@@ -153,7 +154,7 @@ public class AppDataRollbackHelper {
}
/**
- * Computes the list of pending backups for {@code userId} given lists of available rollbacks.
+ * Computes the list of pending backups for {@code userId} given lists of rollbacks.
* Packages pending backup for the given user are added to {@code pendingBackupPackages} along
* with their corresponding {@code PackageRollbackInfo}.
*
@@ -162,10 +163,10 @@ public class AppDataRollbackHelper {
*/
private static List<RollbackData> computePendingBackups(int userId,
Map<String, PackageRollbackInfo> pendingBackupPackages,
- List<RollbackData> availableRollbacks) {
+ List<RollbackData> rollbacks) {
List<RollbackData> rd = new ArrayList<>();
- for (RollbackData data : availableRollbacks) {
+ for (RollbackData data : rollbacks) {
for (PackageRollbackInfo info : data.info.getPackages()) {
final IntArray pendingBackupUsers = info.getPendingBackups();
if (pendingBackupUsers != null) {
@@ -183,20 +184,20 @@ public class AppDataRollbackHelper {
}
/**
- * Computes the list of pending restores for {@code userId} given lists of recent rollbacks.
+ * Computes the list of pending restores for {@code userId} given lists of rollbacks.
* Packages pending restore are added to {@code pendingRestores} along with their corresponding
* {@code PackageRollbackInfo}.
*
- * @return the list of {@code RollbackInfo} that has pending restores. Note that some of the
+ * @return the list of {@code RollbackData} that has pending restores. Note that some of the
* restores won't be performed, because they might be counteracted by pending backups.
*/
- private static List<RollbackInfo> computePendingRestores(int userId,
+ private static List<RollbackData> computePendingRestores(int userId,
Map<String, PackageRollbackInfo> pendingRestorePackages,
- List<RollbackInfo> recentRollbacks) {
- List<RollbackInfo> rd = new ArrayList<>();
+ List<RollbackData> rollbacks) {
+ List<RollbackData> rd = new ArrayList<>();
- for (RollbackInfo data : recentRollbacks) {
- for (PackageRollbackInfo info : data.getPackages()) {
+ for (RollbackData data : rollbacks) {
+ for (PackageRollbackInfo info : data.info.getPackages()) {
final RestoreInfo ri = info.getRestoreInfo(userId);
if (ri != null) {
pendingRestorePackages.put(info.getPackageName(), info);
@@ -215,18 +216,18 @@ public class AppDataRollbackHelper {
* backups updates corresponding {@code changedRollbackData} with a mapping from {@code userId}
* to a inode of theirs CE user data snapshot.
*
- * @return a list {@code RollbackData} that have been changed and should be stored on disk.
+ * @return the set of {@code RollbackData} that have been changed and should be stored on disk.
*/
- public List<RollbackData> commitPendingBackupAndRestoreForUser(int userId,
- List<RollbackData> availableRollbacks, List<RollbackInfo> recentlyExecutedRollbacks) {
+ public Set<RollbackData> commitPendingBackupAndRestoreForUser(int userId,
+ List<RollbackData> rollbacks) {
final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>();
final List<RollbackData> pendingBackups = computePendingBackups(userId,
- pendingBackupPackages, availableRollbacks);
+ pendingBackupPackages, rollbacks);
final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>();
- final List<RollbackInfo> pendingRestores = computePendingRestores(userId,
- pendingRestorePackages, recentlyExecutedRollbacks);
+ final List<RollbackData> pendingRestores = computePendingRestores(userId,
+ pendingRestorePackages, rollbacks);
// First remove unnecessary backups, i.e. when user did not unlock their phone between the
// request to backup data and the request to restore it.
@@ -266,13 +267,13 @@ public class AppDataRollbackHelper {
}
if (!pendingRestorePackages.isEmpty()) {
- for (RollbackInfo data : pendingRestores) {
- for (PackageRollbackInfo info : data.getPackages()) {
+ for (RollbackData data : pendingRestores) {
+ for (PackageRollbackInfo info : data.info.getPackages()) {
final RestoreInfo ri = info.getRestoreInfo(userId);
if (ri != null) {
try {
mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
- ri.seInfo, userId, data.getRollbackId(),
+ ri.seInfo, userId, data.info.getRollbackId(),
Installer.FLAG_STORAGE_CE);
info.removeRestoreInfo(ri);
} catch (InstallerException ie) {
@@ -284,7 +285,9 @@ public class AppDataRollbackHelper {
}
}
- return pendingBackups;
+ final Set<RollbackData> changed = new HashSet<>(pendingBackups);
+ changed.addAll(pendingRestores);
+ return changed;
}
/**
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index 655bf4ab57a7..8a95877be71d 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -16,9 +16,13 @@
package com.android.server.rollback;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.content.rollback.RollbackInfo;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
import java.util.ArrayList;
@@ -27,6 +31,30 @@ import java.util.ArrayList;
* packages.
*/
class RollbackData {
+ @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
+ ROLLBACK_STATE_ENABLING,
+ ROLLBACK_STATE_AVAILABLE,
+ ROLLBACK_STATE_COMMITTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RollbackState {}
+
+ /**
+ * The rollback is in the process of being enabled. It is not yet
+ * available for use.
+ */
+ static final int ROLLBACK_STATE_ENABLING = 0;
+
+ /**
+ * The rollback is currently available.
+ */
+ static final int ROLLBACK_STATE_AVAILABLE = 1;
+
+ /**
+ * The rollback has been committed.
+ */
+ static final int ROLLBACK_STATE_COMMITTED = 3;
+
/**
* The rollback info for this rollback.
*/
@@ -40,22 +68,23 @@ class RollbackData {
/**
* The time when the upgrade occurred, for purposes of expiring
* rollback data.
+ *
+ * The timestamp is not applicable for all rollback states, but we make
+ * sure to keep it non-null to avoid potential errors there.
*/
- public Instant timestamp;
+ public @NonNull Instant timestamp;
/**
* The session ID for the staged session if this rollback data represents a staged session,
* {@code -1} otherwise.
*/
- public int stagedSessionId;
+ public final int stagedSessionId;
/**
- * A flag to indicate whether the rollback should be considered available
- * for use. This will always be true for rollbacks of non-staged sessions.
- * For rollbacks of staged sessions, this is not set to true until after
- * the staged session has been applied.
+ * The current state of the rollback.
+ * ENABLING, AVAILABLE, or COMMITTED.
*/
- public boolean isAvailable;
+ public @RollbackState int state;
/**
* The id of the post-reboot apk session for a staged install, if any.
@@ -85,19 +114,20 @@ class RollbackData {
/* committedSessionId */ -1);
this.backupDir = backupDir;
this.stagedSessionId = stagedSessionId;
- this.isAvailable = (stagedSessionId == -1);
+ this.state = ROLLBACK_STATE_ENABLING;
+ this.timestamp = Instant.now();
}
/**
* Constructs a RollbackData instance with full rollback data information.
*/
RollbackData(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
- boolean isAvailable, int apkSessionId, boolean restoreUserDataInProgress) {
+ @RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress) {
this.info = info;
this.backupDir = backupDir;
this.timestamp = timestamp;
this.stagedSessionId = stagedSessionId;
- this.isAvailable = isAvailable;
+ this.state = state;
this.apkSessionId = apkSessionId;
this.restoreUserDataInProgress = restoreUserDataInProgress;
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 52d441255da6..f52f3a3627ad 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -64,6 +64,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -75,7 +76,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
private static final String TAG = "RollbackManager";
// Rollbacks expire after 48 hours.
- // TODO: How to test rollback expiration works properly?
private static final long DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS =
TimeUnit.HOURS.toMillis(48);
@@ -106,15 +106,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
@GuardedBy("mLock")
private final Map<Integer, Integer> mChildSessions = new HashMap<>();
- // Package rollback data available to be used for rolling back a package.
+ // The list of all rollbacks, including available and committed rollbacks.
// This list is null until the rollback data has been loaded.
@GuardedBy("mLock")
- private List<RollbackData> mAvailableRollbacks;
-
- // The list of recently executed rollbacks.
- // This list is null until the rollback data has been loaded.
- @GuardedBy("mLock")
- private List<RollbackInfo> mRecentlyExecutedRollbacks;
+ private List<RollbackData> mRollbacks;
private final RollbackStore mRollbackStore;
@@ -176,17 +171,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
}, filter, null, getHandler());
- // NOTE: A new intent filter is being created here because this broadcast
- // doesn't use a data scheme ("package") like above.
- IntentFilter sessionUpdatedFilter = new IntentFilter();
- sessionUpdatedFilter.addAction(PackageInstaller.ACTION_SESSION_UPDATED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- onStagedSessionUpdated(intent);
- }
- }, sessionUpdatedFilter, null, getHandler());
-
IntentFilter enableRollbackFilter = new IntentFilter();
enableRollbackFilter.addAction(Intent.ACTION_PACKAGE_ENABLE_ROLLBACK);
try {
@@ -240,9 +224,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
List<RollbackInfo> rollbacks = new ArrayList<>();
- for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
- RollbackData data = mAvailableRollbacks.get(i);
- if (data.isAvailable) {
+ for (int i = 0; i < mRollbacks.size(); ++i) {
+ RollbackData data = mRollbacks.get(i);
+ if (data.state == RollbackData.ROLLBACK_STATE_AVAILABLE) {
rollbacks.add(data.info);
}
}
@@ -258,7 +242,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- List<RollbackInfo> rollbacks = new ArrayList<>(mRecentlyExecutedRollbacks);
+ List<RollbackInfo> rollbacks = new ArrayList<>();
+ for (int i = 0; i < mRollbacks.size(); ++i) {
+ RollbackData data = mRollbacks.get(i);
+ if (data.state == RollbackData.ROLLBACK_STATE_COMMITTED) {
+ rollbacks.add(data.info);
+ }
+ }
return new ParceledListSlice<>(rollbacks);
}
}
@@ -290,21 +280,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
+ Iterator<RollbackData> iter = mRollbacks.iterator();
while (iter.hasNext()) {
RollbackData data = iter.next();
-
data.timestamp = data.timestamp.plusMillis(timeDifference);
- try {
- mRollbackStore.saveRollbackData(data);
- } catch (IOException ioe) {
- // TODO: figure out the right way to deal with this, especially if
- // it fails for some data and succeeds for others.
- Log.e(TAG, "Unable to save rollback info for : "
- + data.info.getRollbackId(), ioe);
- }
+ saveRollbackData(data);
}
-
}
}
};
@@ -328,18 +309,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
Log.i(TAG, "Initiating rollback");
RollbackData data = getRollbackForId(rollbackId);
- if (data == null) {
+ if (data == null || data.state != RollbackData.ROLLBACK_STATE_AVAILABLE) {
sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
"Rollback unavailable");
return;
}
- if (data.restoreUserDataInProgress) {
- sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
- "Rollback for package is already in progress.");
- return;
- }
-
// Verify the RollbackData is up to date with what's installed on
// device.
// TODO: We assume that between now and the time we commit the
@@ -438,13 +413,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
final LocalIntentReceiver receiver = new LocalIntentReceiver(
(Intent result) -> {
getHandler().post(() -> {
- // We've now completed the rollback, so we mark it as no longer in
- // progress.
- data.restoreUserDataInProgress = false;
int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status != PackageInstaller.STATUS_SUCCESS) {
+ // Committing the rollback failed, but we
+ // still have all the info we need to try
+ // rolling back again, so restore the rollback
+ // state to how it was before we tried
+ // committing.
+ // TODO: Should we just kill this rollback if
+ // commit failed? Why would we expect commit
+ // not to fail again?
+ synchronized (mLock) {
+ // TODO: Could this cause a rollback to be
+ // resurrected if it should otherwise have
+ // expired by now?
+ data.state = RollbackData.ROLLBACK_STATE_AVAILABLE;
+ data.restoreUserDataInProgress = false;
+ }
sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_INSTALL,
"Rollback downgrade install failed: "
+ result.getStringExtra(
@@ -452,9 +439,19 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
return;
}
- data.info.setCommittedSessionId(parentSessionId);
- data.info.getCausePackages().addAll(causePackages);
- addRecentlyExecutedRollback(data.info);
+ synchronized (mLock) {
+ if (!data.isStaged()) {
+ // All calls to restoreUserData should have
+ // completed by now for a non-staged install.
+ data.restoreUserDataInProgress = false;
+ }
+
+ data.info.setCommittedSessionId(parentSessionId);
+ data.info.getCausePackages().addAll(causePackages);
+ }
+ mRollbackStore.deletePackageCodePaths(data);
+ saveRollbackData(data);
+
sendSuccess(statusReceiver);
Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
@@ -468,7 +465,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
);
- data.restoreUserDataInProgress = true;
+ synchronized (mLock) {
+ data.state = RollbackData.ROLLBACK_STATE_COMMITTED;
+ data.restoreUserDataInProgress = true;
+ }
parentSession.commit(receiver.getIntentSender());
} catch (IOException e) {
Log.e(TAG, "Rollback failed", e);
@@ -485,8 +485,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
"reloadPersistedData");
synchronized (mLock) {
- mAvailableRollbacks = null;
- mRecentlyExecutedRollbacks = null;
+ mRollbacks = null;
}
getHandler().post(() -> {
updateRollbackLifetimeDurationInMillis();
@@ -499,14 +498,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ROLLBACKS,
"expireRollbackForPackage");
-
- // TODO: Should this take a package version number in addition to
- // package name? For now, just remove all rollbacks matching the
- // package name. This method is only currently used to facilitate
- // testing anyway.
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
+ Iterator<RollbackData> iter = mRollbacks.iterator();
while (iter.hasNext()) {
RollbackData data = iter.next();
for (PackageRollbackInfo info : data.info.getPackages()) {
@@ -522,29 +516,16 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
void onUnlockUser(int userId) {
getHandler().post(() -> {
- final List<RollbackData> availableRollbacks;
- final List<RollbackInfo> recentlyExecutedRollbacks;
+ final List<RollbackData> rollbacks;
synchronized (mLock) {
- ensureRollbackDataLoadedLocked();
- availableRollbacks = new ArrayList<>(mAvailableRollbacks);
- recentlyExecutedRollbacks = new ArrayList<>(mRecentlyExecutedRollbacks);
+ rollbacks = new ArrayList<>(mRollbacks);
}
- final List<RollbackData> changed =
- mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId,
- availableRollbacks, recentlyExecutedRollbacks);
+ final Set<RollbackData> changed =
+ mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, rollbacks);
for (RollbackData rd : changed) {
- try {
- mRollbackStore.saveRollbackData(rd);
- } catch (IOException ioe) {
- Log.e(TAG, "Unable to save rollback info for : "
- + rd.info.getRollbackId(), ioe);
- }
- }
-
- synchronized (mLock) {
- mRollbackStore.saveRecentlyExecutedRollbacks(mRecentlyExecutedRollbacks);
+ saveRollbackData(rd);
}
});
}
@@ -569,42 +550,55 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
scheduleExpiration(0);
getHandler().post(() -> {
- // Check to see if any staged sessions with rollback enabled have
- // been applied.
- List<RollbackData> staged = new ArrayList<>();
+ // Check to see if any rollback-enabled staged sessions or staged
+ // rollback sessions been applied.
+ List<RollbackData> enabling = new ArrayList<>();
+ List<RollbackData> restoreInProgress = new ArrayList<>();
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- for (RollbackData data : mAvailableRollbacks) {
- if (!data.isAvailable && data.isStaged()) {
- staged.add(data);
+ for (RollbackData data : mRollbacks) {
+ if (data.isStaged()) {
+ if (data.state == RollbackData.ROLLBACK_STATE_ENABLING) {
+ enabling.add(data);
+ } else if (data.restoreUserDataInProgress) {
+ restoreInProgress.add(data);
+ }
}
}
}
- for (RollbackData data : staged) {
+ for (RollbackData data : enabling) {
PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo session = installer.getSessionInfo(
data.stagedSessionId);
+ // TODO: What if session is null?
if (session != null) {
if (session.isStagedSessionApplied()) {
- synchronized (mLock) {
- data.isAvailable = true;
- }
- try {
- mRollbackStore.saveRollbackData(data);
- } catch (IOException ioe) {
- Log.e(TAG, "Unable to save rollback info for : "
- + data.info.getRollbackId(), ioe);
- }
+ makeRollbackAvailable(data);
} else if (session.isStagedSessionFailed()) {
// TODO: Do we need to remove this from
- // mAvailableRollbacks, or is it okay to leave as
+ // mRollbacks, or is it okay to leave as
// unavailable until the next reboot when it will go
// away on its own?
deleteRollback(data);
}
}
}
+
+ for (RollbackData data : restoreInProgress) {
+ PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
+ PackageInstaller.SessionInfo session = installer.getSessionInfo(
+ data.stagedSessionId);
+ // TODO: What if session is null?
+ if (session != null) {
+ if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
+ synchronized (mLock) {
+ data.restoreUserDataInProgress = false;
+ }
+ saveRollbackData(data);
+ }
+ }
+ }
});
}
@@ -621,12 +615,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
/**
* Load rollback data from storage if it has not already been loaded.
- * After calling this function, mAvailableRollbacks and
- * mRecentlyExecutedRollbacks will be non-null.
+ * After calling this function, mRollbacks will be non-null.
*/
@GuardedBy("mLock")
private void ensureRollbackDataLoadedLocked() {
- if (mAvailableRollbacks == null) {
+ if (mRollbacks == null) {
loadAllRollbackDataLocked();
}
}
@@ -639,15 +632,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
*/
@GuardedBy("mLock")
private void loadAllRollbackDataLocked() {
- mAvailableRollbacks = mRollbackStore.loadAvailableRollbacks();
- for (RollbackData data : mAvailableRollbacks) {
+ mRollbacks = mRollbackStore.loadAllRollbackData();
+ for (RollbackData data : mRollbacks) {
mAllocatedRollbackIds.put(data.info.getRollbackId(), true);
}
-
- mRecentlyExecutedRollbacks = mRollbackStore.loadRecentlyExecutedRollbacks();
- for (RollbackInfo info : mRecentlyExecutedRollbacks) {
- mAllocatedRollbackIds.put(info.getRollbackId(), true);
- }
}
/**
@@ -662,17 +650,21 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
+ Iterator<RollbackData> iter = mRollbacks.iterator();
while (iter.hasNext()) {
RollbackData data = iter.next();
- for (PackageRollbackInfo info : data.info.getPackages()) {
- if (info.getPackageName().equals(packageName)
- && !packageVersionsEqual(
- info.getVersionRolledBackFrom(),
- installedVersion)) {
- iter.remove();
- deleteRollback(data);
- break;
+ // TODO: Should we remove rollbacks in the ENABLING state here?
+ if (data.state == RollbackData.ROLLBACK_STATE_AVAILABLE
+ || data.state == RollbackData.ROLLBACK_STATE_ENABLING) {
+ for (PackageRollbackInfo info : data.info.getPackages()) {
+ if (info.getPackageName().equals(packageName)
+ && !packageVersionsEqual(
+ info.getVersionRolledBackFrom(),
+ installedVersion)) {
+ iter.remove();
+ deleteRollback(data);
+ break;
+ }
}
}
}
@@ -685,53 +677,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
*/
private void onPackageFullyRemoved(String packageName) {
expireRollbackForPackage(packageName);
-
- synchronized (mLock) {
- ensureRollbackDataLoadedLocked();
- Iterator<RollbackInfo> iter = mRecentlyExecutedRollbacks.iterator();
- boolean changed = false;
- while (iter.hasNext()) {
- RollbackInfo rollback = iter.next();
- for (PackageRollbackInfo info : rollback.getPackages()) {
- if (packageName.equals(info.getPackageName())) {
- iter.remove();
- changed = true;
- break;
- }
- }
- }
-
- if (changed) {
- mRollbackStore.saveRecentlyExecutedRollbacks(mRecentlyExecutedRollbacks);
- }
- }
- }
-
- /**
- * Records that the given package has been recently rolled back.
- */
- private void addRecentlyExecutedRollback(RollbackInfo rollback) {
- // TODO: if the list of rollbacks gets too big, trim it to only those
- // that are necessary to keep track of.
- synchronized (mLock) {
- ensureRollbackDataLoadedLocked();
-
- // This should never happen because we can't have any pending backups left after
- // a rollback has been executed. See AppDataRollbackHelper#restoreAppData where we
- // clear all pending backups at the point of restore because they're guaranteed to be
- // no-ops.
- //
- // We may, however, have one or more pending restores left to handle.
- for (PackageRollbackInfo target : rollback.getPackages()) {
- if (target.getPendingBackups().size() > 0) {
- Log.e(TAG, "No backups allowed to be pending for: " + target);
- target.getPendingBackups().clear();
- }
- }
-
- mRecentlyExecutedRollbacks.add(rollback);
- mRollbackStore.saveRecentlyExecutedRollbacks(mRecentlyExecutedRollbacks);
- }
}
/**
@@ -768,17 +713,16 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
// Check to see if anything needs expiration, and if so, expire it.
// Schedules future expiration as appropriate.
- // TODO: Handle cases where the user changes time on the device.
private void runExpiration() {
Instant now = Instant.now();
Instant oldest = null;
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
+ Iterator<RollbackData> iter = mRollbacks.iterator();
while (iter.hasNext()) {
RollbackData data = iter.next();
- if (!data.isAvailable) {
+ if (data.state != RollbackData.ROLLBACK_STATE_AVAILABLE) {
continue;
}
if (!now.isBefore(data.timestamp.plusMillis(mRollbackLifetimeDurationInMillis))) {
@@ -876,8 +820,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
RollbackData rd = null;
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
- RollbackData data = mAvailableRollbacks.get(i);
+ for (int i = 0; i < mRollbacks.size(); ++i) {
+ RollbackData data = mRollbacks.get(i);
if (data.apkSessionId == parentSessionId) {
rd = data;
break;
@@ -901,16 +845,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
if (info.getPackageName().equals(packageName)) {
info.getInstalledUsers().addAll(IntArray.wrap(installedUsers));
mAppDataRollbackHelper.snapshotAppData(rd.info.getRollbackId(), info);
- try {
- mRollbackStore.saveRollbackData(rd);
- } catch (IOException ioe) {
- // TODO: Hopefully this is okay because we will try
- // again to save the rollback when the staged session
- // is applied. Just so long as the device doesn't
- // reboot before then.
- Log.e(TAG, "Unable to save rollback info for : "
- + rd.info.getRollbackId(), ioe);
- }
+ saveRollbackData(rd);
return true;
}
}
@@ -1039,32 +974,33 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
private void restoreUserDataInternal(String packageName, int[] userIds, int appId,
long ceDataInode, String seInfo, int token) {
- final RollbackData rollbackData = getRollbackForPackage(packageName);
- if (rollbackData == null) {
- return;
+ PackageRollbackInfo info = null;
+ RollbackData rollbackData = null;
+ synchronized (mLock) {
+ ensureRollbackDataLoadedLocked();
+ for (int i = 0; i < mRollbacks.size(); ++i) {
+ RollbackData data = mRollbacks.get(i);
+ if (data.restoreUserDataInProgress) {
+ info = getPackageRollbackInfo(data, packageName);
+ if (info != null) {
+ rollbackData = data;
+ break;
+ }
+ }
+ }
}
- if (!rollbackData.restoreUserDataInProgress) {
- Log.e(TAG, "Request to restore userData for: " + packageName
- + ", but no rollback in progress.");
+ if (rollbackData == null) {
return;
}
for (int userId : userIds) {
- final PackageRollbackInfo info = getPackageRollbackInfo(rollbackData, packageName);
final boolean changedRollbackData = mAppDataRollbackHelper.restoreAppData(
rollbackData.info.getRollbackId(), info, userId, appId, seInfo);
// We've updated metadata about this rollback, so save it to flash.
if (changedRollbackData) {
- try {
- mRollbackStore.saveRollbackData(rollbackData);
- } catch (IOException ioe) {
- // TODO(narayan): What is the right thing to do here ? This isn't a fatal
- // error, since it will only result in us trying to restore data again,
- // which will be a no-op if there's no data available.
- Log.e(TAG, "Unable to save available rollback: " + packageName, ioe);
- }
+ saveRollbackData(rollbackData);
}
}
}
@@ -1108,6 +1044,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
}
+ completeEnableRollback(sessionId, true);
result.offer(true);
});
@@ -1125,8 +1062,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
RollbackData rd = null;
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
- RollbackData data = mAvailableRollbacks.get(i);
+ for (int i = 0; i < mRollbacks.size(); ++i) {
+ RollbackData data = mRollbacks.get(i);
if (data.stagedSessionId == originalSessionId) {
data.apkSessionId = apkSessionId;
rd = data;
@@ -1136,12 +1073,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
if (rd != null) {
- try {
- mRollbackStore.saveRollbackData(rd);
- } catch (IOException ioe) {
- Log.e(TAG, "Unable to save rollback info for : "
- + rd.info.getRollbackId(), ioe);
- }
+ saveRollbackData(rd);
}
});
}
@@ -1183,21 +1115,21 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
@Override
public void onFinished(int sessionId, boolean success) {
- // If sessionId refers to a staged session, we can't deal with it here since the
- // session might take an unbounded amount of time to become "ready" after the package
- // installer session is committed. In those cases, we respond to it in response to
- // a session ready broadcast.
- PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
- PackageInstaller.SessionInfo si = packageInstaller.getSessionInfo(sessionId);
- if (si != null && si.isStaged()) {
- return;
+ RollbackData rollback = completeEnableRollback(sessionId, success);
+ if (rollback != null && !rollback.isStaged()) {
+ makeRollbackAvailable(rollback);
}
-
- completeEnableRollback(sessionId, success);
}
}
- private void completeEnableRollback(int sessionId, boolean success) {
+ /**
+ * Add a rollback to the list of rollbacks.
+ * This should be called after rollback has been enabled for all packages
+ * in the rollback. It does not make the rollback available yet.
+ *
+ * @return the rollback data for a successfully enable-completed rollback.
+ */
+ private RollbackData completeEnableRollback(int sessionId, boolean success) {
RollbackData data = null;
synchronized (mLock) {
Integer parentSessionId = mChildSessions.remove(sessionId);
@@ -1208,107 +1140,71 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
data = mPendingRollbacks.remove(sessionId);
}
- if (data != null) {
- if (success) {
- try {
- data.timestamp = Instant.now();
-
- mRollbackStore.saveRollbackData(data);
- synchronized (mLock) {
- // Note: There is a small window of time between when
- // the session has been committed by the package
- // manager and when we make the rollback available
- // here. Presumably the window is small enough that
- // nobody will want to roll back the newly installed
- // package before we make the rollback available.
- // TODO: We'll lose the rollback data if the
- // device reboots between when the session is
- // committed and this point. Revisit this after
- // adding support for rollback of staged installs.
- ensureRollbackDataLoadedLocked();
- mAvailableRollbacks.add(data);
- }
- // TODO(zezeozue): Provide API to explicitly start observing instead
- // of doing this for all rollbacks. If we do this for all rollbacks,
- // should document in PackageInstaller.SessionParams#setEnableRollback
- // After enabling and commiting any rollback, observe packages and
- // prepare to rollback if packages crashes too frequently.
- List<String> packages = new ArrayList<>();
- for (int i = 0; i < data.info.getPackages().size(); i++) {
- packages.add(data.info.getPackages().get(i).getPackageName());
- }
- mPackageHealthObserver.startObservingHealth(packages,
- mRollbackLifetimeDurationInMillis);
- scheduleExpiration(mRollbackLifetimeDurationInMillis);
- } catch (IOException e) {
- Log.e(TAG, "Unable to enable rollback", e);
- deleteRollback(data);
- }
- } else {
- // The install session was aborted, clean up the pending
- // install.
- deleteRollback(data);
- }
+ if (data == null) {
+ return null;
}
- }
- private void onStagedSessionUpdated(Intent intent) {
- PackageInstaller.SessionInfo pi = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
- if (pi == null) {
- Log.e(TAG, "Missing intent extra: " + PackageInstaller.EXTRA_SESSION);
- return;
+ if (!success) {
+ // The install session was aborted, clean up the pending install.
+ deleteRollback(data);
+ return null;
}
- if (pi.isStaged()) {
- if (!pi.isStagedSessionFailed()) {
- // TODO: The session really isn't "enabled" at this point, since more work might
- // be required post reboot.
- // TODO: We need to make this case consistent with the call from onFinished.
- // Ideally, we'd call completeEnableRollback excatly once per multi-package session
- // with the parentSessionId only.
- completeEnableRollback(pi.sessionId, pi.isStagedSessionReady());
- } else {
- // TODO: Clean up the saved rollback when the session fails. This may need to be
- // unified with the case where things fail post reboot.
- }
- } else {
- Log.e(TAG, "Received onStagedSessionUpdated for: " + pi.sessionId
- + ", which isn't staged");
+ saveRollbackData(data);
+ synchronized (mLock) {
+ // Note: There is a small window of time between when
+ // the session has been committed by the package
+ // manager and when we make the rollback available
+ // here. Presumably the window is small enough that
+ // nobody will want to roll back the newly installed
+ // package before we make the rollback available.
+ // TODO: We'll lose the rollback data if the
+ // device reboots between when the session is
+ // committed and this point. Revisit this after
+ // adding support for rollback of staged installs.
+ ensureRollbackDataLoadedLocked();
+ mRollbacks.add(data);
}
+
+ return data;
}
- /*
- * Returns the RollbackData, if any, for an available rollback that would
- * roll back the given package. Note: This assumes we have at most one
- * available rollback for a given package at any one time.
- */
- private RollbackData getRollbackForPackage(String packageName) {
+ private void makeRollbackAvailable(RollbackData data) {
+ // TODO: What if the rollback has since been expired, for example due
+ // to a new package being installed. Won't this revive an expired
+ // rollback? Consider adding a ROLLBACK_STATE_EXPIRED to address this.
synchronized (mLock) {
- // TODO: Have ensureRollbackDataLoadedLocked return the list of
- // available rollbacks, to hopefully avoid forgetting to call it?
- ensureRollbackDataLoadedLocked();
- for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
- RollbackData data = mAvailableRollbacks.get(i);
- if (data.isAvailable && getPackageRollbackInfo(data, packageName) != null) {
- return data;
- }
- }
+ data.state = RollbackData.ROLLBACK_STATE_AVAILABLE;
+ data.timestamp = Instant.now();
}
- return null;
+ saveRollbackData(data);
+
+ // TODO(zezeozue): Provide API to explicitly start observing instead
+ // of doing this for all rollbacks. If we do this for all rollbacks,
+ // should document in PackageInstaller.SessionParams#setEnableRollback
+ // After enabling and commiting any rollback, observe packages and
+ // prepare to rollback if packages crashes too frequently.
+ List<String> packages = new ArrayList<>();
+ for (int i = 0; i < data.info.getPackages().size(); i++) {
+ packages.add(data.info.getPackages().get(i).getPackageName());
+ }
+ mPackageHealthObserver.startObservingHealth(packages,
+ mRollbackLifetimeDurationInMillis);
+ scheduleExpiration(mRollbackLifetimeDurationInMillis);
}
/*
- * Returns the RollbackData, if any, for an available rollback with the
- * given rollbackId.
+ * Returns the RollbackData, if any, for a rollback with the given
+ * rollbackId.
*/
private RollbackData getRollbackForId(int rollbackId) {
synchronized (mLock) {
// TODO: Have ensureRollbackDataLoadedLocked return the list of
// available rollbacks, to hopefully avoid forgetting to call it?
ensureRollbackDataLoadedLocked();
- for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
- RollbackData data = mAvailableRollbacks.get(i);
- if (data.isAvailable && data.info.getRollbackId() == rollbackId) {
+ for (int i = 0; i < mRollbacks.size(); ++i) {
+ RollbackData data = mRollbacks.get(i);
+ if (data.info.getRollbackId() == rollbackId) {
return data;
}
}
@@ -1358,4 +1254,19 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
mRollbackStore.deleteRollbackData(rollbackData);
}
+
+ /**
+ * Saves rollback data, swallowing any IOExceptions.
+ * For those times when it's not obvious what to do about the IOException.
+ * TODO: Double check we can't do a better job handling the IOException in
+ * a cases where this method is called.
+ */
+ private void saveRollbackData(RollbackData rollbackData) {
+ try {
+ mRollbackStore.saveRollbackData(rollbackData);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Unable to save rollback info for: "
+ + rollbackData.info.getRollbackId(), ioe);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index ecdb2ccd872b..4f8f6856bb6f 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -35,6 +35,7 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
+import java.text.ParseException;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
@@ -47,60 +48,44 @@ class RollbackStore {
private static final String TAG = "RollbackManager";
// Assuming the rollback data directory is /data/rollback, we use the
- // following directory structure to store persisted data for available and
- // recently executed rollbacks:
+ // following directory structure to store persisted data for rollbacks:
// /data/rollback/
- // available/
- // XXX/
- // rollback.json
- // com.package.A/
- // base.apk
- // com.package.B/
- // base.apk
- // YYY/
- // rollback.json
- // com.package.C/
- // base.apk
- // recently_executed.json
+ // XXX/
+ // rollback.json
+ // com.package.A/
+ // base.apk
+ // com.package.B/
+ // base.apk
+ // YYY/
+ // rollback.json
//
// * XXX, YYY are the rollbackIds for the corresponding rollbacks.
- // * rollback.json contains all relevant metadata for the rollback. This
- // file is not written until the rollback is made available.
+ // * rollback.json contains all relevant metadata for the rollback.
//
// TODO: Use AtomicFile for all the .json files?
private final File mRollbackDataDir;
- private final File mAvailableRollbacksDir;
- private final File mRecentlyExecutedRollbacksFile;
RollbackStore(File rollbackDataDir) {
mRollbackDataDir = rollbackDataDir;
- mAvailableRollbacksDir = new File(mRollbackDataDir, "available");
- mRecentlyExecutedRollbacksFile = new File(mRollbackDataDir, "recently_executed.json");
}
/**
- * Reads the list of available rollbacks from persistent storage.
+ * Reads the rollback data from persistent storage.
*/
- List<RollbackData> loadAvailableRollbacks() {
- List<RollbackData> availableRollbacks = new ArrayList<>();
- mAvailableRollbacksDir.mkdirs();
- for (File rollbackDir : mAvailableRollbacksDir.listFiles()) {
+ List<RollbackData> loadAllRollbackData() {
+ List<RollbackData> rollbacks = new ArrayList<>();
+ mRollbackDataDir.mkdirs();
+ for (File rollbackDir : mRollbackDataDir.listFiles()) {
if (rollbackDir.isDirectory()) {
try {
- RollbackData data = loadRollbackData(rollbackDir);
- availableRollbacks.add(data);
+ rollbacks.add(loadRollbackData(rollbackDir));
} catch (IOException e) {
- // Note: Deleting the rollbackDir here will cause pending
- // rollbacks to be deleted. This should only ever happen
- // if reloadPersistedData is called while there are
- // pending rollbacks. The reloadPersistedData method is
- // currently only for testing, so that should be okay.
Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e);
removeFile(rollbackDir);
}
}
}
- return availableRollbacks;
+ return rollbacks;
}
/**
@@ -203,37 +188,11 @@ class RollbackStore {
}
/**
- * Reads the list of recently executed rollbacks from persistent storage.
- */
- List<RollbackInfo> loadRecentlyExecutedRollbacks() {
- List<RollbackInfo> recentlyExecutedRollbacks = new ArrayList<>();
- if (mRecentlyExecutedRollbacksFile.exists()) {
- try {
- // TODO: How to cope with changes to the format of this file from
- // when RollbackStore is updated in the future?
- String jsonString = IoUtils.readFileAsString(
- mRecentlyExecutedRollbacksFile.getAbsolutePath());
- JSONObject object = new JSONObject(jsonString);
- JSONArray array = object.getJSONArray("recentlyExecuted");
- for (int i = 0; i < array.length(); ++i) {
- recentlyExecutedRollbacks.add(rollbackInfoFromJson(array.getJSONObject(i)));
- }
- } catch (IOException | JSONException e) {
- // TODO: What to do here? Surely we shouldn't just forget about
- // everything after the point of exception?
- Log.e(TAG, "Failed to read recently executed rollbacks", e);
- }
- }
-
- return recentlyExecutedRollbacks;
- }
-
- /**
* Creates a new RollbackData instance for a non-staged rollback with
* backupDir assigned.
*/
RollbackData createNonStagedRollback(int rollbackId) throws IOException {
- File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
+ File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
return new RollbackData(rollbackId, backupDir, -1);
}
@@ -243,7 +202,7 @@ class RollbackStore {
*/
RollbackData createStagedRollback(int rollbackId, int stagedSessionId)
throws IOException {
- File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
+ File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
return new RollbackData(rollbackId, backupDir, stagedSessionId);
}
@@ -277,6 +236,17 @@ class RollbackStore {
}
/**
+ * Deletes all backed up apks and apex files associated with the given
+ * rollback.
+ */
+ static void deletePackageCodePaths(RollbackData data) {
+ for (PackageRollbackInfo info : data.info.getPackages()) {
+ File targetDir = new File(data.backupDir, info.getPackageName());
+ removeFile(targetDir);
+ }
+ }
+
+ /**
* Saves the rollback data to persistent storage.
*/
void saveRollbackData(RollbackData data) throws IOException {
@@ -285,7 +255,7 @@ class RollbackStore {
dataJson.put("info", rollbackInfoToJson(data.info));
dataJson.put("timestamp", data.timestamp.toString());
dataJson.put("stagedSessionId", data.stagedSessionId);
- dataJson.put("isAvailable", data.isAvailable);
+ dataJson.put("state", rollbackStateToString(data.state));
dataJson.put("apkSessionId", data.apkSessionId);
dataJson.put("restoreUserDataInProgress", data.restoreUserDataInProgress);
@@ -305,29 +275,6 @@ class RollbackStore {
}
/**
- * Writes the list of recently executed rollbacks to storage.
- */
- void saveRecentlyExecutedRollbacks(List<RollbackInfo> recentlyExecutedRollbacks) {
- try {
- JSONObject json = new JSONObject();
- JSONArray array = new JSONArray();
- json.put("recentlyExecuted", array);
-
- for (int i = 0; i < recentlyExecutedRollbacks.size(); ++i) {
- RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
- array.put(rollbackInfoToJson(rollback));
- }
-
- PrintWriter pw = new PrintWriter(mRecentlyExecutedRollbacksFile);
- pw.println(json.toString());
- pw.close();
- } catch (IOException | JSONException e) {
- // TODO: What to do here?
- Log.e(TAG, "Failed to save recently executed rollbacks", e);
- }
- }
-
- /**
* Reads the metadata for a rollback from the given directory.
* @throws IOException in case of error reading the data.
*/
@@ -342,10 +289,10 @@ class RollbackStore {
backupDir,
Instant.parse(dataJson.getString("timestamp")),
dataJson.getInt("stagedSessionId"),
- dataJson.getBoolean("isAvailable"),
+ rollbackStateFromString(dataJson.getString("state")),
dataJson.getInt("apkSessionId"),
dataJson.getBoolean("restoreUserDataInProgress"));
- } catch (JSONException | DateTimeParseException e) {
+ } catch (JSONException | DateTimeParseException | ParseException e) {
throw new IOException(e);
}
}
@@ -444,7 +391,7 @@ class RollbackStore {
* If the file is a directory, its contents are deleted as well.
* Has no effect if the directory does not exist.
*/
- private void removeFile(File file) {
+ private static void removeFile(File file) {
if (file.isDirectory()) {
for (File child : file.listFiles()) {
removeFile(child);
@@ -454,4 +401,23 @@ class RollbackStore {
file.delete();
}
}
+
+ private static String rollbackStateToString(@RollbackData.RollbackState int state) {
+ switch (state) {
+ case RollbackData.ROLLBACK_STATE_ENABLING: return "enabling";
+ case RollbackData.ROLLBACK_STATE_AVAILABLE: return "available";
+ case RollbackData.ROLLBACK_STATE_COMMITTED: return "committed";
+ }
+ throw new AssertionError("Invalid rollback state: " + state);
+ }
+
+ private static @RollbackData.RollbackState int rollbackStateFromString(String state)
+ throws ParseException {
+ switch (state) {
+ case "enabling": return RollbackData.ROLLBACK_STATE_ENABLING;
+ case "available": return RollbackData.ROLLBACK_STATE_AVAILABLE;
+ case "committed": return RollbackData.ROLLBACK_STATE_COMMITTED;
+ }
+ throw new ParseException("Invalid rollback state: " + state, 0);
+ }
}
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 fc7ccc576abb..798605568138 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -31,7 +31,6 @@ import static org.mockito.Mockito.when;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
-import android.content.rollback.RollbackInfo;
import android.util.IntArray;
import android.util.SparseLongArray;
@@ -46,8 +45,7 @@ import org.mockito.Mockito;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
+import java.util.Set;
@RunWith(JUnit4.class)
public class AppDataRollbackHelperTest {
@@ -247,13 +245,13 @@ public class AppDataRollbackHelperTest {
-1);
dataForDifferentUser.info.getPackages().add(ignoredInfo);
- RollbackInfo rollbackInfo = new RollbackInfo(17239,
- Arrays.asList(pendingRestore, wasRecentlyRestored), false,
- new ArrayList<>(), -1);
+ RollbackData dataForRestore = new RollbackData(17239, new File("/does/not/exist"), -1);
+ dataForRestore.info.getPackages().add(pendingRestore);
+ dataForRestore.info.getPackages().add(wasRecentlyRestored);
- List<RollbackData> changed = helper.commitPendingBackupAndRestoreForUser(37,
- Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser),
- Collections.singletonList(rollbackInfo));
+ Set<RollbackData> changed = helper.commitPendingBackupAndRestoreForUser(37,
+ Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser,
+ dataForRestore));
InOrder inOrder = Mockito.inOrder(installer);
// Check that pending backup and restore for the same package mutually destroyed each other.
@@ -267,9 +265,10 @@ public class AppDataRollbackHelperTest {
assertEquals(53, pendingBackup.getCeSnapshotInodes().get(37));
// Check that changed returns correct RollbackData.
- assertEquals(2, changed.size());
- assertEquals(dataWithPendingBackup, changed.get(0));
- assertEquals(dataWithRecentRestore, changed.get(1));
+ assertEquals(3, changed.size());
+ assertTrue(changed.contains(dataWithPendingBackup));
+ assertTrue(changed.contains(dataWithRecentRestore));
+ assertTrue(changed.contains(dataForRestore));
// Check that restore was performed.
inOrder.verify(installer).restoreAppDataSnapshot(
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 7be83edd91b9..a6054e8f41d0 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -19,19 +19,17 @@ package com.android.tests.rollback;
import static com.android.tests.rollback.RollbackTestUtils.assertPackageRollbackInfoEquals;
import static com.android.tests.rollback.RollbackTestUtils.assertRollbackInfoEquals;
import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfoForPackage;
+import static com.android.tests.rollback.RollbackTestUtils.processUserData;
import android.Manifest;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.VersionedPackage;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
-import android.os.Handler;
-import android.os.HandlerThread;
import android.provider.DeviceConfig;
import android.support.test.InstrumentationRegistry;
import android.util.Log;
@@ -47,7 +45,6 @@ import org.junit.runners.JUnit4;
import java.util.Collections;
import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
@@ -427,52 +424,6 @@ public class RollbackTest {
}
}
- private static final String NO_RESPONSE = "NO RESPONSE";
-
- // Calls into the test app to process user data.
- // Asserts if the user data could not be processed or was version
- // incompatible with the previously processed user data.
- private void processUserData(String packageName) throws Exception {
- Intent intent = new Intent();
- intent.setComponent(new ComponentName(packageName,
- "com.android.tests.rollback.testapp.ProcessUserData"));
- Context context = InstrumentationRegistry.getContext();
-
- HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
- handlerThread.start();
-
- // It can sometimes take a while after rollback before the app will
- // receive this broadcast, so try a few times in a loop.
- String result = NO_RESPONSE;
- for (int i = 0; result.equals(NO_RESPONSE) && i < 5; ++i) {
- BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>();
- context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (getResultCode() == 1) {
- resultQueue.add("OK");
- } else {
- // If the test app doesn't receive the broadcast or
- // fails to set the result data, then getResultData
- // here returns the initial NO_RESPONSE data passed to
- // the sendOrderedBroadcast call.
- resultQueue.add(getResultData());
- }
- }
- }, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null);
-
- result = resultQueue.poll(10, TimeUnit.SECONDS);
- if (result == null) {
- result = "ProcessUserData broadcast timed out";
- }
- }
-
- handlerThread.quit();
- if (!"OK".equals(result)) {
- fail(result);
- }
- }
-
/**
* Test that app user data is rolled back.
*/
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index f28714c87125..2f989f316ea6 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -17,6 +17,7 @@
package com.android.tests.rollback;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -27,12 +28,15 @@ import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.support.test.InstrumentationRegistry;
import android.util.Log;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InputStream;
@@ -393,4 +397,53 @@ class RollbackTestUtils {
throw new AssertionError(e);
}
}
+
+ private static final String NO_RESPONSE = "NO RESPONSE";
+
+ /**
+ * Calls into the test app to process user data.
+ * Asserts if the user data could not be processed or was version
+ * incompatible with the previously processed user data.
+ */
+ static void processUserData(String packageName) {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(packageName,
+ "com.android.tests.rollback.testapp.ProcessUserData"));
+ Context context = InstrumentationRegistry.getContext();
+
+ HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
+ handlerThread.start();
+
+ // It can sometimes take a while after rollback before the app will
+ // receive this broadcast, so try a few times in a loop.
+ String result = NO_RESPONSE;
+ for (int i = 0; result.equals(NO_RESPONSE) && i < 5; ++i) {
+ BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>();
+ context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (getResultCode() == 1) {
+ resultQueue.add("OK");
+ } else {
+ // If the test app doesn't receive the broadcast or
+ // fails to set the result data, then getResultData
+ // here returns the initial NO_RESPONSE data passed to
+ // the sendOrderedBroadcast call.
+ resultQueue.add(getResultData());
+ }
+ }
+ }, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null);
+
+ try {
+ result = resultQueue.take();
+ } catch (InterruptedException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ handlerThread.quit();
+ if (!"OK".equals(result)) {
+ fail(result);
+ }
+ }
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index b65917b2ae5b..59ae8d9deaeb 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -84,6 +84,7 @@ public class StagedRollbackTest {
RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ RollbackTestUtils.processUserData(TEST_APP_A);
RollbackTestUtils.installStaged(true, "RollbackTestAppAv2.apk");
@@ -98,6 +99,7 @@ public class StagedRollbackTest {
@Test
public void testApkOnlyCommitRollback() throws Exception {
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ RollbackTestUtils.processUserData(TEST_APP_A);
RollbackManager rm = RollbackTestUtils.getRollbackManager();
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
@@ -129,6 +131,7 @@ public class StagedRollbackTest {
@Test
public void testApkOnlyConfirmRollback() throws Exception {
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ RollbackTestUtils.processUserData(TEST_APP_A);
RollbackManager rm = RollbackTestUtils.getRollbackManager();
RollbackInfo rollback = getUniqueRollbackInfoForPackage(