summaryrefslogtreecommitdiffstats
path: root/services/backup/java/com/android/server
diff options
context:
space:
mode:
Diffstat (limited to 'services/backup/java/com/android/server')
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java522
-rw-r--r--services/backup/java/com/android/server/backup/KeyValueBackupJob.java121
-rw-r--r--services/backup/java/com/android/server/backup/Trampoline.java6
3 files changed, 487 insertions, 162 deletions
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 4d7ebed..5cc59e5 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -22,12 +22,14 @@ import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.IApplicationThread;
import android.app.IBackupAgent;
+import android.app.PackageInstallObserver;
import android.app.PendingIntent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupTransport;
import android.app.backup.FullBackup;
+import android.app.backup.FullBackupDataOutput;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.app.backup.IBackupManager;
@@ -45,7 +47,6 @@ import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -134,6 +135,7 @@ import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.Deflater;
@@ -157,9 +159,9 @@ import libcore.io.IoUtils;
public class BackupManagerService {
private static final String TAG = "BackupManagerService";
- private static final boolean DEBUG = true;
- private static final boolean MORE_DEBUG = false;
- private static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;
+ static final boolean DEBUG = true;
+ static final boolean MORE_DEBUG = false;
+ static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;
// System-private key used for backing up an app's widget state. Must
// begin with U+FFxx by convention (we reserve all keys starting
@@ -195,23 +197,11 @@ public class BackupManagerService {
static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
- // How often we perform a backup pass. Privileged external callers can
- // trigger an immediate pass.
- private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR;
-
- // Random variation in backup scheduling time to avoid server load spikes
- private static final int FUZZ_MILLIS = 5 * 60 * 1000;
-
- // The amount of time between the initial provisioning of the device and
- // the first backup pass.
- private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR;
-
// Retry interval for clear/init when the transport is unavailable
private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
private static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
- private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR";
private static final int MSG_RUN_BACKUP = 1;
private static final int MSG_RUN_ADB_BACKUP = 2;
private static final int MSG_RUN_RESTORE = 3;
@@ -299,7 +289,6 @@ public class BackupManagerService {
volatile boolean mBackupRunning;
volatile boolean mConnecting;
volatile long mLastBackupPass;
- volatile long mNextBackupPass;
// For debugging, we maintain a progress trace of operations during backup
static final boolean DEBUG_BACKUP_TRACE = true;
@@ -381,8 +370,8 @@ public class BackupManagerService {
if (mProvisioned && !wasProvisioned && mEnabled) {
// we're now good to go, so start the backup alarms
if (MORE_DEBUG) Slog.d(TAG, "Now provisioned, so starting backups");
- startBackupAlarmsLocked(FIRST_BACKUP_INTERVAL);
- scheduleNextFullBackupJob();
+ KeyValueBackupJob.schedule(mContext);
+ scheduleNextFullBackupJob(0);
}
}
}
@@ -613,7 +602,7 @@ public class BackupManagerService {
return token;
}
- // High level policy: apps are ineligible for backup if certain conditions apply
+ // High level policy: apps are generally ineligible for backup if certain conditions apply
public static boolean appIsEligibleForBackup(ApplicationInfo app) {
// 1. their manifest states android:allowBackup="false"
if ((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
@@ -640,7 +629,7 @@ public class BackupManagerService {
return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0;
}
- // No agent means we do full backups for it
+ // No agent or fullBackupOnly="true" means we do indeed perform full-data backups for it
return true;
}
@@ -657,7 +646,6 @@ public class BackupManagerService {
case MSG_RUN_BACKUP:
{
mLastBackupPass = System.currentTimeMillis();
- mNextBackupPass = mLastBackupPass + BACKUP_INTERVAL;
IBackupTransport transport = getTransport(mCurrentTransport);
if (transport == null) {
@@ -740,7 +728,7 @@ public class BackupManagerService {
{
try {
BackupRestoreTask task = (BackupRestoreTask) msg.obj;
- task.operationComplete();
+ task.operationComplete(msg.arg1);
} catch (ClassCastException e) {
Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj);
}
@@ -1211,11 +1199,13 @@ public class BackupManagerService {
temp = new RandomAccessFile(tempProcessedFile, "rws");
in = new RandomAccessFile(mEverStored, "r");
+ // Loop until we hit EOF
while (true) {
- PackageInfo info;
String pkg = in.readUTF();
try {
- info = mPackageManager.getPackageInfo(pkg, 0);
+ // is this package still present?
+ mPackageManager.getPackageInfo(pkg, 0);
+ // if we get here then yes it is; remember it
mEverStoredApps.add(pkg);
temp.writeUTF(pkg);
if (MORE_DEBUG) Slog.v(TAG, " + " + pkg);
@@ -1279,7 +1269,23 @@ public class BackupManagerService {
for (int i = 0; i < N; i++) {
String pkgName = in.readUTF();
long lastBackup = in.readLong();
- schedule.add(new FullBackupEntry(pkgName, lastBackup));
+ try {
+ PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
+ if (appGetsFullBackup(pkg)
+ && appIsEligibleForBackup(pkg.applicationInfo)) {
+ schedule.add(new FullBackupEntry(pkgName, lastBackup));
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkgName
+ + " no longer eligible for full backup");
+ }
+ }
+ } catch (NameNotFoundException e) {
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkgName
+ + " not installed; dropping from full backup");
+ }
+ }
}
Collections.sort(schedule);
} catch (Exception e) {
@@ -1302,7 +1308,7 @@ public class BackupManagerService {
schedule = new ArrayList<FullBackupEntry>(N);
for (int i = 0; i < N; i++) {
PackageInfo info = apps.get(i);
- if (appGetsFullBackup(info)) {
+ if (appGetsFullBackup(info) && appIsEligibleForBackup(info.applicationInfo)) {
schedule.add(new FullBackupEntry(info.packageName, 0));
}
}
@@ -1774,13 +1780,13 @@ public class BackupManagerService {
addPackageParticipantsLocked(pkgList);
}
// If they're full-backup candidates, add them there instead
+ final long now = System.currentTimeMillis();
for (String packageName : pkgList) {
try {
PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
- long now = System.currentTimeMillis();
- if (appGetsFullBackup(app)) {
+ if (appGetsFullBackup(app) && appIsEligibleForBackup(app.applicationInfo)) {
enqueueFullBackup(packageName, now);
- scheduleNextFullBackupJob();
+ scheduleNextFullBackupJob(0);
}
// Transport maintenance: rebind to known existing transports that have
@@ -2220,7 +2226,7 @@ public class BackupManagerService {
void execute();
// An operation that wanted a callback has completed
- void operationComplete();
+ void operationComplete(int result);
// An operation that wanted a callback has timed out
void handleTimeout();
@@ -2475,7 +2481,7 @@ public class BackupManagerService {
BackupRequest request = mQueue.get(0);
mQueue.remove(0);
- Slog.d(TAG, "starting agent for backup of " + request);
+ Slog.d(TAG, "starting key/value backup of " + request);
addBackupTrace("launch agent for " + request.packageName);
// Verify that the requested app exists; it might be something that
@@ -2486,13 +2492,24 @@ public class BackupManagerService {
try {
mCurrentPackage = mPackageManager.getPackageInfo(request.packageName,
PackageManager.GET_SIGNATURES);
- if (mCurrentPackage.applicationInfo.backupAgentName == null) {
+ if (!appIsEligibleForBackup(mCurrentPackage.applicationInfo)) {
// The manifest has changed but we had a stale backup request pending.
// This won't happen again because the app won't be requesting further
// backups.
Slog.i(TAG, "Package " + request.packageName
+ " no longer supports backup; skipping");
- addBackupTrace("skipping - no agent, completion is noop");
+ addBackupTrace("skipping - not eligible, completion is noop");
+ executeNextState(BackupState.RUNNING_QUEUE);
+ return;
+ }
+
+ if (appGetsFullBackup(mCurrentPackage)) {
+ // It's possible that this app *formerly* was enqueued for key/value backup,
+ // but has since been updated and now only supports the full-data path.
+ // Don't proceed with a key/value backup for it in this case.
+ Slog.i(TAG, "Package " + request.packageName
+ + " requests full-data rather than key/value; skipping");
+ addBackupTrace("skipping - fullBackupOnly, completion is noop");
executeNextState(BackupState.RUNNING_QUEUE);
return;
}
@@ -2777,8 +2794,23 @@ public class BackupManagerService {
}
@Override
- public void operationComplete() {
- // Okay, the agent successfully reported back to us!
+ public void operationComplete(int 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 (DEBUG) {
+ Slog.i(TAG, "Callback after agent teardown: " + pkg);
+ }
+ addBackupTrace("late opComplete; curPkg = " + pkg);
+ return;
+ }
+
final String pkgName = mCurrentPackage.packageName;
final long filepos = mBackupDataName.length();
FileDescriptor fd = mBackupData.getFileDescriptor();
@@ -2924,12 +2956,22 @@ public class BackupManagerService {
void revertAndEndBackup() {
if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything");
addBackupTrace("transport error; reverting");
+
+ // We want to reset the backup schedule based on whatever the transport suggests
+ // by way of retry/backoff time.
+ long delay;
+ try {
+ delay = mTransport.requestBackupTime();
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to contact transport for recommended backoff");
+ delay = 0; // use the scheduler's default
+ }
+ KeyValueBackupJob.schedule(mContext, delay);
+
for (BackupRequest request : mOriginalQueue) {
dataChangedImpl(request.packageName);
}
- // We also want to reset the backup schedule based on whatever
- // the transport suggests by way of retry/backoff time.
- restartBackupAlarm();
+
}
void agentErrorCleanup() {
@@ -2962,15 +3004,6 @@ public class BackupManagerService {
}
}
- void restartBackupAlarm() {
- addBackupTrace("setting backup trigger");
- synchronized (mQueueLock) {
- try {
- startBackupAlarmsLocked(mTransport.requestBackupTime());
- } catch (RemoteException e) { /* cannot happen */ }
- }
- }
-
void executeNextState(BackupState nextState) {
if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
+ this + " nextState=" + nextState);
@@ -3097,8 +3130,23 @@ public class BackupManagerService {
// Core logic for performing one package's full backup, gathering the tarball from the
// application and emitting it to the designated OutputStream.
+
+ // Callout from the engine to an interested participant that might need to communicate
+ // with the agent prior to asking it to move data
+ interface FullBackupPreflight {
+ /**
+ * Perform the preflight operation necessary for the given package.
+ * @param pkg The name of the package being proposed for full-data backup
+ * @param agent Live BackupAgent binding to the target app's agent
+ * @return BackupTransport.TRANSPORT_OK to proceed with the backup operation,
+ * or one of the other BackupTransport.* error codes as appropriate
+ */
+ int preflightFullBackup(PackageInfo pkg, IBackupAgent agent);
+ };
+
class FullBackupEngine {
OutputStream mOutput;
+ FullBackupPreflight mPreflightHook;
IFullBackupRestoreObserver mObserver;
File mFilesDir;
File mManifestFile;
@@ -3129,8 +3177,7 @@ public class BackupManagerService {
@Override
public void run() {
try {
- BackupDataOutput output = new BackupDataOutput(
- mPipe.getFileDescriptor());
+ FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
if (mWriteManifest) {
final boolean writeWidgetData = mWidgetData != null;
@@ -3173,15 +3220,16 @@ public class BackupManagerService {
}
}
- FullBackupEngine(OutputStream output, String packageName, boolean alsoApks) {
+ FullBackupEngine(OutputStream output, String packageName, FullBackupPreflight preflightHook,
+ boolean alsoApks) {
mOutput = output;
+ mPreflightHook = preflightHook;
mIncludeApks = alsoApks;
mFilesDir = new File("/data/system");
mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
}
-
public int backupOnePackage(PackageInfo pkg) throws RemoteException {
int result = BackupTransport.TRANSPORT_OK;
Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName);
@@ -3191,42 +3239,52 @@ public class BackupManagerService {
if (agent != null) {
ParcelFileDescriptor[] pipes = null;
try {
- pipes = ParcelFileDescriptor.createPipe();
-
- ApplicationInfo app = pkg.applicationInfo;
- final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
- final boolean sendApk = mIncludeApks
- && !isSharedStorage
- && ((app.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) == 0)
- && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
+ // Call the preflight hook, if any
+ if (mPreflightHook != null) {
+ result = mPreflightHook.preflightFullBackup(pkg, agent);
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "preflight returned " + result);
+ }
+ }
+
+ // If we're still good to go after preflighting, start moving data
+ if (result == BackupTransport.TRANSPORT_OK) {
+ pipes = ParcelFileDescriptor.createPipe();
+
+ ApplicationInfo app = pkg.applicationInfo;
+ final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
+ final boolean sendApk = mIncludeApks
+ && !isSharedStorage
+ && ((app.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) == 0)
+ && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
(app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
- byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName,
- UserHandle.USER_OWNER);
+ byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName,
+ UserHandle.USER_OWNER);
- final int token = generateToken();
- FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1],
- token, sendApk, !isSharedStorage, widgetBlob);
- pipes[1].close(); // the runner has dup'd it
- pipes[1] = null;
- Thread t = new Thread(runner, "app-data-runner");
- t.start();
+ final int token = generateToken();
+ FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1],
+ token, sendApk, !isSharedStorage, widgetBlob);
+ pipes[1].close(); // the runner has dup'd it
+ pipes[1] = null;
+ Thread t = new Thread(runner, "app-data-runner");
+ t.start();
- // Now pull data from the app and stuff it into the output
- try {
- routeSocketDataToOutput(pipes[0], mOutput);
- } catch (IOException e) {
- Slog.i(TAG, "Caught exception reading from agent", e);
- result = BackupTransport.AGENT_ERROR;
- }
+ // Now pull data from the app and stuff it into the output
+ try {
+ routeSocketDataToOutput(pipes[0], mOutput);
+ } catch (IOException e) {
+ Slog.i(TAG, "Caught exception reading from agent", e);
+ result = BackupTransport.AGENT_ERROR;
+ }
- if (!waitUntilOperationComplete(token)) {
- Slog.e(TAG, "Full backup failed on package " + pkg.packageName);
- result = BackupTransport.AGENT_ERROR;
- } else {
- if (DEBUG) Slog.d(TAG, "Full package backup success: " + pkg.packageName);
+ if (!waitUntilOperationComplete(token)) {
+ Slog.e(TAG, "Full backup failed on package " + pkg.packageName);
+ result = BackupTransport.AGENT_ERROR;
+ } else {
+ if (DEBUG) Slog.d(TAG, "Full package backup success: " + pkg.packageName);
+ }
}
-
} catch (IOException e) {
Slog.e(TAG, "Error backing up " + pkg.packageName, e);
result = BackupTransport.AGENT_ERROR;
@@ -3251,7 +3309,7 @@ public class BackupManagerService {
return result;
}
- private void writeApkToBackup(PackageInfo pkg, BackupDataOutput output) {
+ private void writeApkToBackup(PackageInfo pkg, FullBackupDataOutput output) {
// Forward-locked apps, system-bundled .apks, etc are filtered out before we get here
// TODO: handle backing up split APKs
final String appSourceDir = pkg.applicationInfo.getBaseCodePath();
@@ -3750,7 +3808,7 @@ public class BackupManagerService {
final boolean isSharedStorage =
pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
- mBackupEngine = new FullBackupEngine(out, pkg.packageName, mIncludeApks);
+ mBackupEngine = new FullBackupEngine(out, pkg.packageName, null, mIncludeApks);
sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
mBackupEngine.backupOnePackage(pkg);
@@ -3797,13 +3855,13 @@ public class BackupManagerService {
static final String TAG = "PFTBT";
ArrayList<PackageInfo> mPackages;
boolean mUpdateSchedule;
- AtomicBoolean mLatch;
+ CountDownLatch mLatch;
AtomicBoolean mKeepRunning; // signal from job scheduler
FullBackupJob mJob; // if a scheduled job needs to be finished afterwards
PerformFullTransportBackupTask(IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
- FullBackupJob runningJob, AtomicBoolean latch) {
+ FullBackupJob runningJob, CountDownLatch latch) {
super(observer);
mUpdateSchedule = updateSchedule;
mLatch = latch;
@@ -3832,6 +3890,14 @@ public class BackupManagerService {
Slog.d(TAG, "Ignoring non-agent system package " + pkg);
}
continue;
+ } else if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+ // Cull any packages in the 'stopped' state: they've either just been
+ // installed or have explicitly been force-stopped by the user. In both
+ // cases we do not want to launch them for backup.
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Ignoring stopped package " + pkg);
+ }
+ continue;
}
mPackages.add(info);
} catch (NameNotFoundException e) {
@@ -3853,6 +3919,7 @@ public class BackupManagerService {
ParcelFileDescriptor[] transportPipes = null;
PackageInfo currentPackage;
+ long backoff = 0;
try {
if (!mEnabled || !mProvisioned) {
@@ -3895,10 +3962,10 @@ public class BackupManagerService {
// Now set up the backup engine / data source end of things
enginePipes = ParcelFileDescriptor.createPipe();
- AtomicBoolean runnerLatch = new AtomicBoolean(false);
+ CountDownLatch runnerLatch = new CountDownLatch(1);
SinglePackageBackupRunner backupRunner =
new SinglePackageBackupRunner(enginePipes[1], currentPackage,
- runnerLatch);
+ transport, runnerLatch);
// The runner dup'd the pipe half, so we close it here
enginePipes[1].close();
enginePipes[1] = null;
@@ -3923,6 +3990,9 @@ public class BackupManagerService {
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);
result = transport.sendBackupData(nRead);
@@ -3954,6 +4024,14 @@ public class BackupManagerService {
Slog.e(TAG, "Error " + result
+ " backing up " + currentPackage.packageName);
}
+
+ // Also ask the transport how long it wants us to wait before
+ // moving on to the next package, if any.
+ backoff = transport.requestFullBackupTime();
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Transport suggested backoff=" + backoff);
+ }
+
}
// Roll this package to the end of the backup queue if we're
@@ -4006,15 +4084,12 @@ public class BackupManagerService {
mRunningFullBackupTask = null;
}
- synchronized (mLatch) {
- mLatch.set(true);
- mLatch.notifyAll();
- }
+ mLatch.countDown();
// Now that we're actually done with schedule-driven work, reschedule
// the next pass based on the new queue state.
if (mUpdateSchedule) {
- scheduleNextFullBackupJob();
+ scheduleNextFullBackupJob(backoff);
}
}
}
@@ -4045,16 +4120,79 @@ public class BackupManagerService {
// Run the backup and pipe it back to the given socket -- expects to run on
// a standalone thread. The runner owns this half of the pipe, and closes
// it to indicate EOD to the other end.
+ class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight {
+ final AtomicInteger mResult = new AtomicInteger();
+ final CountDownLatch mLatch = new CountDownLatch(1);
+ final IBackupTransport mTransport;
+
+ public SinglePackageBackupPreflight(IBackupTransport transport) {
+ mTransport = transport;
+ }
+
+ @Override
+ public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) {
+ int result;
+ try {
+ final int token = generateToken();
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, this);
+ addBackupTrace("preflighting");
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
+ }
+ agent.doMeasureFullBackup(token, mBackupManagerBinder);
+
+ // now wait to get our result back
+ mLatch.await();
+ int totalSize = mResult.get();
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Got preflight response; size=" + totalSize);
+ }
+
+ result = mTransport.checkFullBackupSize(totalSize);
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception preflighting " + pkg.packageName + ": " + e.getMessage());
+ result = BackupTransport.AGENT_ERROR;
+ }
+ return result;
+ }
+
+ @Override
+ public void execute() {
+ // Unused in this case
+ }
+
+ @Override
+ public void operationComplete(int result) {
+ // got the callback, and our preflightFullBackup() method is waiting for the result
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Preflight op complete, result=" + result);
+ }
+ mResult.set(result);
+ mLatch.countDown();
+ }
+
+ @Override
+ public void handleTimeout() {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Preflight timeout; failing");
+ }
+ mResult.set(BackupTransport.AGENT_ERROR);
+ mLatch.countDown();
+ }
+
+ }
+
class SinglePackageBackupRunner implements Runnable {
final ParcelFileDescriptor mOutput;
final PackageInfo mTarget;
- final AtomicBoolean mLatch;
+ final FullBackupPreflight mPreflight;
+ final CountDownLatch mLatch;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
- AtomicBoolean latch) throws IOException {
- int oldfd = output.getFd();
+ IBackupTransport transport, CountDownLatch latch) throws IOException {
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
+ mPreflight = new SinglePackageBackupPreflight(transport);
mLatch = latch;
}
@@ -4062,15 +4200,13 @@ public class BackupManagerService {
public void run() {
try {
FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
- FullBackupEngine engine = new FullBackupEngine(out, mTarget.packageName, false);
+ FullBackupEngine engine = new FullBackupEngine(out, mTarget.packageName,
+ mPreflight, false);
engine.backupOnePackage(mTarget);
} catch (Exception e) {
Slog.e(TAG, "Exception during full package backup of " + mTarget);
} finally {
- synchronized (mLatch) {
- mLatch.set(true);
- mLatch.notifyAll();
- }
+ mLatch.countDown();
try {
mOutput.close();
} catch (IOException e) {
@@ -4078,7 +4214,6 @@ public class BackupManagerService {
}
}
}
-
}
}
@@ -4087,16 +4222,17 @@ public class BackupManagerService {
/**
* Schedule a job to tell us when it's a good time to run a full backup
*/
- void scheduleNextFullBackupJob() {
+ void scheduleNextFullBackupJob(long transportMinLatency) {
synchronized (mQueueLock) {
if (mFullBackupQueue.size() > 0) {
// schedule the next job at the point in the future when the least-recently
// backed up app comes due for backup again; or immediately if it's already
// due.
- long upcomingLastBackup = mFullBackupQueue.get(0).lastBackup;
- long timeSinceLast = System.currentTimeMillis() - upcomingLastBackup;
- final long latency = (timeSinceLast < MIN_FULL_BACKUP_INTERVAL)
+ final long upcomingLastBackup = mFullBackupQueue.get(0).lastBackup;
+ final long timeSinceLast = System.currentTimeMillis() - upcomingLastBackup;
+ final long appLatency = (timeSinceLast < MIN_FULL_BACKUP_INTERVAL)
? (MIN_FULL_BACKUP_INTERVAL - timeSinceLast) : 0;
+ final long latency = Math.min(transportMinLatency, appLatency);
Runnable r = new Runnable() {
@Override public void run() {
FullBackupJob.schedule(mContext, latency);
@@ -4150,6 +4286,31 @@ public class BackupManagerService {
writeFullBackupScheduleAsync();
}
+ private boolean fullBackupAllowable(IBackupTransport transport) {
+ if (transport == null) {
+ Slog.w(TAG, "Transport not present; full data backup not performed");
+ return false;
+ }
+
+ // Don't proceed unless we have already established package metadata
+ // for the current dataset via a key/value backup pass.
+ try {
+ File stateDir = new File(mBaseStateDir, transport.transportDirName());
+ File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL);
+ if (pmState.length() <= 0) {
+ if (DEBUG) {
+ Slog.i(TAG, "Full backup requested but dataset not yet initialized");
+ }
+ return false;
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to contact transport");
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Conditions are right for a full backup operation, so run one. The model we use is
* to perform one app backup per scheduled job execution, and to reschedule the job
@@ -4161,6 +4322,7 @@ public class BackupManagerService {
boolean beginFullBackup(FullBackupJob scheduledJob) {
long now = System.currentTimeMillis();
FullBackupEntry entry = null;
+ long latency = MIN_FULL_BACKUP_INTERVAL;
if (!mEnabled || !mProvisioned) {
// Backups are globally disabled, so don't proceed. We also don't reschedule
@@ -4192,17 +4354,41 @@ public class BackupManagerService {
return false;
}
- entry = mFullBackupQueue.get(0);
- long timeSinceRun = now - entry.lastBackup;
- if (timeSinceRun < MIN_FULL_BACKUP_INTERVAL) {
- // It's too early to back up the next thing in the queue, so bow out
+ // At this point we know that we have work to do, just not right now. Any
+ // exit without actually running backups will also require that we
+ // reschedule the job.
+ boolean runBackup = true;
+
+ if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
if (MORE_DEBUG) {
- Slog.i(TAG, "Device ready but too early to back up next app");
+ Slog.i(TAG, "Preconditions not met; not running full backup");
}
- final long latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+ runBackup = false;
+ // Typically this means we haven't run a key/value backup yet. Back off
+ // full-backup operations by the key/value job's run interval so that
+ // next time we run, we are likely to be able to make progress.
+ latency = KeyValueBackupJob.BATCH_INTERVAL;
+ }
+
+ if (runBackup) {
+ entry = mFullBackupQueue.get(0);
+ long timeSinceRun = now - entry.lastBackup;
+ runBackup = (timeSinceRun >= MIN_FULL_BACKUP_INTERVAL);
+ if (!runBackup) {
+ // It's too early to back up the next thing in the queue, so bow out
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Device ready but too early to back up next app");
+ }
+ // Wait until the next app in the queue falls due for a full data backup
+ latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+ }
+ }
+
+ if (!runBackup) {
+ final long deferTime = latency; // pin for the closure
mBackupHandler.post(new Runnable() {
@Override public void run() {
- FullBackupJob.schedule(mContext, latency);
+ FullBackupJob.schedule(mContext, deferTime);
}
});
return false;
@@ -4210,7 +4396,7 @@ public class BackupManagerService {
// Okay, the top thing is runnable now. Pop it off and get going.
mFullBackupQueue.remove(0);
- AtomicBoolean latch = new AtomicBoolean(false);
+ CountDownLatch latch = new CountDownLatch(1);
String[] pkg = new String[] {entry.packageName};
mRunningFullBackupTask = new PerformFullTransportBackupTask(null, pkg, true,
scheduledJob, latch);
@@ -4764,7 +4950,7 @@ public class BackupManagerService {
}
}
- class RestoreInstallObserver extends IPackageInstallObserver.Stub {
+ class RestoreInstallObserver extends PackageInstallObserver {
final AtomicBoolean mDone = new AtomicBoolean();
String mPackageName;
int mResult;
@@ -4790,8 +4976,8 @@ public class BackupManagerService {
}
@Override
- public void packageInstalled(String packageName, int returnCode)
- throws RemoteException {
+ public void onPackageInstalled(String packageName, int returnCode,
+ String msg, Bundle extras) {
synchronized (mDone) {
mResult = returnCode;
mPackageName = packageName;
@@ -5047,7 +5233,9 @@ public class BackupManagerService {
offset = extractLine(buffer, offset, str);
version = Integer.parseInt(str[0]); // app version
offset = extractLine(buffer, offset, str);
- int platformVersion = Integer.parseInt(str[0]);
+ // This is the platform version, which we don't use, but we parse it
+ // as a safety against corruption in the manifest.
+ Integer.parseInt(str[0]);
offset = extractLine(buffer, offset, str);
info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
offset = extractLine(buffer, offset, str);
@@ -6108,7 +6296,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
}
- class RestoreInstallObserver extends IPackageInstallObserver.Stub {
+ class RestoreInstallObserver extends PackageInstallObserver {
final AtomicBoolean mDone = new AtomicBoolean();
String mPackageName;
int mResult;
@@ -6134,8 +6322,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
@Override
- public void packageInstalled(String packageName, int returnCode)
- throws RemoteException {
+ public void onPackageInstalled(String packageName, int returnCode,
+ String msg, Bundle extras) {
synchronized (mDone) {
mResult = returnCode;
mPackageName = packageName;
@@ -6384,7 +6572,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
offset = extractLine(buffer, offset, str);
version = Integer.parseInt(str[0]); // app version
offset = extractLine(buffer, offset, str);
- int platformVersion = Integer.parseInt(str[0]);
+ // This is the platform version, which we don't use, but we parse it
+ // as a safety against corruption in the manifest.
+ Integer.parseInt(str[0]);
offset = extractLine(buffer, offset, str);
info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
offset = extractLine(buffer, offset, str);
@@ -7843,7 +8033,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
@Override
- public void operationComplete() {
+ public void operationComplete(int unusedResult) {
if (MORE_DEBUG) {
Slog.i(TAG, "operationComplete() during restore: target="
+ mCurrentPackage.packageName
@@ -8096,6 +8286,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
}
}
+
+ // ...and schedule a backup pass if necessary
+ KeyValueBackupJob.schedule(mContext);
}
// Note: packageName is currently unused, but may be in the future
@@ -8230,16 +8423,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
synchronized (mQueueLock) {
- // Because the alarms we are using can jitter, and we want an *immediate*
- // backup pass to happen, we restart the timer beginning with "next time,"
- // then manually fire the backup trigger intent ourselves.
- startBackupAlarmsLocked(BACKUP_INTERVAL);
+ // Fire the intent that kicks off the whole shebang...
try {
mRunBackupIntent.send();
} catch (PendingIntent.CanceledException e) {
// should never happen
Slog.e(TAG, "run-backup intent cancelled!");
}
+
+ // ...and cancel any pending scheduled job, because we've just superseded it
+ KeyValueBackupJob.cancel(mContext);
}
}
@@ -8306,7 +8499,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
// make sure the screen is lit for the user interaction
- mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ 0);
// start the confirmation countdown
startConfirmationTimeout(token, params);
@@ -8334,21 +8529,33 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
throw new IllegalStateException("Restore supported only for the device owner");
}
- if (DEBUG) {
- Slog.d(TAG, "fullTransportBackup()");
- }
+ if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+ Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?");
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "fullTransportBackup()");
+ }
- AtomicBoolean latch = new AtomicBoolean(false);
- PerformFullTransportBackupTask task =
- new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
- (new Thread(task, "full-transport-master")).start();
- synchronized (latch) {
- try {
- while (latch.get() == false) {
- latch.wait();
+ CountDownLatch latch = new CountDownLatch(1);
+ PerformFullTransportBackupTask task =
+ new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
+ (new Thread(task, "full-transport-master")).start();
+ do {
+ try {
+ latch.await();
+ break;
+ } catch (InterruptedException e) {
+ // Just go back to waiting for the latch to indicate completion
}
- } catch (InterruptedException e) {}
+ } while (true);
+
+ // We just ran a backup on these packages, so kick them to the end of the queue
+ final long now = System.currentTimeMillis();
+ for (String pkg : pkgNames) {
+ enqueueFullBackup(pkg, now);
+ }
}
+
if (DEBUG) {
Slog.d(TAG, "Done with full transport backup.");
}
@@ -8389,7 +8596,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
// make sure the screen is lit for the user interaction
- mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ 0);
// start the confirmation countdown
startConfirmationTimeout(token, params);
@@ -8515,13 +8724,13 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
synchronized (mQueueLock) {
if (enable && !wasEnabled && mProvisioned) {
// if we've just been enabled, start scheduling backup passes
- startBackupAlarmsLocked(BACKUP_INTERVAL);
- scheduleNextFullBackupJob();
+ KeyValueBackupJob.schedule(mContext);
+ scheduleNextFullBackupJob(0);
} else if (!enable) {
// No longer enabled, so stop running backups
if (DEBUG) Slog.i(TAG, "Opting out of backup");
- mAlarmManager.cancel(mRunBackupIntent);
+ KeyValueBackupJob.cancel(mContext);
// This also constitutes an opt-out, so we wipe any data for
// this device from the backend. We start that process with
@@ -8575,19 +8784,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
*/
}
- private void startBackupAlarmsLocked(long delayBeforeFirstBackup) {
- // We used to use setInexactRepeating(), but that may be linked to
- // backups running at :00 more often than not, creating load spikes.
- // Schedule at an exact time for now, and also add a bit of "fuzz".
-
- Random random = new Random();
- long when = System.currentTimeMillis() + delayBeforeFirstBackup +
- random.nextInt(FUZZ_MILLIS);
- mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, when,
- BACKUP_INTERVAL + random.nextInt(FUZZ_MILLIS), mRunBackupIntent);
- mNextBackupPass = when;
- }
-
// Report whether the backup mechanism is currently enabled
public boolean isBackupEnabled() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "isBackupEnabled");
@@ -8902,8 +9098,10 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// Note that a currently-active backup agent has notified us that it has
// completed the given outstanding asynchronous backup/restore operation.
- public void opComplete(int token) {
- if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token));
+ public void opComplete(int token, long result) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "opComplete: " + Integer.toHexString(token) + " result=" + result);
+ }
Operation op = null;
synchronized (mCurrentOpLock) {
op = mCurrentOperations.get(token);
@@ -8916,6 +9114,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// The completion callback, if any, is invoked on the handler
if (op != null && op.callback != null) {
Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, op.callback);
+ // NB: this cannot distinguish between results > 2 gig
+ msg.arg1 = (result > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) result;
mBackupHandler.sendMessage(msg);
}
}
@@ -9168,6 +9368,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// check whether there is data for it in the current dataset, falling back
// to the ancestral dataset if not.
long token = getAvailableRestoreToken(packageName);
+ if (DEBUG) Slog.v(TAG, "restorePackage pkg=" + packageName
+ + " token=" + Long.toHexString(token));
// If we didn't come up with a place to look -- no ancestral dataset and
// the app has never been backed up from this device -- there's nothing
@@ -9293,7 +9495,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
if (mBackupRunning) pw.println("Backup currently running");
pw.println("Last backup pass started: " + mLastBackupPass
+ " (now = " + System.currentTimeMillis() + ')');
- pw.println(" next scheduled: " + mNextBackupPass);
+ pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled());
pw.println("Available transports:");
final String[] transports = listAllTransports();
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
new file mode 100644
index 0000000..a4489c1
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.app.AlarmManager;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.Random;
+
+/**
+ * Job for scheduling key/value backup work. This module encapsulates all
+ * of the policy around when those backup passes are executed.
+ */
+public class KeyValueBackupJob extends JobService {
+ private static final String TAG = "KeyValueBackupJob";
+ private static ComponentName sKeyValueJobService =
+ new ComponentName("android", KeyValueBackupJob.class.getName());
+ private static final int JOB_ID = 0x5039;
+
+ // Once someone asks for a backup, this is how long we hold off, batching
+ // up additional requests, before running the actual backup pass. Privileged
+ // callers can always trigger an immediate pass via BackupManager.backupNow().
+ static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR;
+
+ // Random variation in next-backup scheduling time to avoid server load spikes
+ private static final int FUZZ_MILLIS = 10 * 60 * 1000;
+
+ // Don't let the job scheduler defer forever; give it a (lenient) deadline
+ private static final long MAX_DEFERRAL = 1 * AlarmManager.INTERVAL_HOUR;
+
+ private static boolean sScheduled = false;
+ private static long sNextScheduled = 0;
+
+ public static void schedule(Context ctx) {
+ schedule(ctx, 0);
+ }
+
+ public static void schedule(Context ctx, long delay) {
+ synchronized (KeyValueBackupJob.class) {
+ if (!sScheduled) {
+ if (delay <= 0) {
+ delay = BATCH_INTERVAL + new Random().nextInt(FUZZ_MILLIS);
+ }
+ if (BackupManagerService.DEBUG_SCHEDULING) {
+ Slog.v(TAG, "Scheduling k/v pass in "
+ + (delay / 1000 / 60) + " minutes");
+ }
+ JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
+ .setMinimumLatency(delay)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setOverrideDeadline(delay + MAX_DEFERRAL);
+ js.schedule(builder.build());
+
+ sNextScheduled = System.currentTimeMillis() + delay;
+ sScheduled = true;
+ }
+ }
+ }
+
+ public static void cancel(Context ctx) {
+ synchronized (KeyValueBackupJob.class) {
+ JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ js.cancel(JOB_ID);
+ sNextScheduled = 0;
+ sScheduled = false;
+ }
+ }
+
+ public static long nextScheduled() {
+ synchronized (KeyValueBackupJob.class) {
+ return sNextScheduled;
+ }
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ synchronized (KeyValueBackupJob.class) {
+ sNextScheduled = 0;
+ sScheduled = false;
+ }
+
+ // Time to run a key/value backup!
+ Trampoline service = BackupManagerService.getInstance();
+ try {
+ service.backupNow();
+ } catch (RemoteException e) {}
+
+ // This was just a trigger; ongoing wakelock management is done by the
+ // rest of the backup system.
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ // Intentionally empty; the job starting was just a trigger
+ return false;
+ }
+
+}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 8bd7132..99bbdae 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -309,15 +309,17 @@ public class Trampoline extends IBackupManager.Stub {
}
@Override
- public void opComplete(int token) throws RemoteException {
+ public void opComplete(int token, long result) throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.opComplete(token);
+ svc.opComplete(token, result);
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
BackupManagerService svc = mService;
if (svc != null) {
svc.dump(fd, pw, args);