summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShreyas Basarge <snb@google.com>2017-01-28 16:50:09 +0000
committerShreyas Basarge <snb@google.com>2017-02-14 13:51:59 +0000
commitc3704427c9a34397fd90bf5438adae2eebcc97f6 (patch)
tree8b72d3ecfea34539e7b35ed942d2b30b2aafdd0f
parentf49f3482de64b44c9c6d3132521c5ed54b64a2f1 (diff)
BackupManager#cancelBackups() API
Introduces a cancelBackups() API for BackupManager. When this function returns, it is guaranteed that currently running backup operations won't interact with the active transport. Bug: 34760860 Ref: https://docs.google.com/document/d/18MnfwkDfKNtXQBPRmL8vpVgfLgSWJsDja1Nm1QV5hOw/edit#heading=h.9p6yo0wx44k3 Test: GTS tests at ag/1893365 Change-Id: I67f78699bbe763ea71c85937fbc01a5b48694eed
-rw-r--r--api/system-current.txt2
-rw-r--r--cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java24
-rw-r--r--core/java/android/app/backup/BackupManager.java28
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl6
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java976
-rw-r--r--services/backup/java/com/android/server/backup/Trampoline.java8
-rw-r--r--services/core/java/com/android/server/EventLogTags.logtags1
7 files changed, 736 insertions, 309 deletions
diff --git a/api/system-current.txt b/api/system-current.txt
index edf778b04c19..59a080e2526e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6892,6 +6892,7 @@ package android.app.backup {
ctor public BackupManager(android.content.Context);
method public void backupNow();
method public android.app.backup.RestoreSession beginRestoreSession();
+ method public void cancelBackups();
method public void dataChanged();
method public static void dataChanged(java.lang.String);
method public long getAvailableRestoreToken(java.lang.String);
@@ -6908,6 +6909,7 @@ package android.app.backup {
method public void setAutoRestore(boolean);
method public void setBackupEnabled(boolean);
field public static final int ERROR_AGENT_FAILURE = -1003; // 0xfffffc15
+ field public static final int ERROR_BACKUP_CANCELLED = -2003; // 0xfffff82d
field public static final int ERROR_BACKUP_NOT_ALLOWED = -2001; // 0xfffff82f
field public static final int ERROR_PACKAGE_NOT_FOUND = -2002; // 0xfffff82e
field public static final int ERROR_TRANSPORT_ABORTED = -1000; // 0xfffffc18
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index a67e47f92017..bfcad1bb5f96 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -127,6 +127,11 @@ public final class Bmgr {
return;
}
+ if ("cancel".equals(op)) {
+ doCancel();
+ return;
+ }
+
if ("whitelist".equals(op)) {
doPrintWhitelist();
return;
@@ -270,6 +275,8 @@ public final class Bmgr {
return "Agent error";
case BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED:
return "Size quota exceeded";
+ case BackupManager.ERROR_BACKUP_CANCELLED:
+ return "Backup Cancelled";
default:
return "Unknown error";
}
@@ -361,6 +368,21 @@ public final class Bmgr {
}
}
+ private void doCancel() {
+ String arg = nextArg();
+ if ("backups".equals(arg)) {
+ try {
+ mBmgr.cancelBackups();
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(BMGR_NOT_RUNNING_ERR);
+ }
+ return;
+ }
+
+ System.err.println("Unknown command.");
+ }
+
private void doTransport() {
try {
String which = nextArg();
@@ -721,6 +743,7 @@ public final class Bmgr {
System.err.println(" bmgr wipe TRANSPORT PACKAGE");
System.err.println(" bmgr fullbackup PACKAGE...");
System.err.println(" bmgr backupnow --all|PACKAGE...");
+ System.err.println(" bmgr cancel backups");
System.err.println("");
System.err.println("The 'backup' command schedules a backup pass for the named package.");
System.err.println("Note that the backup pass will effectively be a no-op if the package");
@@ -780,5 +803,6 @@ public final class Bmgr {
System.err.println("For each package it will run key/value or full data backup ");
System.err.println("depending on the package's manifest declarations.");
System.err.println("The data is sent via the currently active transport.");
+ System.err.println("The 'cancel backups' command cancels all running backups.");
}
}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 59bd01f94592..9d02f53b13bd 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -89,6 +89,14 @@ public class BackupManager {
public static final int ERROR_PACKAGE_NOT_FOUND = -2002;
/**
+ * The backup operation was cancelled.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_BACKUP_CANCELLED = -2003;
+
+ /**
* The transport for some reason was not in a good state and
* aborted the entire backup request. This is a transient
* failure and should not be retried immediately.
@@ -626,6 +634,26 @@ public class BackupManager {
return -1;
}
+ /**
+ * Cancel all running backups. After this call returns, no currently running backups will
+ * interact with the selected transport.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void cancelBackups() {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.cancelBackups();
+ } catch (RemoteException e) {
+ Log.e(TAG, "cancelBackups() couldn't connect.");
+ }
+ }
+ }
+
/*
* We wrap incoming binder calls with a private class implementation that
* redirects them into main-thread actions. This serializes the backup
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 393667d7ff2d..59a941ac46c8 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -386,4 +386,10 @@ interface IBackupManager {
*/
int requestBackup(in String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor,
int flags);
+
+ /**
+ * Cancel all running backups. After this call returns, no currently running backups will
+ * interact with the selected transport.
+ */
+ void cancelBackups();
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 6bf0e8d8c2cd..13e6ae04d858 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -599,15 +599,42 @@ public class BackupManagerService {
static final int OP_ACKNOWLEDGED = 1;
static final int OP_TIMEOUT = -1;
+ private static final int OP_TYPE_WAIT = 0; // Waiting for BackupAgent.
+ private static final int OP_TYPE_BACKUP = 1; // Backup operation in progress.
+
class Operation {
- public int state;
- public BackupRestoreTask callback;
+ int state;
+ final BackupRestoreTask callback;
+ final int type;
- Operation(int initialState, BackupRestoreTask callbackObj) {
+ Operation(int initialState, BackupRestoreTask callbackObj, int type) {
state = initialState;
callback = callbackObj;
+ this.type = type;
}
}
+
+ /**
+ * mCurrentOperations contains the list of currently active operations.
+ *
+ * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
+ * An operation wraps a BackupRestoreTask within it.
+ * It's the responsibility of this task to remove the operation from this array.
+ *
+ * A BackupRestore task gets notified of ack/timeout for the operation via
+ * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
+ * on the mCurrentOpLock. {@link BackupManagerService#waitUntilOperationComplete(int)} is
+ * used in various places to 'wait' for notifyAll and detect change of pending state of an
+ * operation. So typically, an operation will be removed from this array by:
+ * - BackupRestoreTask#handleCancel and
+ * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
+ * these places because waitUntilOperationComplete relies on the operation being present to
+ * determine its completion status.
+ *
+ * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
+ * cancel backup tasks.
+ */
+ @GuardedBy("mCurrentOpLock")
final SparseArray<Operation> mCurrentOperations = new SparseArray<Operation>();
final Object mCurrentOpLock = new Object();
final Random mTokenGenerator = new Random();
@@ -967,7 +994,8 @@ public class BackupManagerService {
case MSG_TIMEOUT:
{
- handleTimeout(msg.arg1, msg.obj);
+ Slog.d(TAG, "Timeout message received for token=" + Integer.toHexString(msg.arg1));
+ handleCancel(msg.arg1, false);
break;
}
@@ -2364,6 +2392,33 @@ public class BackupManagerService {
return BackupManager.SUCCESS;
}
+ // Cancel all running backups.
+ public void cancelBackups(){
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups");
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "cancelBackups() called.");
+ }
+ final long oldToken = Binder.clearCallingIdentity();
+ try {
+ synchronized (mCurrentOpLock) {
+ for (int i = 0; i < mCurrentOperations.size(); i++) {
+ Operation op = mCurrentOperations.valueAt(i);
+ int token = mCurrentOperations.keyAt(i);
+ if (op.type == OP_TYPE_BACKUP) {
+ handleCancel(token, true /* cancelAll */);
+ }
+ }
+ }
+
+ // We don't want the backup jobs to kick in any time soon.
+ // Reschedules them to run in the distant future.
+ KeyValueBackupJob.schedule(mContext, BUSY_BACKOFF_MIN_MILLIS);
+ FullBackupJob.schedule(mContext, 2 * BUSY_BACKOFF_MIN_MILLIS);
+ } finally {
+ Binder.restoreCallingIdentity(oldToken);
+ }
+ }
+
// -----
// Interface and methods used by the asynchronous-with-timeout backup/restore operations
@@ -2375,20 +2430,34 @@ public class BackupManagerService {
void operationComplete(long result);
// An operation that wanted a callback has timed out
- void handleTimeout();
+ void handleCancel(boolean cancelAll);
}
- void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback) {
+ void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
+ int operationType) {
if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
+ " interval=" + interval + " callback=" + callback);
synchronized (mCurrentOpLock) {
- mCurrentOperations.put(token, new Operation(OP_PENDING, callback));
+ mCurrentOperations.put(token, new Operation(OP_PENDING, callback, operationType));
Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0, callback);
mBackupHandler.sendMessageDelayed(msg, interval);
}
}
+ private void removeOperation(int token) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Removing operation token=" + Integer.toHexString(token));
+ }
+ synchronized (mCurrentOpLock) {
+ if (mCurrentOperations.get(token) == null) {
+ Slog.w(TAG, "Duplicate remove for operation. token=" +
+ Integer.toHexString(token));
+ }
+ mCurrentOperations.remove(token);
+ }
+ }
+
// synchronous waiter case
boolean waitUntilOperationComplete(int token) {
if (MORE_DEBUG) Slog.i(TAG, "Blocking until operation complete for "
@@ -2405,9 +2474,14 @@ public class BackupManagerService {
if (op.state == OP_PENDING) {
try {
mCurrentOpLock.wait();
- } catch (InterruptedException e) {}
+ } catch (InterruptedException e) {
+ }
// When the wait is notified we loop around and recheck the current state
} else {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Unblocked waiting for operation token=" +
+ Integer.toHexString(token));
+ }
// No longer pending; we're done
finalState = op.state;
break;
@@ -2416,33 +2490,36 @@ public class BackupManagerService {
}
}
+ removeOperation(token);
mBackupHandler.removeMessages(MSG_TIMEOUT);
if (MORE_DEBUG) Slog.v(TAG, "operation " + Integer.toHexString(token)
+ " complete: finalState=" + finalState);
return finalState == OP_ACKNOWLEDGED;
}
- void handleTimeout(int token, Object obj) {
+ void handleCancel(int token, boolean cancelAll) {
// Notify any synchronous waiters
Operation op = null;
synchronized (mCurrentOpLock) {
op = mCurrentOperations.get(token);
if (MORE_DEBUG) {
- if (op == null) Slog.w(TAG, "Timeout of token " + Integer.toHexString(token)
+ if (op == null) Slog.w(TAG, "Cancel of token " + Integer.toHexString(token)
+ " but no op found");
}
int state = (op != null) ? op.state : OP_TIMEOUT;
if (state == OP_ACKNOWLEDGED) {
// The operation finished cleanly, so we have nothing more to do.
- if (MORE_DEBUG) {
- Slog.v(TAG, "handleTimeout() after success; cleanup happens now");
+ if (DEBUG) {
+ Slog.w(TAG, "Operation already got an ack." +
+ "Should have been removed from mCurrentOperations.");
}
op = null;
mCurrentOperations.delete(token);
} else if (state == OP_PENDING) {
- if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + Integer.toHexString(token));
+ if (DEBUG) Slog.v(TAG, "Cancel: token=" + Integer.toHexString(token));
op.state = OP_TIMEOUT;
- // Leaves the object in place for later ack
+ // Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
+ // called after we receive cancel here. We need this op's state there.
}
mCurrentOpLock.notifyAll();
}
@@ -2450,9 +2527,9 @@ public class BackupManagerService {
// If there's a TimeoutHandler for this event, call it
if (op != null && op.callback != null) {
if (MORE_DEBUG) {
- Slog.v(TAG, " Invoking timeout on " + op.callback);
+ Slog.v(TAG, " Invoking cancel on " + op.callback);
}
- op.callback.handleTimeout();
+ op.callback.handleCancel(cancelAll);
}
}
@@ -2464,9 +2541,36 @@ public class BackupManagerService {
FINAL
}
+ /**
+ * This class handles the process of backing up a given list of key/value backup packages.
+ * Also takes in a list of pending dolly backups and kicks them off when key/value backups
+ * are done.
+ *
+ * Flow:
+ * If required, backup @pm@.
+ * For each pending key/value backup package:
+ * - Bind to agent.
+ * - Call agent.doBackup()
+ * - Wait either for cancel/timeout or operationComplete() callback from the agent.
+ * Start task to perform dolly backups.
+ *
+ * There are three entry points into this class:
+ * - execute() [Called from the handler thread]
+ * - operationComplete(long result) [Called from the handler thread]
+ * - handleCancel(boolean cancelAll) [Can be called from any thread]
+ * These methods synchronize on mCancelLock.
+ *
+ * Interaction with mCurrentOperations:
+ * - An entry for this task is put into mCurrentOperations for the entire lifetime of the
+ * task. This is useful to cancel the task if required.
+ * - An ephemeral entry is put into mCurrentOperations each time we are waiting on for
+ * response from a backup agent. This is used to plumb timeouts and completion callbacks.
+ */
class PerformBackupTask implements BackupRestoreTask {
private static final String TAG = "PerformBackupTask";
+ private final Object mCancelLock = new Object();
+
IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
ArrayList<BackupRequest> mOriginalQueue;
@@ -2477,6 +2581,10 @@ public class BackupManagerService {
IBackupObserver mObserver;
IBackupManagerMonitor mMonitor;
+ private final PerformFullTransportBackupTask mFullBackupTask;
+ private final int mCurrentOpToken;
+ private volatile int mEphemeralOpToken;
+
// carried information about the current in-flight operation
IBackupAgent mAgentBinder;
PackageInfo mCurrentPackage;
@@ -2491,6 +2599,8 @@ public class BackupManagerService {
final boolean mUserInitiated;
final boolean mNonIncremental;
+ private volatile boolean mCancelAll;
+
public PerformBackupTask(IBackupTransport transport, String dirName,
ArrayList<BackupRequest> queue, File journal, IBackupObserver observer,
IBackupManagerMonitor monitor, ArrayList<String> pendingFullBackups,
@@ -2505,33 +2615,63 @@ public class BackupManagerService {
mNonIncremental = nonIncremental;
mStateDir = new File(mBaseStateDir, dirName);
+ mCurrentOpToken = generateToken();
mCurrentState = BackupState.INITIAL;
mFinished = false;
+ CountDownLatch latch = new CountDownLatch(1);
+ String[] fullBackups =
+ mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]);
+ mFullBackupTask =
+ new PerformFullTransportBackupTask(/*fullBackupRestoreObserver*/ null,
+ fullBackups, /*updateSchedule*/ false, /*runningJob*/ null, latch,
+ mObserver, mMonitor,mUserInitiated);
+
+ registerTask();
addBackupTrace("STATE => INITIAL");
}
+ /**
+ * Put this task in the repository of running tasks.
+ */
+ private void registerTask() {
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.put(mCurrentOpToken, new Operation(OP_PENDING, this,
+ OP_TYPE_BACKUP));
+ }
+ }
+
+ /**
+ * Remove this task from repository of running tasks.
+ */
+ private void unregisterTask() {
+ removeOperation(mCurrentOpToken);
+ }
+
// Main entry point: perform one chunk of work, updating the state as appropriate
// and reposting the next chunk to the primary backup handler thread.
@Override
+ @GuardedBy("mCancelLock")
public void execute() {
- switch (mCurrentState) {
- case INITIAL:
- beginBackup();
- break;
+ synchronized (mCancelLock) {
+ switch (mCurrentState) {
+ case INITIAL:
+ beginBackup();
+ break;
- case RUNNING_QUEUE:
- invokeNextAgent();
- break;
+ case RUNNING_QUEUE:
+ invokeNextAgent();
+ break;
- case FINAL:
- if (!mFinished) finalizeBackup();
- else {
- Slog.e(TAG, "Duplicate finish");
- }
- mFinished = true;
- break;
+ case FINAL:
+ if (!mFinished) finalizeBackup();
+ else {
+ Slog.e(TAG, "Duplicate finish");
+ }
+ mFinished = true;
+ break;
+ }
}
}
@@ -2796,6 +2936,12 @@ public class BackupManagerService {
void finalizeBackup() {
addBackupTrace("finishing");
+ // Mark packages that we didn't backup (because backup was cancelled, etc.) as needing
+ // backup.
+ for (BackupRequest req : mQueue) {
+ dataChangedImpl(req.packageName);
+ }
+
// Either backup was successful, in which case we of course do not need
// this pass's journal any more; or it failed, in which case we just
// re-enqueued all of these packages in the current active journal.
@@ -2850,7 +2996,9 @@ public class BackupManagerService {
clearBackupTrace();
- if (mStatus == BackupTransport.TRANSPORT_OK &&
+ unregisterTask();
+
+ if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK &&
mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
CountDownLatch latch = new CountDownLatch(1);
@@ -2862,8 +3010,12 @@ public class BackupManagerService {
mObserver, mMonitor, mUserInitiated);
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
mWakelock.acquire();
- (new Thread(task, "full-transport-requested")).start();
+ (new Thread(mFullBackupTask, "full-transport-requested")).start();
+ } else if (mCancelAll) {
+ mFullBackupTask.unregisterTask();
+ sendBackupFinished(mObserver, BackupManager.ERROR_BACKUP_CANCELLED);
} else {
+ mFullBackupTask.unregisterTask();
switch (mStatus) {
case BackupTransport.TRANSPORT_OK:
sendBackupFinished(mObserver, BackupManager.SUCCESS);
@@ -2905,8 +3057,8 @@ public class BackupManagerService {
mBackupData = null;
mNewState = null;
- final int token = generateToken();
boolean callingAgent = false;
+ mEphemeralOpToken = generateToken();
try {
// Look up the package info & signatures. This is first so that if it
// throws an exception, there's no file setup yet that would need to
@@ -2945,10 +3097,11 @@ public class BackupManagerService {
// Initiate the target's backup pass
addBackupTrace("setting timeout");
- prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, this);
+ prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_BACKUP_INTERVAL, this,
+ OP_TYPE_WAIT);
addBackupTrace("calling agent doBackup()");
- agent.doBackup(mSavedState, mBackupData, mNewState, quota, token,
+ agent.doBackup(mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
mBackupManagerBinder);
} catch (Exception e) {
Slog.e(TAG, "Error invoking for backup on " + packageName + ". " + e);
@@ -3060,195 +3213,225 @@ public class BackupManagerService {
}
@Override
+ @GuardedBy("mCancelLock")
public void operationComplete(long unusedResult) {
- // The agent reported back to us!
-
- if (mBackupData == null) {
- // This callback was racing with our timeout, so we've cleaned up the
- // agent state already and are on to the next thing. We have nothing
- // further to do here: agent state having been cleared means that we've
- // initiated the appropriate next operation.
- final String pkg = (mCurrentPackage != null)
- ? mCurrentPackage.packageName : "[none]";
- if (MORE_DEBUG) {
- Slog.i(TAG, "Callback after agent teardown: " + pkg);
+ removeOperation(mEphemeralOpToken);
+ synchronized (mCancelLock) {
+ // The agent reported back to us!
+ if (mFinished) {
+ Slog.d(TAG, "operationComplete received after task finished.");
+ return;
}
- addBackupTrace("late opComplete; curPkg = " + pkg);
- return;
- }
- final String pkgName = mCurrentPackage.packageName;
- final long filepos = mBackupDataName.length();
- FileDescriptor fd = mBackupData.getFileDescriptor();
- try {
- // If it's a 3rd party app, see whether they wrote any protected keys
- // and complain mightily if they are attempting shenanigans.
- if (mCurrentPackage.applicationInfo != null &&
- (mCurrentPackage.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
- ParcelFileDescriptor readFd = ParcelFileDescriptor.open(mBackupDataName,
- ParcelFileDescriptor.MODE_READ_ONLY);
- BackupDataInput in = new BackupDataInput(readFd.getFileDescriptor());
- try {
- while (in.readNextHeader()) {
- final String key = in.getKey();
- if (key != null && key.charAt(0) >= 0xff00) {
- // Not okay: crash them and bail.
- failAgent(mAgentBinder, "Illegal backup key: " + key);
- addBackupTrace("illegal key " + key + " from " + pkgName);
- EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
- "bad key");
- mBackupHandler.removeMessages(MSG_TIMEOUT);
- sendBackupOnPackageResult(mObserver, pkgName,
- BackupManager.ERROR_AGENT_FAILURE);
- errorCleanup();
- // errorCleanup() implicitly executes next state properly
- return;
- }
- in.skipEntityData();
- }
- } finally {
- if (readFd != null) {
- readFd.close();
- }
+ if (mBackupData == null) {
+ // This callback was racing with our timeout, so we've cleaned up the
+ // agent state already and are on to the next thing. We have nothing
+ // further to do here: agent state having been cleared means that we've
+ // initiated the appropriate next operation.
+ final String pkg = (mCurrentPackage != null)
+ ? mCurrentPackage.packageName : "[none]";
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Callback after agent teardown: " + pkg);
}
+ addBackupTrace("late opComplete; curPkg = " + pkg);
+ return;
}
- // Piggyback the widget state payload, if any
- writeWidgetPayloadIfAppropriate(fd, pkgName);
- } catch (IOException e) {
- // Hard disk error; recovery/failure policy TBD. For now roll back,
- // but we may want to consider this a transport-level failure (i.e.
- // we're in such a bad state that we can't contemplate doing backup
- // operations any more during this pass).
- Slog.w(TAG, "Unable to save widget state for " + pkgName);
+ final String pkgName = mCurrentPackage.packageName;
+ final long filepos = mBackupDataName.length();
+ FileDescriptor fd = mBackupData.getFileDescriptor();
try {
- Os.ftruncate(fd, filepos);
- } catch (ErrnoException ee) {
- Slog.w(TAG, "Unable to roll back!");
+ // If it's a 3rd party app, see whether they wrote any protected keys
+ // and complain mightily if they are attempting shenanigans.
+ if (mCurrentPackage.applicationInfo != null &&
+ (mCurrentPackage.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ == 0) {
+ ParcelFileDescriptor readFd = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ BackupDataInput in = new BackupDataInput(readFd.getFileDescriptor());
+ try {
+ while (in.readNextHeader()) {
+ final String key = in.getKey();
+ if (key != null && key.charAt(0) >= 0xff00) {
+ // Not okay: crash them and bail.
+ failAgent(mAgentBinder, "Illegal backup key: " + key);
+ addBackupTrace("illegal key " + key + " from " + pkgName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
+ "bad key");
+ mBackupHandler.removeMessages(MSG_TIMEOUT);
+ sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_AGENT_FAILURE);
+ errorCleanup();
+ // agentErrorCleanup() implicitly executes next state properly
+ return;
+ }
+ in.skipEntityData();
+ }
+ } finally {
+ if (readFd != null) {
+ readFd.close();
+ }
+ }
+ }
+
+ // Piggyback the widget state payload, if any
+ writeWidgetPayloadIfAppropriate(fd, pkgName);
+ } catch (IOException e) {
+ // Hard disk error; recovery/failure policy TBD. For now roll back,
+ // but we may want to consider this a transport-level failure (i.e.
+ // we're in such a bad state that we can't contemplate doing backup
+ // operations any more during this pass).
+ Slog.w(TAG, "Unable to save widget state for " + pkgName);
+ try {
+ Os.ftruncate(fd, filepos);
+ } catch (ErrnoException ee) {
+ Slog.w(TAG, "Unable to roll back!");
+ }
}
- }
- // Spin the data off to the transport and proceed with the next stage.
- if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
- + pkgName);
- mBackupHandler.removeMessages(MSG_TIMEOUT);
- clearAgentState();
- addBackupTrace("operation complete");
+ // Spin the data off to the transport and proceed with the next stage.
+ if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
+ + pkgName);
+ mBackupHandler.removeMessages(MSG_TIMEOUT);
+ clearAgentState();
+ addBackupTrace("operation complete");
- ParcelFileDescriptor backupData = null;
- mStatus = BackupTransport.TRANSPORT_OK;
- long size = 0;
- try {
- size = mBackupDataName.length();
- if (size > 0) {
- if (mStatus == BackupTransport.TRANSPORT_OK) {
- backupData = ParcelFileDescriptor.open(mBackupDataName,
- ParcelFileDescriptor.MODE_READ_ONLY);
- addBackupTrace("sending data to transport");
- int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
- mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
- }
+ ParcelFileDescriptor backupData = null;
+ mStatus = BackupTransport.TRANSPORT_OK;
+ long size = 0;
+ try {
+ size = mBackupDataName.length();
+ if (size > 0) {
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ backupData = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ addBackupTrace("sending data to transport");
+ int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+ mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+ }
- // TODO - We call finishBackup() for each application backed up, because
- // we need to know now whether it succeeded or failed. Instead, we should
- // hold off on finishBackup() until the end, which implies holding off on
- // renaming *all* the output state files (see below) until that happens.
+ // TODO - We call finishBackup() for each application backed up, because
+ // we need to know now whether it succeeded or failed. Instead, we should
+ // hold off on finishBackup() until the end, which implies holding off on
+ // renaming *all* the output state files (see below) until that happens.
+
+ addBackupTrace("data delivered: " + mStatus);
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ addBackupTrace("finishing op on transport");
+ mStatus = mTransport.finishBackup();
+ addBackupTrace("finished: " + mStatus);
+ } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ addBackupTrace("transport rejected package");
+ }
+ } else {
+ if (MORE_DEBUG) Slog.i(TAG,
+ "no backup data written; not calling transport");
+ addBackupTrace("no data to send");
+ }
- addBackupTrace("data delivered: " + mStatus);
if (mStatus == BackupTransport.TRANSPORT_OK) {
- addBackupTrace("finishing op on transport");
- mStatus = mTransport.finishBackup();
- addBackupTrace("finished: " + mStatus);
+ // After successful transport, delete the now-stale data
+ // and juggle the files so that next time we supply the agent
+ // with the new state file it just created.
+ mBackupDataName.delete();
+ mNewStateName.renameTo(mSavedStateName);
+ sendBackupOnPackageResult(mObserver, pkgName, BackupManager.SUCCESS);
+ EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
+ logBackupComplete(pkgName);
} else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
- addBackupTrace("transport rejected package");
+ // The transport has rejected backup of this specific package. Roll it
+ // back but proceed with running the rest of the queue.
+ mBackupDataName.delete();
+ mNewStateName.delete();
+ sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
+ EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
+ } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+ sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
+ EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
+ } else {
+ // Actual transport-level failure to communicate the data to the backend
+ sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
}
- } else {
- if (MORE_DEBUG) Slog.i(TAG, "no backup data written; not calling transport");
- addBackupTrace("no data to send");
- }
-
- if (mStatus == BackupTransport.TRANSPORT_OK) {
- // After successful transport, delete the now-stale data
- // and juggle the files so that next time we supply the agent
- // with the new state file it just created.
- mBackupDataName.delete();
- mNewStateName.renameTo(mSavedStateName);
- sendBackupOnPackageResult(mObserver, pkgName, BackupManager.SUCCESS);
- EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
- logBackupComplete(pkgName);
- } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
- // The transport has rejected backup of this specific package. Roll it
- // back but proceed with running the rest of the queue.
- mBackupDataName.delete();
- mNewStateName.delete();
- sendBackupOnPackageResult(mObserver, pkgName,
- BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
- EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
- } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
- sendBackupOnPackageResult(mObserver, pkgName,
- BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
- EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
- } else {
- // Actual transport-level failure to communicate the data to the backend
+ } catch (Exception e) {
sendBackupOnPackageResult(mObserver, pkgName,
BackupManager.ERROR_TRANSPORT_ABORTED);
+ Slog.e(TAG, "Transport error backing up " + pkgName, e);
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ } finally {
+ try {
+ if (backupData != null) backupData.close();
+ } catch (IOException e) {
+ }
}
- } catch (Exception e) {
- sendBackupOnPackageResult(mObserver, pkgName,
- BackupManager.ERROR_TRANSPORT_ABORTED);
- Slog.e(TAG, "Transport error backing up " + pkgName, e);
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
- mStatus = BackupTransport.TRANSPORT_ERROR;
- } finally {
- try { if (backupData != null) backupData.close(); } catch (IOException e) {}
- }
- final BackupState nextState;
- if (mStatus == BackupTransport.TRANSPORT_OK
- || mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
- // Success or single-package rejection. Proceed with the next app if any,
- // otherwise we're done.
- nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
- } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
- if (MORE_DEBUG) {
- Slog.d(TAG, "Package " + mCurrentPackage.packageName +
- " hit quota limit on k/v backup");
- }
- if (mAgentBinder != null) {
- try {
- long quota = mTransport.getBackupQuota(mCurrentPackage.packageName, false);
- mAgentBinder.doQuotaExceeded(size, quota);
- } catch (Exception e) {
- Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
+ final BackupState nextState;
+ if (mStatus == BackupTransport.TRANSPORT_OK
+ || mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ // Success or single-package rejection. Proceed with the next app if any,
+ // otherwise we're done.
+ nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+ } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Package " + mCurrentPackage.packageName +
+ " hit quota limit on k/v backup");
+ }
+ if (mAgentBinder != null) {
+ try {
+ long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
+ false);
+ mAgentBinder.doQuotaExceeded(size, quota);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
+ }
}
+ nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+ } else {
+ // Any other error here indicates a transport-level failure. That means
+ // we need to halt everything and reschedule everything for next time.
+ revertAndEndBackup();
+ nextState = BackupState.FINAL;
}
- nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
- } else {
- // Any other error here indicates a transport-level failure. That means
- // we need to halt everything and reschedule everything for next time.
- revertAndEndBackup();
- nextState = BackupState.FINAL;
- }
- executeNextState(nextState);
+ executeNextState(nextState);
+ }
}
+
@Override
- public void handleTimeout() {
- // Whoops, the current agent timed out running doBackup(). Tidy up and restage
- // it for the next time we run a backup pass.
- // !!! TODO: keep track of failure counts per agent, and blacklist those which
- // fail repeatedly (i.e. have proved themselves to be buggy).
- Slog.e(TAG, "Timeout backing up " + mCurrentPackage.packageName);
- EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName,
- "timeout");
- mMonitor = monitorEvent(mMonitor,
- BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT,
- mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
- addBackupTrace("timeout of " + mCurrentPackage.packageName);
- errorCleanup();
- dataChangedImpl(mCurrentPackage.packageName);
+ @GuardedBy("mCancelLock")
+ public void handleCancel(boolean cancelAll) {
+ removeOperation(mEphemeralOpToken);
+ synchronized (mCancelLock) {
+ if (mFinished) {
+ // We have already cancelled this operation.
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Ignoring stale cancel. cancelAll=" + cancelAll);
+ }
+ return;
+ }
+ mCancelAll = cancelAll;
+ // Whoops, the current agent timed out running doBackup(). Tidy up and restage
+ // it for the next time we run a backup pass.
+ // !!! TODO: keep track of failure counts per agent, and blacklist those which
+ // fail repeatedly (i.e. have proved themselves to be buggy).
+ Slog.e(TAG, "Cancel backing up " + mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName);
+ addBackupTrace(
+ "cancel of " + mCurrentPackage.packageName + ", cancelAll=" + cancelAll);
+ errorCleanup();
+ if (!cancelAll) {
+ executeNextState(
+ mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
+ dataChangedImpl(mCurrentPackage.packageName);
+ } else {
+ finalizeBackup();
+ }
+ }
}
void revertAndEndBackup() {
@@ -3276,8 +3459,6 @@ public class BackupManagerService {
mBackupDataName.delete();
mNewStateName.delete();
clearAgentState();
-
- executeNextState(mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
}
// Cleanup common to both success and failure cases
@@ -3289,7 +3470,7 @@ public class BackupManagerService {
// Current-operation callback handling requires the validity of these various
// bits of internal state as an invariant of the operation still being live.
// This means we make sure to clear all of the state in unison inside the lock.
- mCurrentOperations.clear();
+ mCurrentOperations.remove(mEphemeralOpToken);
mSavedState = mBackupData = mNewState = null;
}
@@ -3343,7 +3524,7 @@ public class BackupManagerService {
try {
pipes = ParcelFileDescriptor.createPipe();
int token = generateToken();
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null, OP_TYPE_WAIT);
mService.backupObbs(pkg.packageName, pipes[1], token, mBackupManagerBinder);
routeSocketDataToOutput(pipes[0], out);
success = waitUntilOperationComplete(token);
@@ -3481,6 +3662,7 @@ public class BackupManagerService {
boolean mIncludeApks;
PackageInfo mPkg;
private final long mQuota;
+ private final int mOpToken;
class FullBackupRunner implements Runnable {
PackageInfo mPackage;
@@ -3535,7 +3717,7 @@ public class BackupManagerService {
if (DEBUG) Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName);
prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL,
- mTimeoutMonitor /* in parent class */);
+ mTimeoutMonitor /* in parent class */, OP_TYPE_WAIT);
mAgent.doFullBackup(mPipe, mQuota, mToken, mBackupManagerBinder);
} catch (IOException e) {
Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
@@ -3551,7 +3733,7 @@ public class BackupManagerService {
}
FullBackupEngine(OutputStream output, FullBackupPreflight preflightHook, PackageInfo pkg,
- boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota) {
+ boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken) {
mOutput = output;
mPreflightHook = preflightHook;
mPkg = pkg;
@@ -3561,6 +3743,7 @@ public class BackupManagerService {
mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
mQuota = quota;
+ mOpToken = opToken;
}
public int preflightCheck() throws RemoteException {
@@ -3603,9 +3786,8 @@ public class BackupManagerService {
byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(mPkg.packageName,
UserHandle.USER_SYSTEM);
- final int token = generateToken();
FullBackupRunner runner = new FullBackupRunner(mPkg, mAgent, pipes[1],
- token, sendApk, !isSharedStorage, widgetBlob);
+ mOpToken, sendApk, !isSharedStorage, widgetBlob);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
Thread t = new Thread(runner, "app-data-runner");
@@ -3614,7 +3796,7 @@ public class BackupManagerService {
// Now pull data from the app and stuff it into the output
routeSocketDataToOutput(pipes[0], mOutput);
- if (!waitUntilOperationComplete(token)) {
+ if (!waitUntilOperationComplete(mOpToken)) {
Slog.e(TAG, "Full backup failed on package " + mPkg.packageName);
} else {
if (MORE_DEBUG) {
@@ -3865,12 +4047,14 @@ public class BackupManagerService {
PackageInfo mCurrentTarget;
String mCurrentPassword;
String mEncryptPassword;
+ private final int mCurrentOpToken;
PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
boolean includeApks, boolean includeObbs, boolean includeShared,
boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) {
super(observer);
+ mCurrentOpToken = generateToken();
mLatch = latch;
mOutputFile = fd;
@@ -4156,8 +4340,7 @@ public class BackupManagerService {
final boolean isSharedStorage =
pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
- mBackupEngine = new FullBackupEngine(out, null, pkg, mIncludeApks,
- this /* BackupRestoreTask */, Long.MAX_VALUE /* quota */);
+ mBackupEngine = new FullBackupEngine(out, null, pkg, mIncludeApks, this, Long.MAX_VALUE, mCurrentOpToken);
sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
// Don't need to check preflight result as there is no preflight hook.
@@ -4190,9 +4373,6 @@ public class BackupManagerService {
} catch (IOException e) {
/* nothing we can do about this */
}
- synchronized (mCurrentOpLock) {
- mCurrentOperations.clear();
- }
synchronized (mLatch) {
mLatch.set(true);
mLatch.notifyAll();
@@ -4216,29 +4396,68 @@ public class BackupManagerService {
}
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
final PackageInfo target = mCurrentTarget;
if (DEBUG) {
- Slog.w(TAG, "adb backup timeout of " + target);
+ Slog.w(TAG, "adb backup cancel of " + target);
}
if (target != null) {
tearDownAgentAndKill(mCurrentTarget.applicationInfo);
}
+ removeOperation(mCurrentOpToken);
}
}
- // Full backup task extension used for transport-oriented operation
- class PerformFullTransportBackupTask extends FullBackupTask {
+ /**
+ * Full backup task extension used for transport-oriented operation.
+ *
+ * Flow:
+ * For each requested package:
+ * - Spin off a new SinglePackageBackupRunner (mBackupRunner) for the current package.
+ * - Wait until preflight is complete. (mBackupRunner.getPreflightResultBlocking())
+ * - If preflight data size is within limit, start reading data from agent pipe and writing
+ * to transport pipe. While there is data to send, call transport.sendBackupData(int) to
+ * tell the transport how many bytes to expect on its pipe.
+ * - After sending all data, call transport.finishBackup() if things went well. And
+ * transport.cancelFullBackup() otherwise.
+ *
+ * Interactions with mCurrentOperations:
+ * - An entry for this object is added to mCurrentOperations for the entire lifetime of this
+ * object. Used to cancel the operation.
+ * - SinglePackageBackupRunner and SinglePackageBackupPreflight will put ephemeral entries
+ * to get timeouts or operation complete callbacks.
+ *
+ * Handling cancels:
+ * - The contract we provide is that the task won't interact with the transport after
+ * handleCancel() is done executing.
+ * - This task blocks at 3 points: 1. Preflight result check 2. Reading on agent side pipe
+ * and 3. Get backup result from mBackupRunner.
+ * - Bubbling up handleCancel to mBackupRunner handles all 3: 1. Calls handleCancel on the
+ * preflight operation which counts down on the preflight latch. 2. Tears down the agent,
+ * so read() returns -1. 3. Notifies mCurrentOpLock which unblocks
+ * mBackupRunner.getBackupResultBlocking().
+ */
+ class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
static final String TAG = "PFTBT";
+
+ private final Object mCancelLock = new Object();
+
ArrayList<PackageInfo> mPackages;
PackageInfo mCurrentPackage;
boolean mUpdateSchedule;
CountDownLatch mLatch;
- AtomicBoolean mKeepRunning; // signal from job scheduler
FullBackupJob mJob; // if a scheduled job needs to be finished afterwards
IBackupObserver mBackupObserver;
IBackupManagerMonitor mMonitor;
boolean mUserInitiated;
+ private volatile IBackupTransport mTransport;
+ SinglePackageBackupRunner mBackupRunner;
+ private final int mBackupRunnerOpToken;
+
+ // This is true when a backup operation for some package is in progress.
+ private volatile boolean mIsDoingBackup;
+ private volatile boolean mCancelAll;
+ private final int mCurrentOpToken;
PerformFullTransportBackupTask(IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
@@ -4247,12 +4466,15 @@ public class BackupManagerService {
super(observer);
mUpdateSchedule = updateSchedule;
mLatch = latch;
- mKeepRunning = new AtomicBoolean(true);
mJob = runningJob;
mPackages = new ArrayList<PackageInfo>(whichPackages.length);
mBackupObserver = backupObserver;
mMonitor = monitor;
mUserInitiated = userInitiated;
+ mCurrentOpToken = generateToken();
+ mBackupRunnerOpToken = generateToken();
+
+ registerTask();
for (String pkg : whichPackages) {
try {
@@ -4298,12 +4520,60 @@ public class BackupManagerService {
}
}
- public void setRunning(boolean running) {
- mKeepRunning.set(running);
+ private void registerTask() {
+ synchronized (mCurrentOpLock) {
+ Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
+ mCurrentOperations.put(mCurrentOpToken, new Operation(OP_PENDING, this,
+ OP_TYPE_BACKUP));
+ }
+ }
+
+ private void unregisterTask() {
+ removeOperation(mCurrentOpToken);
+ }
+
+ @Override
+ public void execute() {
+ // Nothing to do.
+ }
+
+ @Override
+ public void handleCancel(boolean cancelAll) {
+ synchronized (mCancelLock) {
+ // We only support 'cancelAll = true' case for this task. Cancelling of a single package
+
+ // due to timeout is handled by SinglePackageBackupRunner and SinglePackageBackupPreflight.
+
+ if (!cancelAll) {
+ Slog.wtf(TAG, "Expected cancelAll to be true.");
+ }
+
+ if (mCancelAll) {
+ Slog.d(TAG, "Ignoring duplicate cancel call.");
+ return;
+ }
+
+ mCancelAll = true;
+ if (mIsDoingBackup) {
+ BackupManagerService.this.handleCancel(mBackupRunnerOpToken, cancelAll);
+ try {
+ mTransport.cancelFullBackup();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e);
+ // Can't do much.
+ }
+ }
+ }
+ }
+
+ @Override
+ public void operationComplete(long result) {
+ // Nothing to do.
}
@Override
public void run() {
+
// data from the app, passed to us for bridging to the transport
ParcelFileDescriptor[] enginePipes = null;
@@ -4325,8 +4595,8 @@ public class BackupManagerService {
return;
}
- IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
- if (transport == null) {
+ mTransport = mTransportManager.getCurrentTransportBinder();
+ if (mTransport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
return;
@@ -4347,30 +4617,40 @@ public class BackupManagerService {
// Tell the transport the data's coming
int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
- int backupPackageStatus = transport.performFullBackup(currentPackage,
- transportPipes[0], flags);
+ int backupPackageStatus;
+ long quota = Long.MAX_VALUE;
+ synchronized (mCancelLock) {
+ if (mCancelAll) {
+ break;
+ }
+ backupPackageStatus = mTransport.performFullBackup(currentPackage,
+ transportPipes[0], flags);
+
+ if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+ quota = mTransport.getBackupQuota(currentPackage.packageName,
+ true /* isFullBackup */);
+ // Now set up the backup engine / data source end of things
+ enginePipes = ParcelFileDescriptor.createPipe();
+ mBackupRunner =
+ new SinglePackageBackupRunner(enginePipes[1], currentPackage,
+ mTransport, quota, mBackupRunnerOpToken);
+ // The runner dup'd the pipe half, so we close it here
+ enginePipes[1].close();
+ enginePipes[1] = null;
+
+ mIsDoingBackup = true;
+ }
+ }
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
- final long quota = transport.getBackupQuota(currentPackage.packageName,
- true /* isFullBackup */);
// The transport has its own copy of the read end of the pipe,
// so close ours now
transportPipes[0].close();
transportPipes[0] = null;
- // Now set up the backup engine / data source end of things
- enginePipes = ParcelFileDescriptor.createPipe();
-
- SinglePackageBackupRunner backupRunner =
- new SinglePackageBackupRunner(enginePipes[1], currentPackage,
- transport, quota);
- // The runner dup'd the pipe half, so we close it here
- enginePipes[1].close();
- enginePipes[1] = null;
-
// Spin off the runner to fetch the app's data and pipe it
// into the engine pipes
- (new Thread(backupRunner, "package-backup-bridge")).start();
+ (new Thread(mBackupRunner, "package-backup-bridge")).start();
// Read data off the engine pipe and pass it to the transport
// pipe until we hit EOD on the input stream. We do not take
@@ -4380,7 +4660,7 @@ public class BackupManagerService {
FileOutputStream out = new FileOutputStream(
transportPipes[1].getFileDescriptor());
long totalRead = 0;
- final long preflightResult = backupRunner.getPreflightResultBlocking();
+ final long preflightResult = mBackupRunner.getPreflightResultBlocking();
// Preflight result is negative if some error happened on preflight.
if (preflightResult < 0) {
if (MORE_DEBUG) {
@@ -4392,19 +4672,17 @@ public class BackupManagerService {
} else {
int nRead = 0;
do {
- if (!mKeepRunning.get()) {
- if (DEBUG_SCHEDULING) {
- Slog.i(TAG, "Full backup task told to stop");
- }
- break;
- }
nRead = in.read(buffer);
if (MORE_DEBUG) {
Slog.v(TAG, "in.read(buffer) from app: " + nRead);
}
if (nRead > 0) {
out.write(buffer, 0, nRead);
- backupPackageStatus = transport.sendBackupData(nRead);
+ synchronized (mCancelLock) {
+ if (!mCancelAll) {
+ backupPackageStatus = mTransport.sendBackupData(nRead);
+ }
+ }
totalRead += nRead;
if (mBackupObserver != null && preflightResult > 0) {
sendBackupOnUpdate(mBackupObserver, packageName,
@@ -4413,29 +4691,32 @@ public class BackupManagerService {
}
} while (nRead > 0
&& backupPackageStatus == BackupTransport.TRANSPORT_OK);
-
// Despite preflight succeeded, package still can hit quota on flight.
if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
Slog.w(TAG, "Package hit quota limit in-flight " + packageName
+ ": " + totalRead + " of " + quota);
- backupRunner.sendQuotaExceeded(totalRead, quota);
+ mBackupRunner.sendQuotaExceeded(totalRead, quota);
}
}
- // If we've lost our running criteria, tell the transport to cancel
- // and roll back this (partial) backup payload; otherwise tell it
- // that we've reached the clean finish state.
- if (!mKeepRunning.get()) {
- backupPackageStatus = BackupTransport.TRANSPORT_ERROR;
- transport.cancelFullBackup();
- } else {
- // If we were otherwise in a good state, now interpret the final
- // result based on what finishBackup() returns. If we're in a
- // failure case already, preserve that result and ignore whatever
- // finishBackup() reports.
- final int finishResult = transport.finishBackup();
- if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
- backupPackageStatus = finishResult;
+ final int backupRunnerResult = mBackupRunner.getBackupResultBlocking();
+
+ synchronized (mCancelLock) {
+ mIsDoingBackup = false;
+ // If mCancelCurrent is true, we have already called cancelFullBackup().
+ if (!mCancelAll) {
+ if (backupRunnerResult == BackupTransport.TRANSPORT_OK) {
+ // If we were otherwise in a good state, now interpret the final
+ // result based on what finishBackup() returns. If we're in a
+ // failure case already, preserve that result and ignore whatever
+ // finishBackup() reports.
+ final int finishResult = mTransport.finishBackup();
+ if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+ backupPackageStatus = finishResult;
+ }
+ } else {
+ mTransport.cancelFullBackup();
+ }
}
}
@@ -4449,8 +4730,7 @@ public class BackupManagerService {
// errors take precedence over agent/app-specific errors for purposes of
// determining our course of action.
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
- // We still could fail in backup runner thread, getting result from there.
- int backupRunnerResult = backupRunner.getBackupResultBlocking();
+ // We still could fail in backup runner thread.
if (backupRunnerResult != BackupTransport.TRANSPORT_OK) {
// If there was an error in runner thread and
// not TRANSPORT_ERROR here, overwrite it.
@@ -4474,7 +4754,7 @@ public class BackupManagerService {
// Also ask the transport how long it wants us to wait before
// moving on to the next package, if any.
- backoff = transport.requestFullBackupTime();
+ backoff = mTransport.requestFullBackupTime();
if (DEBUG_SCHEDULING) {
Slog.i(TAG, "Transport suggested backoff=" + backoff);
}
@@ -4513,6 +4793,14 @@ public class BackupManagerService {
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
+ } else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) {
+ sendBackupOnPackageResult(mBackupObserver, packageName,
+ BackupManager.ERROR_BACKUP_CANCELLED);
+ Slog.w(TAG, "Backup cancelled. package=" + packageName +
+ ", cancelAll=" + mCancelAll);
+ EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
+ tearDownAgentAndKill(currentPackage.applicationInfo);
+ // Do nothing, clean up, and continue looping.
} else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.ERROR_TRANSPORT_ABORTED);
@@ -4542,6 +4830,11 @@ public class BackupManagerService {
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
Slog.w(TAG, "Exception trying full transport backup", e);
} finally {
+
+ if (mCancelAll) {
+ backupRunStatus = BackupManager.ERROR_BACKUP_CANCELLED;
+ }
+
if (DEBUG) {
Slog.i(TAG, "Full backup completed with status: " + backupRunStatus);
}
@@ -4550,6 +4843,8 @@ public class BackupManagerService {
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
+ unregisterTask();
+
if (mJob != null) {
mJob.finishBackupPass();
}
@@ -4565,6 +4860,7 @@ public class BackupManagerService {
if (mUpdateSchedule) {
scheduleNextFullBackupJob(backoff);
}
+
Slog.i(BackupManagerService.TAG, "Full data backup pass finished.");
mWakelock.release();
}
@@ -4601,23 +4897,24 @@ public class BackupManagerService {
final CountDownLatch mLatch = new CountDownLatch(1);
final IBackupTransport mTransport;
final long mQuota;
+ private final int mCurrentOpToken;
- public SinglePackageBackupPreflight(IBackupTransport transport, long quota) {
+ SinglePackageBackupPreflight(IBackupTransport transport, long quota, int currentOpToken) {
mTransport = transport;
mQuota = quota;
+ mCurrentOpToken = currentOpToken;
}
@Override
public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) {
int result;
try {
- final int token = generateToken();
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, this);
+ prepareOperationTimeout(mCurrentOpToken, TIMEOUT_FULL_BACKUP_INTERVAL, this, OP_TYPE_WAIT);
addBackupTrace("preflighting");
if (MORE_DEBUG) {
Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
}
- agent.doMeasureFullBackup(mQuota, token, mBackupManagerBinder);
+ agent.doMeasureFullBackup(mQuota, mCurrentOpToken, mBackupManagerBinder);
// Now wait to get our result back. If this backstop timeout is reached without
// the latch being thrown, flow will continue as though a result or "normal"
@@ -4652,7 +4949,7 @@ public class BackupManagerService {
@Override
public void execute() {
- // Unused in this case
+ // Unused.
}
@Override
@@ -4663,15 +4960,17 @@ public class BackupManagerService {
}
mResult.set(result);
mLatch.countDown();
+ removeOperation(mCurrentOpToken);
}
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
if (MORE_DEBUG) {
- Slog.i(TAG, "Preflight timeout; failing");
+ Slog.i(TAG, "Preflight cancelled; failing");
}
mResult.set(BackupTransport.AGENT_ERROR);
mLatch.countDown();
+ removeOperation(mCurrentOpToken);
}
@Override
@@ -4688,43 +4987,67 @@ public class BackupManagerService {
class SinglePackageBackupRunner implements Runnable, BackupRestoreTask {
final ParcelFileDescriptor mOutput;
final PackageInfo mTarget;
- final FullBackupPreflight mPreflight;
+ final SinglePackageBackupPreflight mPreflight;
final CountDownLatch mPreflightLatch;
final CountDownLatch mBackupLatch;
+ private final int mCurrentOpToken;
+ private final int mEphemeralToken;
private FullBackupEngine mEngine;
private volatile int mPreflightResult;
private volatile int mBackupResult;
private final long mQuota;
+ private volatile boolean mIsCancelled;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
- IBackupTransport transport, long quota) throws IOException {
+ IBackupTransport transport, long quota, int currentOpToken) throws IOException {
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
- mPreflight = new SinglePackageBackupPreflight(transport, quota);
+ mCurrentOpToken = currentOpToken;
+ mEphemeralToken = generateToken();
+ mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken);
mPreflightLatch = new CountDownLatch(1);
mBackupLatch = new CountDownLatch(1);
mPreflightResult = BackupTransport.AGENT_ERROR;
mBackupResult = BackupTransport.AGENT_ERROR;
mQuota = quota;
+ registerTask();
+ }
+
+ void registerTask() {
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.put(mCurrentOpToken, new Operation(OP_PENDING, this,
+ OP_TYPE_WAIT));
+ }
+ }
+
+ void unregisterTask() {
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.remove(mCurrentOpToken);
+ }
}
@Override
public void run() {
FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
- mEngine = new FullBackupEngine(out, mPreflight, mTarget, false, this, mQuota);
+ mEngine = new FullBackupEngine(out, mPreflight, mTarget, false, this, mQuota, mCurrentOpToken);
try {
try {
- mPreflightResult = mEngine.preflightCheck();
+ if (!mIsCancelled) {
+ mPreflightResult = mEngine.preflightCheck();
+ }
} finally {
mPreflightLatch.countDown();
}
// If there is no error on preflight, continue backup.
if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
- mBackupResult = mEngine.backupOnePackage();
+ if (!mIsCancelled) {
+ mBackupResult = mEngine.backupOnePackage();
+ }
}
} catch (Exception e) {
Slog.e(TAG, "Exception during full package backup of " + mTarget.packageName);
} finally {
+ unregisterTask();
mBackupLatch.countDown();
try {
mOutput.close();
@@ -4743,6 +5066,9 @@ public class BackupManagerService {
long getPreflightResultBlocking() {
try {
mPreflightLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+ if (mIsCancelled) {
+ return BackupManager.ERROR_BACKUP_CANCELLED;
+ }
if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
return mPreflight.getExpectedSizeOrErrorCode();
} else {
@@ -4756,6 +5082,9 @@ public class BackupManagerService {
int getBackupResultBlocking() {
try {
mBackupLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+ if (mIsCancelled) {
+ return BackupManager.ERROR_BACKUP_CANCELLED;
+ }
return mBackupResult;
} catch (InterruptedException e) {
return BackupTransport.AGENT_ERROR;
@@ -4772,14 +5101,23 @@ public class BackupManagerService {
public void operationComplete(long result) { /* intentionally empty */ }
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
if (DEBUG) {
- Slog.w(TAG, "Full backup timeout of " + mTarget.packageName);
+ Slog.w(TAG, "Full backup cancel of " + mTarget.packageName);
}
+
mMonitor = monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_TIMEOUT,
mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
+ mIsCancelled = true;
+ // Cancel tasks spun off by this task.
+ BackupManagerService.this.handleCancel(mEphemeralToken, cancelAll);
tearDownAgentAndKill(mTarget.applicationInfo);
+ // Free up everyone waiting on this task and its children.
+ mPreflightLatch.countDown();
+ mBackupLatch.countDown();
+ // We are done with this operation.
+ removeOperation(mCurrentOpToken);
}
}
}
@@ -5057,7 +5395,7 @@ public class BackupManagerService {
if (DEBUG_SCHEDULING) {
Slog.i(TAG, "Telling running backup to stop");
}
- mRunningFullBackupTask.setRunning(false);
+ mRunningFullBackupTask.handleCancel(true);
}
}
}
@@ -5191,6 +5529,8 @@ public class BackupManagerService {
// Widget blob to be restored out-of-band
byte[] mWidgetData = null;
+ private final int mEphemeralOpToken;
+
// Runner that can be placed in a separate thread to do in-process
// invocations of the full restore API asynchronously. Used by adb restore.
class RestoreFileRunnable implements Runnable {
@@ -5226,7 +5566,9 @@ public class BackupManagerService {
}
public FullRestoreEngine(BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
- PackageInfo onlyPackage, boolean allowApks, boolean allowObbs) {
+ PackageInfo onlyPackage, boolean allowApks, boolean allowObbs,
+ int ephemeralOpToken) {
+ mEphemeralOpToken = ephemeralOpToken;
mMonitorTask = monitorTask;
mObserver = observer;
mOnlyPackage = onlyPackage;
@@ -5431,17 +5773,16 @@ public class BackupManagerService {
if (okay) {
boolean agentSuccess = true;
long toCopy = info.size;
- final int token = generateToken();
try {
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL,
- mMonitorTask);
+ prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_FULL_BACKUP_INTERVAL,
+ mMonitorTask, OP_TYPE_WAIT);
if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
+ " : " + info.path);
mObbConnection.restoreObbFile(pkg, mPipes[0],
info.size, info.type, info.path, info.mode,
- info.mtime, token, mBackupManagerBinder);
+ info.mtime, mEphemeralOpToken, mBackupManagerBinder);
} else {
if (MORE_DEBUG) Slog.d(TAG, "Invoking agent to restore file "
+ info.path);
@@ -5452,12 +5793,12 @@ public class BackupManagerService {
if (mTargetApp.processName.equals("system")) {
Slog.d(TAG, "system process agent - spinning a thread");
RestoreFileRunnable runner = new RestoreFileRunnable(
- mAgent, info, mPipes[0], token);
+ mAgent, info, mPipes[0], mEphemeralOpToken);
new Thread(runner, "restore-sys-runner").start();
} else {
mAgent.doRestoreFile(mPipes[0], info.size, info.type,
info.domain, info.path, info.mode, info.mtime,
- token, mBackupManagerBinder);
+ mEphemeralOpToken, mBackupManagerBinder);
}
}
} catch (IOException e) {
@@ -5509,7 +5850,7 @@ public class BackupManagerService {
// and now that we've sent it all, wait for the remote
// side to acknowledge receipt
- agentSuccess = waitUntilOperationComplete(token);
+ agentSuccess = waitUntilOperationComplete(mEphemeralOpToken);
}
// okay, if the remote end failed at any point, deal with
@@ -6354,9 +6695,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
class AdbRestoreFinishedLatch implements BackupRestoreTask {
static final String TAG = "AdbRestoreFinishedLatch";
final CountDownLatch mLatch;
+ private final int mCurrentOpToken;
- AdbRestoreFinishedLatch() {
+ AdbRestoreFinishedLatch(int currentOpToken) {
mLatch = new CountDownLatch(1);
+ mCurrentOpToken = currentOpToken;
}
void await() {
@@ -6379,14 +6722,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
Slog.w(TAG, "adb onRestoreFinished() complete");
}
mLatch.countDown();
+ removeOperation(mCurrentOpToken);
}
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
if (DEBUG) {
Slog.w(TAG, "adb onRestoreFinished() timed out");
}
mLatch.countDown();
+ removeOperation(mCurrentOpToken);
}
}
@@ -6577,9 +6922,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
Slog.w(TAG, "Close of restore data pipe threw", e);
/* nothing we can do about this */
}
- synchronized (mCurrentOpLock) {
- mCurrentOperations.clear();
- }
synchronized (mLatchObject) {
mLatchObject.set(true);
mLatchObject.notifyAll();
@@ -6868,7 +7210,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
long toCopy = info.size;
final int token = generateToken();
try {
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null,
+ OP_TYPE_WAIT);
if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
+ " : " + info.path);
@@ -7003,8 +7346,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// In the adb restore case, we do restore-finished here
if (doRestoreFinished) {
final int token = generateToken();
- final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch();
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, latch);
+ final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(token);
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, latch,
+ OP_TYPE_WAIT);
if (mTargetApp.processName.equals("system")) {
if (MORE_DEBUG) {
Slog.d(TAG, "system agent - restoreFinished on thread");
@@ -7872,11 +8216,14 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
ParcelFileDescriptor mBackupData;
ParcelFileDescriptor mNewState;
+ private final int mEphemeralOpToken;
+
// Invariant: mWakelock is already held, and this task is responsible for
// releasing it at the end of the restore operation.
PerformUnifiedRestoreTask(IBackupTransport transport, IRestoreObserver observer,
IBackupManagerMonitor monitor, long restoreSetToken, PackageInfo targetPackage,
int pmToken, boolean isFullSystemRestore, String[] filterSet) {
+ mEphemeralOpToken = generateToken();
mState = UnifiedRestoreState.INITIAL;
mStartRealtime = SystemClock.elapsedRealtime();
@@ -8304,7 +8651,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
ParcelFileDescriptor stage;
File downloadFile = (staging) ? mStageName : mBackupDataName;
- final int token = generateToken();
try {
// Run the transport's restore pass
stage = ParcelFileDescriptor.open(downloadFile,
@@ -8377,9 +8723,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// Kick off the restore, checking for hung agents. The timeout or
// the operationComplete() callback will schedule the next step,
// so we do not do that here.
- prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
+ prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_RESTORE_INTERVAL, this, OP_TYPE_WAIT);
mAgent.doRestore(mBackupData, appVersionCode, mNewState,
- token, mBackupManagerBinder);
+ mEphemeralOpToken, mBackupManagerBinder);
} catch (Exception e) {
Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
@@ -8426,9 +8772,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// state RESTORE_FINISHED : provide the "no more data" signpost callback at the end
private void restoreFinished() {
try {
- final int token = generateToken();
- prepareOperationTimeout(token, TIMEOUT_RESTORE_FINISHED_INTERVAL, this);
- mAgent.doRestoreFinished(token, mBackupManagerBinder);
+ prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_RESTORE_FINISHED_INTERVAL, this,
+ OP_TYPE_WAIT);
+ mAgent.doRestoreFinished(mEphemeralOpToken, mBackupManagerBinder);
// If we get this far, the callback or timeout will schedule the
// next restore state, so we're done
} catch (Exception e) {
@@ -8452,7 +8798,10 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// pipe through which the engine will read data. [0] read, [1] write
ParcelFileDescriptor[] mEnginePipes;
+ private final int mEphemeralOpToken;
+
public StreamFeederThread() throws IOException {
+ mEphemeralOpToken = generateToken();
mTransportPipes = ParcelFileDescriptor.createPipe();
mEnginePipes = ParcelFileDescriptor.createPipe();
setRunning(true);
@@ -8466,7 +8815,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
mCurrentPackage.packageName);
- mEngine = new FullRestoreEngine(this, null, mCurrentPackage, false, false);
+ mEngine = new FullRestoreEngine(this, null, mCurrentPackage, false, false, mEphemeralOpToken);
mEngineThread = new EngineThread(mEngine, mEnginePipes[0]);
ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
@@ -8605,7 +8954,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// The app has timed out handling a restoring file
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
+ removeOperation(mEphemeralOpToken);
if (DEBUG) {
Slog.w(TAG, "Full-data restore target timed out; shutting down");
}
@@ -8785,13 +9135,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// 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();
- }
}
@Override
public void operationComplete(long unusedResult) {
+ removeOperation(mEphemeralOpToken);
if (MORE_DEBUG) {
Slog.i(TAG, "operationComplete() during restore: target="
+ mCurrentPackage.packageName
@@ -8852,7 +9200,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// A call to agent.doRestore() or agent.doRestoreFinished() has timed out
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
+ removeOperation(mEphemeralOpToken);
Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
mMonitor = monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
@@ -9966,7 +10315,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// and ignore it; we've already dealt with the timeout.
op = null;
mCurrentOperations.delete(token);
- } else {
+ } else if (op.state == OP_ACKNOWLEDGED) {
+ if (DEBUG) {
+ Slog.w(TAG, "Received duplicate ack for token=" +
+ Integer.toHexString(token));
+ }
+ op = null;
+ mCurrentOperations.remove(token);
+ } else if (op.state == OP_PENDING) {
+ // Can't delete op from mCurrentOperations. waitUntilOperationComplete can be
+ // called after we we receive this call.
op.state = OP_ACKNOWLEDGED;
}
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index da0cee5aa4fa..8855661b954f 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -363,6 +363,14 @@ public class Trampoline extends IBackupManager.Stub {
}
@Override
+ public void cancelBackups() throws RemoteException {
+ BackupManagerService svc = mService;
+ if (svc != null) {
+ svc.cancelBackups();
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 45592548c319..2e61550614f2 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -132,6 +132,7 @@ option java_package com.android.server
2843 full_backup_success (Package|3)
2844 full_restore_package (Package|3)
2845 full_backup_quota_exceeded (Package|3)
+2846 full_backup_cancelled (Package|3),(Message|3)
2850 backup_transport_lifecycle (Transport|3),(Bound|1|1)