Merge changes from topic "staged_userdata_restore"
* changes: Followup cleanup after refactoring rollback states. Use a single list for available and committed rollbacks.
This commit is contained in:
committed by
Android (Google) Code Review
commit
1fa00a503f
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
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 == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rollbackData.restoreUserDataInProgress) {
|
||||
Log.e(TAG, "Request to restore userData for: " + packageName
|
||||
+ ", but no rollback in progress.");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (data == null) {
|
||||
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");
|
||||
if (!success) {
|
||||
// The install session was aborted, clean up the pending install.
|
||||
deleteRollback(data);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
saveRollbackData(data);
|
||||
synchronized (mLock) {
|
||||
// TODO: Have ensureRollbackDataLoadedLocked return the list of
|
||||
// available rollbacks, to hopefully avoid forgetting to call it?
|
||||
// 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();
|
||||
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
|
||||
RollbackData data = mAvailableRollbacks.get(i);
|
||||
if (data.isAvailable && getPackageRollbackInfo(data, packageName) != null) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
mRollbacks.add(data);
|
||||
}
|
||||
return null;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
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) {
|
||||
data.state = RollbackData.ROLLBACK_STATE_AVAILABLE;
|
||||
data.timestamp = Instant.now();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,38 +187,12 @@ class RollbackStore {
|
||||
json.getInt("committedSessionId"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -276,6 +235,17 @@ class RollbackStore {
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -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);
|
||||
|
||||
@@ -304,29 +274,6 @@ class RollbackStore {
|
||||
removeFile(data.backupDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user