Merge "Fix restore-agent timeouts"
This commit is contained in:
committed by
Android (Google) Code Review
commit
d16d57b45e
@@ -487,8 +487,8 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
case MSG_OP_COMPLETE:
|
case MSG_OP_COMPLETE:
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
BackupRestoreTask obj = (BackupRestoreTask) msg.obj;
|
BackupRestoreTask task = (BackupRestoreTask) msg.obj;
|
||||||
obj.operationComplete();
|
task.operationComplete();
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj);
|
Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj);
|
||||||
}
|
}
|
||||||
@@ -508,9 +508,12 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
{
|
{
|
||||||
RestoreParams params = (RestoreParams)msg.obj;
|
RestoreParams params = (RestoreParams)msg.obj;
|
||||||
Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
|
Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
|
||||||
(new PerformRestoreTask(params.transport, params.observer,
|
PerformRestoreTask task = new PerformRestoreTask(
|
||||||
|
params.transport, params.observer,
|
||||||
params.token, params.pkgInfo, params.pmToken,
|
params.token, params.pkgInfo, params.pmToken,
|
||||||
params.needFullBackup, params.filterSet)).run();
|
params.needFullBackup, params.filterSet);
|
||||||
|
Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
|
||||||
|
sendMessage(restoreMsg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,7 +592,7 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
if (mActiveRestoreSession != null) {
|
if (mActiveRestoreSession != null) {
|
||||||
// Client app left the restore session dangling. We know that it
|
// Client app left the restore session dangling. We know that it
|
||||||
// can't be in the middle of an actual restore operation because
|
// can't be in the middle of an actual restore operation because
|
||||||
// those are executed serially on this same handler thread. Clean
|
// the timeout is suspended while a restore is in progress. Clean
|
||||||
// up now.
|
// up now.
|
||||||
Slog.w(TAG, "Restore session timed out; aborting");
|
Slog.w(TAG, "Restore session timed out; aborting");
|
||||||
post(mActiveRestoreSession.new EndRestoreRunnable(
|
post(mActiveRestoreSession.new EndRestoreRunnable(
|
||||||
@@ -1765,6 +1768,7 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
else {
|
else {
|
||||||
Slog.e(TAG, "Duplicate finish");
|
Slog.e(TAG, "Duplicate finish");
|
||||||
}
|
}
|
||||||
|
mFinished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3922,7 +3926,15 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PerformRestoreTask implements Runnable {
|
enum RestoreState {
|
||||||
|
INITIAL,
|
||||||
|
DOWNLOAD_DATA,
|
||||||
|
PM_METADATA,
|
||||||
|
RUNNING_QUEUE,
|
||||||
|
FINAL
|
||||||
|
}
|
||||||
|
|
||||||
|
class PerformRestoreTask implements BackupRestoreTask {
|
||||||
private IBackupTransport mTransport;
|
private IBackupTransport mTransport;
|
||||||
private IRestoreObserver mObserver;
|
private IRestoreObserver mObserver;
|
||||||
private long mToken;
|
private long mToken;
|
||||||
@@ -3931,6 +3943,21 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
private int mPmToken;
|
private int mPmToken;
|
||||||
private boolean mNeedFullBackup;
|
private boolean mNeedFullBackup;
|
||||||
private HashSet<String> mFilterSet;
|
private HashSet<String> mFilterSet;
|
||||||
|
private long mStartRealtime;
|
||||||
|
private PackageManagerBackupAgent mPmAgent;
|
||||||
|
private List<PackageInfo> mAgentPackages;
|
||||||
|
private ArrayList<PackageInfo> mRestorePackages;
|
||||||
|
private RestoreState mCurrentState;
|
||||||
|
private int mCount;
|
||||||
|
private boolean mFinished;
|
||||||
|
private int mStatus;
|
||||||
|
private File mBackupDataName;
|
||||||
|
private File mNewStateName;
|
||||||
|
private File mSavedStateName;
|
||||||
|
private ParcelFileDescriptor mBackupData;
|
||||||
|
private ParcelFileDescriptor mNewState;
|
||||||
|
private PackageInfo mCurrentPackage;
|
||||||
|
|
||||||
|
|
||||||
class RestoreRequest {
|
class RestoreRequest {
|
||||||
public PackageInfo app;
|
public PackageInfo app;
|
||||||
@@ -3945,6 +3972,10 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
|
PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
|
||||||
long restoreSetToken, PackageInfo targetPackage, int pmToken,
|
long restoreSetToken, PackageInfo targetPackage, int pmToken,
|
||||||
boolean needFullBackup, String[] filterSet) {
|
boolean needFullBackup, String[] filterSet) {
|
||||||
|
mCurrentState = RestoreState.INITIAL;
|
||||||
|
mFinished = false;
|
||||||
|
mPmAgent = null;
|
||||||
|
|
||||||
mTransport = transport;
|
mTransport = transport;
|
||||||
mObserver = observer;
|
mObserver = observer;
|
||||||
mToken = restoreSetToken;
|
mToken = restoreSetToken;
|
||||||
@@ -3968,50 +3999,79 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() {
|
// Execute one tick of whatever state machine the task implements
|
||||||
long startRealtime = SystemClock.elapsedRealtime();
|
@Override
|
||||||
if (DEBUG) Slog.v(TAG, "Beginning restore process mTransport=" + mTransport
|
public void execute() {
|
||||||
+ " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)
|
if (MORE_DEBUG) Slog.v(TAG, "*** Executing restore step: " + mCurrentState);
|
||||||
+ " mTargetPackage=" + mTargetPackage + " mFilterSet=" + mFilterSet
|
switch (mCurrentState) {
|
||||||
+ " mPmToken=" + mPmToken);
|
case INITIAL:
|
||||||
|
beginRestore();
|
||||||
|
break;
|
||||||
|
|
||||||
PackageManagerBackupAgent pmAgent = null;
|
case DOWNLOAD_DATA:
|
||||||
int error = -1; // assume error
|
downloadRestoreData();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PM_METADATA:
|
||||||
|
restorePmMetadata();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RUNNING_QUEUE:
|
||||||
|
restoreNextAgent();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FINAL:
|
||||||
|
if (!mFinished) finalizeRestore();
|
||||||
|
else {
|
||||||
|
Slog.e(TAG, "Duplicate finish");
|
||||||
|
}
|
||||||
|
mFinished = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize and set up for the PM metadata restore, which comes first
|
||||||
|
void beginRestore() {
|
||||||
|
// Don't account time doing the restore as inactivity of the app
|
||||||
|
// that has opened a restore session.
|
||||||
|
mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
|
||||||
|
|
||||||
|
// Assume error until we successfully init everything
|
||||||
|
mStatus = BackupConstants.TRANSPORT_ERROR;
|
||||||
|
|
||||||
// build the set of apps to restore
|
|
||||||
try {
|
try {
|
||||||
// TODO: Log this before getAvailableRestoreSets, somehow
|
// TODO: Log this before getAvailableRestoreSets, somehow
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_START, mTransport.transportDirName(), mToken);
|
EventLog.writeEvent(EventLogTags.RESTORE_START, mTransport.transportDirName(), mToken);
|
||||||
|
|
||||||
// Get the list of all packages which have backup enabled.
|
// Get the list of all packages which have backup enabled.
|
||||||
// (Include the Package Manager metadata pseudo-package first.)
|
// (Include the Package Manager metadata pseudo-package first.)
|
||||||
ArrayList<PackageInfo> restorePackages = new ArrayList<PackageInfo>();
|
mRestorePackages = new ArrayList<PackageInfo>();
|
||||||
PackageInfo omPackage = new PackageInfo();
|
PackageInfo omPackage = new PackageInfo();
|
||||||
omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
|
omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
|
||||||
restorePackages.add(omPackage);
|
mRestorePackages.add(omPackage);
|
||||||
|
|
||||||
List<PackageInfo> agentPackages = allAgentPackages();
|
mAgentPackages = allAgentPackages();
|
||||||
if (mTargetPackage == null) {
|
if (mTargetPackage == null) {
|
||||||
// if there's a filter set, strip out anything that isn't
|
// if there's a filter set, strip out anything that isn't
|
||||||
// present before proceeding
|
// present before proceeding
|
||||||
if (mFilterSet != null) {
|
if (mFilterSet != null) {
|
||||||
for (int i = agentPackages.size() - 1; i >= 0; i--) {
|
for (int i = mAgentPackages.size() - 1; i >= 0; i--) {
|
||||||
final PackageInfo pkg = agentPackages.get(i);
|
final PackageInfo pkg = mAgentPackages.get(i);
|
||||||
if (! mFilterSet.contains(pkg.packageName)) {
|
if (! mFilterSet.contains(pkg.packageName)) {
|
||||||
agentPackages.remove(i);
|
mAgentPackages.remove(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
if (MORE_DEBUG) {
|
||||||
Slog.i(TAG, "Post-filter package set for restore:");
|
Slog.i(TAG, "Post-filter package set for restore:");
|
||||||
for (PackageInfo p : agentPackages) {
|
for (PackageInfo p : mAgentPackages) {
|
||||||
Slog.i(TAG, " " + p);
|
Slog.i(TAG, " " + p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
restorePackages.addAll(agentPackages);
|
mRestorePackages.addAll(mAgentPackages);
|
||||||
} else {
|
} else {
|
||||||
// Just one package to attempt restore of
|
// Just one package to attempt restore of
|
||||||
restorePackages.add(mTargetPackage);
|
mRestorePackages.add(mTargetPackage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// let the observer know that we're running
|
// let the observer know that we're running
|
||||||
@@ -4019,306 +4079,412 @@ class BackupManagerService extends IBackupManager.Stub {
|
|||||||
try {
|
try {
|
||||||
// !!! TODO: get an actual count from the transport after
|
// !!! TODO: get an actual count from the transport after
|
||||||
// its startRestore() runs?
|
// its startRestore() runs?
|
||||||
mObserver.restoreStarting(restorePackages.size());
|
mObserver.restoreStarting(mRestorePackages.size());
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Slog.d(TAG, "Restore observer died at restoreStarting");
|
Slog.d(TAG, "Restore observer died at restoreStarting");
|
||||||
mObserver = null;
|
mObserver = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// Something has gone catastrophically wrong with the transport
|
||||||
|
Slog.e(TAG, "Error communicating with transport for restore");
|
||||||
|
executeNextState(RestoreState.FINAL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0])) !=
|
mStatus = BackupConstants.TRANSPORT_OK;
|
||||||
BackupConstants.TRANSPORT_OK) {
|
executeNextState(RestoreState.DOWNLOAD_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
void downloadRestoreData() {
|
||||||
|
// Note that the download phase can be very time consuming, but we're executing
|
||||||
|
// it inline here on the looper. This is "okay" because it is not calling out to
|
||||||
|
// third party code; the transport is "trusted," and so we assume it is being a
|
||||||
|
// good citizen and timing out etc when appropriate.
|
||||||
|
//
|
||||||
|
// TODO: when appropriate, move the download off the looper and rearrange the
|
||||||
|
// error handling around that.
|
||||||
|
try {
|
||||||
|
mStatus = mTransport.startRestore(mToken,
|
||||||
|
mRestorePackages.toArray(new PackageInfo[0]));
|
||||||
|
if (mStatus != BackupConstants.TRANSPORT_OK) {
|
||||||
Slog.e(TAG, "Error starting restore operation");
|
Slog.e(TAG, "Error starting restore operation");
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
||||||
|
executeNextState(RestoreState.FINAL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.e(TAG, "Error communicating with transport for restore");
|
||||||
|
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
||||||
|
mStatus = BackupConstants.TRANSPORT_ERROR;
|
||||||
|
executeNextState(RestoreState.FINAL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Successful download of the data to be parceled out to the apps, so off we go.
|
||||||
|
executeNextState(RestoreState.PM_METADATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
void restorePmMetadata() {
|
||||||
|
try {
|
||||||
String packageName = mTransport.nextRestorePackage();
|
String packageName = mTransport.nextRestorePackage();
|
||||||
if (packageName == null) {
|
if (packageName == null) {
|
||||||
Slog.e(TAG, "Error getting first restore package");
|
Slog.e(TAG, "Error getting first restore package");
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
||||||
|
mStatus = BackupConstants.TRANSPORT_ERROR;
|
||||||
|
executeNextState(RestoreState.FINAL);
|
||||||
return;
|
return;
|
||||||
} else if (packageName.equals("")) {
|
} else if (packageName.equals("")) {
|
||||||
Slog.i(TAG, "No restore data available");
|
Slog.i(TAG, "No restore data available");
|
||||||
int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
|
int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, 0, millis);
|
EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, 0, millis);
|
||||||
|
mStatus = BackupConstants.TRANSPORT_OK;
|
||||||
|
executeNextState(RestoreState.FINAL);
|
||||||
return;
|
return;
|
||||||
} else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
|
} else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
|
||||||
Slog.e(TAG, "Expected restore data for \"" + PACKAGE_MANAGER_SENTINEL
|
Slog.e(TAG, "Expected restore data for \"" + PACKAGE_MANAGER_SENTINEL
|
||||||
+ "\", found only \"" + packageName + "\"");
|
+ "\", found only \"" + packageName + "\"");
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
|
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
|
||||||
"Package manager data missing");
|
"Package manager data missing");
|
||||||
|
executeNextState(RestoreState.FINAL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull the Package Manager metadata from the restore set first
|
// Pull the Package Manager metadata from the restore set first
|
||||||
pmAgent = new PackageManagerBackupAgent(
|
PackageInfo omPackage = new PackageInfo();
|
||||||
mPackageManager, agentPackages);
|
omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
|
||||||
processOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(pmAgent.onBind()),
|
mPmAgent = new PackageManagerBackupAgent(
|
||||||
|
mPackageManager, mAgentPackages);
|
||||||
|
initiateOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(mPmAgent.onBind()),
|
||||||
mNeedFullBackup);
|
mNeedFullBackup);
|
||||||
|
// The PM agent called operationComplete() already, because our invocation
|
||||||
|
// of it is process-local and therefore synchronous. That means that a
|
||||||
|
// RUNNING_QUEUE message is already enqueued. Only if we're unable to
|
||||||
|
// proceed with running the queue do we remove that pending message and
|
||||||
|
// jump straight to the FINAL state.
|
||||||
|
|
||||||
// Verify that the backup set includes metadata. If not, we can't do
|
// Verify that the backup set includes metadata. If not, we can't do
|
||||||
// signature/version verification etc, so we simply do not proceed with
|
// signature/version verification etc, so we simply do not proceed with
|
||||||
// the restore operation.
|
// the restore operation.
|
||||||
if (!pmAgent.hasMetadata()) {
|
if (!mPmAgent.hasMetadata()) {
|
||||||
Slog.e(TAG, "No restore metadata available, so not restoring settings");
|
Slog.e(TAG, "No restore metadata available, so not restoring settings");
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
|
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
|
||||||
"Package manager restore metadata missing");
|
"Package manager restore metadata missing");
|
||||||
|
mStatus = BackupConstants.TRANSPORT_ERROR;
|
||||||
|
mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
|
||||||
|
executeNextState(RestoreState.FINAL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.e(TAG, "Error communicating with transport for restore");
|
||||||
|
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
||||||
|
mStatus = BackupConstants.TRANSPORT_ERROR;
|
||||||
|
mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
|
||||||
|
executeNextState(RestoreState.FINAL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int count = 0;
|
// Metadata is intact, so we can now run the restore queue. If we get here,
|
||||||
for (;;) {
|
// we have already enqueued the necessary next-step message on the looper.
|
||||||
packageName = mTransport.nextRestorePackage();
|
}
|
||||||
|
|
||||||
if (packageName == null) {
|
void restoreNextAgent() {
|
||||||
Slog.e(TAG, "Error getting next restore package");
|
try {
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
String packageName = mTransport.nextRestorePackage();
|
||||||
return;
|
|
||||||
} else if (packageName.equals("")) {
|
|
||||||
if (DEBUG) Slog.v(TAG, "No next package, finishing restore");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mObserver != null) {
|
if (packageName == null) {
|
||||||
try {
|
Slog.e(TAG, "Error getting next restore package");
|
||||||
mObserver.onUpdate(count, packageName);
|
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
||||||
} catch (RemoteException e) {
|
executeNextState(RestoreState.FINAL);
|
||||||
Slog.d(TAG, "Restore observer died in onUpdate");
|
return;
|
||||||
mObserver = null;
|
} else if (packageName.equals("")) {
|
||||||
}
|
if (DEBUG) Slog.v(TAG, "No next package, finishing restore");
|
||||||
}
|
int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
|
||||||
|
EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
|
||||||
Metadata metaInfo = pmAgent.getRestoredMetadata(packageName);
|
executeNextState(RestoreState.FINAL);
|
||||||
if (metaInfo == null) {
|
return;
|
||||||
Slog.e(TAG, "Missing metadata for " + packageName);
|
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
|
|
||||||
"Package metadata missing");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
PackageInfo packageInfo;
|
|
||||||
try {
|
|
||||||
int flags = PackageManager.GET_SIGNATURES;
|
|
||||||
packageInfo = mPackageManager.getPackageInfo(packageName, flags);
|
|
||||||
} catch (NameNotFoundException e) {
|
|
||||||
Slog.e(TAG, "Invalid package restoring data", e);
|
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
|
|
||||||
"Package missing on device");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metaInfo.versionCode > packageInfo.versionCode) {
|
|
||||||
// Data is from a "newer" version of the app than we have currently
|
|
||||||
// installed. If the app has not declared that it is prepared to
|
|
||||||
// handle this case, we do not attempt the restore.
|
|
||||||
if ((packageInfo.applicationInfo.flags
|
|
||||||
& ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
|
|
||||||
String message = "Version " + metaInfo.versionCode
|
|
||||||
+ " > installed version " + packageInfo.versionCode;
|
|
||||||
Slog.w(TAG, "Package " + packageName + ": " + message);
|
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
|
|
||||||
packageName, message);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
if (DEBUG) Slog.v(TAG, "Version " + metaInfo.versionCode
|
|
||||||
+ " > installed " + packageInfo.versionCode
|
|
||||||
+ " but restoreAnyVersion");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!signaturesMatch(metaInfo.signatures, packageInfo)) {
|
|
||||||
Slog.w(TAG, "Signature mismatch restoring " + packageName);
|
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
|
|
||||||
"Signature mismatch");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG) Slog.v(TAG, "Package " + packageName
|
|
||||||
+ " restore version [" + metaInfo.versionCode
|
|
||||||
+ "] is compatible with installed version ["
|
|
||||||
+ packageInfo.versionCode + "]");
|
|
||||||
|
|
||||||
// Then set up and bind the agent
|
|
||||||
IBackupAgent agent = bindToAgentSynchronous(
|
|
||||||
packageInfo.applicationInfo,
|
|
||||||
IApplicationThread.BACKUP_MODE_INCREMENTAL);
|
|
||||||
if (agent == null) {
|
|
||||||
Slog.w(TAG, "Can't find backup agent for " + packageName);
|
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
|
|
||||||
"Restore agent missing");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// And then finally run the restore on this agent
|
|
||||||
try {
|
|
||||||
processOneRestore(packageInfo, metaInfo.versionCode, agent,
|
|
||||||
mNeedFullBackup);
|
|
||||||
++count;
|
|
||||||
} finally {
|
|
||||||
// unbind and tidy up even on timeout or failure, just in case
|
|
||||||
mActivityManager.unbindBackupAgent(packageInfo.applicationInfo);
|
|
||||||
|
|
||||||
// The agent was probably running with a stub Application object,
|
|
||||||
// which isn't a valid run mode for the main app logic. Shut
|
|
||||||
// down the app so that next time it's launched, it gets the
|
|
||||||
// usual full initialization. Note that this is only done for
|
|
||||||
// full-system restores: when a single app has requested a restore,
|
|
||||||
// it is explicitly not killed following that operation.
|
|
||||||
if (mTargetPackage == null && (packageInfo.applicationInfo.flags
|
|
||||||
& ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) {
|
|
||||||
if (DEBUG) Slog.d(TAG, "Restore complete, killing host process of "
|
|
||||||
+ packageInfo.applicationInfo.processName);
|
|
||||||
mActivityManager.killApplicationProcess(
|
|
||||||
packageInfo.applicationInfo.processName,
|
|
||||||
packageInfo.applicationInfo.uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we get this far, report success to the observer
|
|
||||||
error = 0;
|
|
||||||
int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
|
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, count, millis);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Slog.e(TAG, "Error in restore thread", e);
|
|
||||||
} finally {
|
|
||||||
if (DEBUG) Slog.d(TAG, "finishing restore mObserver=" + mObserver);
|
|
||||||
|
|
||||||
try {
|
|
||||||
mTransport.finishRestore();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Slog.e(TAG, "Error finishing restore", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mObserver != null) {
|
if (mObserver != null) {
|
||||||
try {
|
try {
|
||||||
mObserver.restoreFinished(error);
|
mObserver.onUpdate(mCount, packageName);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Slog.d(TAG, "Restore observer died at restoreFinished");
|
Slog.d(TAG, "Restore observer died in onUpdate");
|
||||||
|
mObserver = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this was a restoreAll operation, record that this was our
|
Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
|
||||||
// ancestral dataset, as well as the set of apps that are possibly
|
if (metaInfo == null) {
|
||||||
// restoreable from the dataset
|
Slog.e(TAG, "Missing metadata for " + packageName);
|
||||||
if (mTargetPackage == null && pmAgent != null) {
|
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
|
||||||
mAncestralPackages = pmAgent.getRestoredPackages();
|
"Package metadata missing");
|
||||||
mAncestralToken = mToken;
|
executeNextState(RestoreState.RUNNING_QUEUE);
|
||||||
writeRestoreTokens();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must under all circumstances tell the Package Manager to
|
PackageInfo packageInfo;
|
||||||
// proceed with install notifications if it's waiting for us.
|
try {
|
||||||
if (mPmToken > 0) {
|
int flags = PackageManager.GET_SIGNATURES;
|
||||||
if (DEBUG) Slog.v(TAG, "finishing PM token " + mPmToken);
|
packageInfo = mPackageManager.getPackageInfo(packageName, flags);
|
||||||
try {
|
} catch (NameNotFoundException e) {
|
||||||
mPackageManagerBinder.finishPackageInstall(mPmToken);
|
Slog.e(TAG, "Invalid package restoring data", e);
|
||||||
} catch (RemoteException e) { /* can't happen */ }
|
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
|
||||||
|
"Package missing on device");
|
||||||
|
executeNextState(RestoreState.RUNNING_QUEUE);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Furthermore we need to reset the session timeout clock
|
if (metaInfo.versionCode > packageInfo.versionCode) {
|
||||||
mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
|
// Data is from a "newer" version of the app than we have currently
|
||||||
mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
|
// installed. If the app has not declared that it is prepared to
|
||||||
TIMEOUT_RESTORE_INTERVAL);
|
// handle this case, we do not attempt the restore.
|
||||||
|
if ((packageInfo.applicationInfo.flags
|
||||||
|
& ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
|
||||||
|
String message = "Version " + metaInfo.versionCode
|
||||||
|
+ " > installed version " + packageInfo.versionCode;
|
||||||
|
Slog.w(TAG, "Package " + packageName + ": " + message);
|
||||||
|
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
|
||||||
|
packageName, message);
|
||||||
|
executeNextState(RestoreState.RUNNING_QUEUE);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (DEBUG) Slog.v(TAG, "Version " + metaInfo.versionCode
|
||||||
|
+ " > installed " + packageInfo.versionCode
|
||||||
|
+ " but restoreAnyVersion");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// done; we can finally release the wakelock
|
if (!signaturesMatch(metaInfo.signatures, packageInfo)) {
|
||||||
mWakelock.release();
|
Slog.w(TAG, "Signature mismatch restoring " + packageName);
|
||||||
|
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
|
||||||
|
"Signature mismatch");
|
||||||
|
executeNextState(RestoreState.RUNNING_QUEUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) Slog.v(TAG, "Package " + packageName
|
||||||
|
+ " restore version [" + metaInfo.versionCode
|
||||||
|
+ "] is compatible with installed version ["
|
||||||
|
+ packageInfo.versionCode + "]");
|
||||||
|
|
||||||
|
// Then set up and bind the agent
|
||||||
|
IBackupAgent agent = bindToAgentSynchronous(
|
||||||
|
packageInfo.applicationInfo,
|
||||||
|
IApplicationThread.BACKUP_MODE_INCREMENTAL);
|
||||||
|
if (agent == null) {
|
||||||
|
Slog.w(TAG, "Can't find backup agent for " + packageName);
|
||||||
|
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
|
||||||
|
"Restore agent missing");
|
||||||
|
executeNextState(RestoreState.RUNNING_QUEUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And then finally start the restore on this agent
|
||||||
|
try {
|
||||||
|
initiateOneRestore(packageInfo, metaInfo.versionCode, agent, mNeedFullBackup);
|
||||||
|
++mCount;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Slog.e(TAG, "Error when attempting restore: " + e.toString());
|
||||||
|
agentErrorCleanup();
|
||||||
|
executeNextState(RestoreState.RUNNING_QUEUE);
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.e(TAG, "Unable to fetch restore data from transport");
|
||||||
|
mStatus = BackupConstants.TRANSPORT_ERROR;
|
||||||
|
executeNextState(RestoreState.FINAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the guts of a restore of one application, using mTransport.getRestoreData().
|
void finalizeRestore() {
|
||||||
void processOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
|
if (MORE_DEBUG) Slog.d(TAG, "finishing restore mObserver=" + mObserver);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mTransport.finishRestore();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.e(TAG, "Error finishing restore", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mObserver != null) {
|
||||||
|
try {
|
||||||
|
mObserver.restoreFinished(mStatus);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.d(TAG, "Restore observer died at restoreFinished");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this was a restoreAll operation, record that this was our
|
||||||
|
// ancestral dataset, as well as the set of apps that are possibly
|
||||||
|
// restoreable from the dataset
|
||||||
|
if (mTargetPackage == null && mPmAgent != null) {
|
||||||
|
mAncestralPackages = mPmAgent.getRestoredPackages();
|
||||||
|
mAncestralToken = mToken;
|
||||||
|
writeRestoreTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must under all circumstances tell the Package Manager to
|
||||||
|
// proceed with install notifications if it's waiting for us.
|
||||||
|
if (mPmToken > 0) {
|
||||||
|
if (MORE_DEBUG) Slog.v(TAG, "finishing PM token " + mPmToken);
|
||||||
|
try {
|
||||||
|
mPackageManagerBinder.finishPackageInstall(mPmToken);
|
||||||
|
} catch (RemoteException e) { /* can't happen */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Furthermore we need to reset the session timeout clock
|
||||||
|
mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
|
||||||
|
mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
|
||||||
|
TIMEOUT_RESTORE_INTERVAL);
|
||||||
|
|
||||||
|
// done; we can finally release the wakelock
|
||||||
|
Slog.i(TAG, "Restore complete.");
|
||||||
|
mWakelock.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call asynchronously into the app, passing it the restore data. The next step
|
||||||
|
// after this is always a callback, either operationComplete() or handleTimeout().
|
||||||
|
void initiateOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
|
||||||
boolean needFullBackup) {
|
boolean needFullBackup) {
|
||||||
// !!! TODO: actually run the restore through mTransport
|
mCurrentPackage = app;
|
||||||
final String packageName = app.packageName;
|
final String packageName = app.packageName;
|
||||||
|
|
||||||
if (DEBUG) Slog.d(TAG, "processOneRestore packageName=" + packageName);
|
if (DEBUG) Slog.d(TAG, "initiateOneRestore packageName=" + packageName);
|
||||||
|
|
||||||
// !!! TODO: get the dirs from the transport
|
// !!! TODO: get the dirs from the transport
|
||||||
File backupDataName = new File(mDataDir, packageName + ".restore");
|
mBackupDataName = new File(mDataDir, packageName + ".restore");
|
||||||
File newStateName = new File(mStateDir, packageName + ".new");
|
mNewStateName = new File(mStateDir, packageName + ".new");
|
||||||
File savedStateName = new File(mStateDir, packageName);
|
mSavedStateName = new File(mStateDir, packageName);
|
||||||
|
|
||||||
ParcelFileDescriptor backupData = null;
|
|
||||||
ParcelFileDescriptor newState = null;
|
|
||||||
|
|
||||||
final int token = generateToken();
|
final int token = generateToken();
|
||||||
try {
|
try {
|
||||||
// Run the transport's restore pass
|
// Run the transport's restore pass
|
||||||
backupData = ParcelFileDescriptor.open(backupDataName,
|
mBackupData = ParcelFileDescriptor.open(mBackupDataName,
|
||||||
ParcelFileDescriptor.MODE_READ_WRITE |
|
ParcelFileDescriptor.MODE_READ_WRITE |
|
||||||
ParcelFileDescriptor.MODE_CREATE |
|
ParcelFileDescriptor.MODE_CREATE |
|
||||||
ParcelFileDescriptor.MODE_TRUNCATE);
|
ParcelFileDescriptor.MODE_TRUNCATE);
|
||||||
|
|
||||||
if (mTransport.getRestoreData(backupData) != BackupConstants.TRANSPORT_OK) {
|
if (mTransport.getRestoreData(mBackupData) != BackupConstants.TRANSPORT_OK) {
|
||||||
Slog.e(TAG, "Error getting restore data for " + packageName);
|
Slog.e(TAG, "Error getting restore data for " + packageName);
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Okay, we have the data. Now have the agent do the restore.
|
// Okay, we have the data. Now have the agent do the restore.
|
||||||
backupData.close();
|
mBackupData.close();
|
||||||
backupData = ParcelFileDescriptor.open(backupDataName,
|
mBackupData = ParcelFileDescriptor.open(mBackupDataName,
|
||||||
ParcelFileDescriptor.MODE_READ_ONLY);
|
ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
|
|
||||||
newState = ParcelFileDescriptor.open(newStateName,
|
mNewState = ParcelFileDescriptor.open(mNewStateName,
|
||||||
ParcelFileDescriptor.MODE_READ_WRITE |
|
ParcelFileDescriptor.MODE_READ_WRITE |
|
||||||
ParcelFileDescriptor.MODE_CREATE |
|
ParcelFileDescriptor.MODE_CREATE |
|
||||||
ParcelFileDescriptor.MODE_TRUNCATE);
|
ParcelFileDescriptor.MODE_TRUNCATE);
|
||||||
|
|
||||||
// Kick off the restore, checking for hung agents
|
// Kick off the restore, checking for hung agents
|
||||||
prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, null);
|
prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
|
||||||
agent.doRestore(backupData, appVersionCode, newState, token, mBackupManagerBinder);
|
agent.doRestore(mBackupData, appVersionCode, mNewState, token, mBackupManagerBinder);
|
||||||
boolean success = waitUntilOperationComplete(token);
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
throw new RuntimeException("restore timeout");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if everything went okay, remember the recorded state now
|
|
||||||
//
|
|
||||||
// !!! TODO: the restored data should be migrated on the server
|
|
||||||
// side into the current dataset. In that case the new state file
|
|
||||||
// we just created would reflect the data already extant in the
|
|
||||||
// backend, so there'd be nothing more to do. Until that happens,
|
|
||||||
// however, we need to make sure that we record the data to the
|
|
||||||
// current backend dataset. (Yes, this means shipping the data over
|
|
||||||
// the wire in both directions. That's bad, but consistency comes
|
|
||||||
// first, then efficiency.) Once we introduce server-side data
|
|
||||||
// migration to the newly-restored device's dataset, we will change
|
|
||||||
// the following from a discard of the newly-written state to the
|
|
||||||
// "correct" operation of renaming into the canonical state blob.
|
|
||||||
newStateName.delete(); // TODO: remove; see above comment
|
|
||||||
//newStateName.renameTo(savedStateName); // TODO: replace with this
|
|
||||||
|
|
||||||
int size = (int) backupDataName.length();
|
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, packageName, size);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Slog.e(TAG, "Error restoring data for " + packageName, e);
|
Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
|
||||||
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString());
|
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString());
|
||||||
|
agentErrorCleanup(); // clears any pending timeout messages as well
|
||||||
|
|
||||||
// If the agent fails restore, it might have put the app's data
|
// After a restore failure we go back to running the queue. If there
|
||||||
// into an incoherent state. For consistency we wipe its data
|
// are no more packages to be restored that will be handled by the
|
||||||
// again in this case before propagating the exception
|
// next step.
|
||||||
clearApplicationDataSynchronous(packageName);
|
executeNextState(RestoreState.RUNNING_QUEUE);
|
||||||
} finally {
|
}
|
||||||
backupDataName.delete();
|
}
|
||||||
try { if (backupData != null) backupData.close(); } catch (IOException e) {}
|
|
||||||
try { if (newState != null) newState.close(); } catch (IOException e) {}
|
|
||||||
backupData = newState = null;
|
|
||||||
synchronized (mCurrentOperations) {
|
|
||||||
mCurrentOperations.delete(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we know a priori that we'll need to perform a full post-restore backup
|
void agentErrorCleanup() {
|
||||||
// pass, clear the new state file data. This means we're discarding work that
|
// If the agent fails restore, it might have put the app's data
|
||||||
// was just done by the app's agent, but this way the agent doesn't need to
|
// into an incoherent state. For consistency we wipe its data
|
||||||
// take any special action based on global device state.
|
// again in this case before continuing with normal teardown
|
||||||
if (needFullBackup) {
|
clearApplicationDataSynchronous(mCurrentPackage.packageName);
|
||||||
newStateName.delete();
|
agentCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void agentCleanup() {
|
||||||
|
mBackupDataName.delete();
|
||||||
|
try { if (mBackupData != null) mBackupData.close(); } catch (IOException e) {}
|
||||||
|
try { if (mNewState != null) mNewState.close(); } catch (IOException e) {}
|
||||||
|
mBackupData = mNewState = null;
|
||||||
|
|
||||||
|
// if everything went okay, remember the recorded state now
|
||||||
|
//
|
||||||
|
// !!! TODO: the restored data should be migrated on the server
|
||||||
|
// side into the current dataset. In that case the new state file
|
||||||
|
// we just created would reflect the data already extant in the
|
||||||
|
// backend, so there'd be nothing more to do. Until that happens,
|
||||||
|
// however, we need to make sure that we record the data to the
|
||||||
|
// current backend dataset. (Yes, this means shipping the data over
|
||||||
|
// the wire in both directions. That's bad, but consistency comes
|
||||||
|
// first, then efficiency.) Once we introduce server-side data
|
||||||
|
// migration to the newly-restored device's dataset, we will change
|
||||||
|
// the following from a discard of the newly-written state to the
|
||||||
|
// "correct" operation of renaming into the canonical state blob.
|
||||||
|
mNewStateName.delete(); // TODO: remove; see above comment
|
||||||
|
//mNewStateName.renameTo(mSavedStateName); // TODO: replace with this
|
||||||
|
|
||||||
|
// If this wasn't the PM pseudopackage, tear down the agent side
|
||||||
|
if (mCurrentPackage.applicationInfo != null) {
|
||||||
|
// unbind and tidy up even on timeout or failure
|
||||||
|
try {
|
||||||
|
mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
|
||||||
|
|
||||||
|
// The agent was probably running with a stub Application object,
|
||||||
|
// which isn't a valid run mode for the main app logic. Shut
|
||||||
|
// down the app so that next time it's launched, it gets the
|
||||||
|
// usual full initialization. Note that this is only done for
|
||||||
|
// full-system restores: when a single app has requested a restore,
|
||||||
|
// it is explicitly not killed following that operation.
|
||||||
|
if (mTargetPackage == null && (mCurrentPackage.applicationInfo.flags
|
||||||
|
& ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) {
|
||||||
|
if (DEBUG) Slog.d(TAG, "Restore complete, killing host process of "
|
||||||
|
+ mCurrentPackage.applicationInfo.processName);
|
||||||
|
mActivityManager.killApplicationProcess(
|
||||||
|
mCurrentPackage.applicationInfo.processName,
|
||||||
|
mCurrentPackage.applicationInfo.uid);
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// can't happen; we run in the same process as the activity manager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The caller is responsible for reestablishing the state machine; our
|
||||||
|
// responsibility here is to clear the decks for whatever comes next.
|
||||||
|
mBackupHandler.removeMessages(MSG_TIMEOUT, this);
|
||||||
|
synchronized (mCurrentOpLock) {
|
||||||
|
mCurrentOperations.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A call to agent.doRestore() has been positively acknowledged as complete
|
||||||
|
@Override
|
||||||
|
public void operationComplete() {
|
||||||
|
int size = (int) mBackupDataName.length();
|
||||||
|
EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, mCurrentPackage.packageName, size);
|
||||||
|
// Just go back to running the restore queue
|
||||||
|
agentCleanup();
|
||||||
|
|
||||||
|
executeNextState(RestoreState.RUNNING_QUEUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A call to agent.doRestore() has timed out
|
||||||
|
@Override
|
||||||
|
public void handleTimeout() {
|
||||||
|
Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
|
||||||
|
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
|
||||||
|
mCurrentPackage.packageName, "restore timeout");
|
||||||
|
// Handle like an agent that threw on invocation: wipe it and go on to the next
|
||||||
|
agentErrorCleanup();
|
||||||
|
executeNextState(RestoreState.RUNNING_QUEUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeNextState(RestoreState nextState) {
|
||||||
|
if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
|
||||||
|
+ this + " nextState=" + nextState);
|
||||||
|
mCurrentState = nextState;
|
||||||
|
Message msg = mBackupHandler.obtainMessage(MSG_BACKUP_RESTORE_STEP, this);
|
||||||
|
mBackupHandler.sendMessage(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user