summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorChristopher Tate <ctate@google.com>2014-07-28 01:31:47 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2014-07-28 01:31:47 +0000
commit4b111442d716c26a24f5c606c8d44d70c327117f (patch)
treea8dae292018886453819b347b9bfd350188c1d1a /services
parente468f29992b242421161078f59cca81a4cba5d6f (diff)
parent083bdc68a94f906767f5ba8c5cc62e89716b7f9e (diff)
downloadframeworks_base-4b111442d716c26a24f5c606c8d44d70c327117f.zip
frameworks_base-4b111442d716c26a24f5c606c8d44d70c327117f.tar.gz
frameworks_base-4b111442d716c26a24f5c606c8d44d70c327117f.tar.bz2
am 8ac64af6: am 9cd88b91: am 7c98ecd3: Schedule full backups
* commit '8ac64af6236b03e0af6ee95bd579612a6d1ef4d4': Schedule full backups
Diffstat (limited to 'services')
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java550
-rw-r--r--services/backup/java/com/android/server/backup/FullBackupJob.java78
-rw-r--r--services/core/java/com/android/server/job/controllers/IdleController.java12
3 files changed, 545 insertions, 95 deletions
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 595e91e..291d366 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -34,6 +34,7 @@ import android.app.backup.IBackupManager;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.app.job.JobParameters;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -78,6 +79,7 @@ import android.os.storage.IMountService;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
+import android.util.AtomicFile;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -119,6 +121,7 @@ import java.security.spec.KeySpec;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -154,6 +157,7 @@ public class BackupManagerService extends IBackupManager.Stub {
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;
// System-private key used for backing up an app's widget state. Must
// begin with U+FFxx by convention (we reserve all keys starting
@@ -239,7 +243,10 @@ public class BackupManagerService extends IBackupManager.Stub {
// order to give them time to enter the backup password.
static final long TIMEOUT_FULL_CONFIRMATION = 60 * 1000;
- private Context mContext;
+ // How long between attempts to perform a full-data backup of any given app
+ static final long MIN_FULL_BACKUP_INTERVAL = 1000 * 60 * 60 * 24; // one day
+
+ Context mContext;
private PackageManager mPackageManager;
IPackageManager mPackageManagerBinder;
private IActivityManager mActivityManager;
@@ -313,17 +320,32 @@ public class BackupManagerService extends IBackupManager.Stub {
// Watch the device provisioning operation during setup
ContentObserver mProvisionedObserver;
+ static BackupManagerService sInstance;
+ static BackupManagerService getInstance() {
+ // Always constructed during system bringup, so no need to lazy-init
+ return sInstance;
+ }
+
public static final class Lifecycle extends SystemService {
- private final BackupManagerService mService;
public Lifecycle(Context context) {
super(context);
- mService = new BackupManagerService(context);
+ sInstance = new BackupManagerService(context);
}
@Override
public void onStart() {
- publishBinderService(Context.BACKUP_SERVICE, mService);
+ publishBinderService(Context.BACKUP_SERVICE, sInstance);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ ContentResolver r = sInstance.mContext.getContentResolver();
+ boolean areEnabled = Settings.Secure.getInt(r,
+ Settings.Secure.BACKUP_ENABLED, 0) != 0;
+ sInstance.setBackupEnabled(areEnabled);
+ }
}
}
@@ -542,6 +564,30 @@ public class BackupManagerService extends IBackupManager.Stub {
static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
HashSet<String> mPendingInits = new HashSet<String>(); // transport names
+ // Round-robin queue for scheduling full backup passes
+ static final int SCHEDULE_FILE_VERSION = 1; // current version of the schedule file
+ class FullBackupEntry implements Comparable<FullBackupEntry> {
+ String packageName;
+ long lastBackup;
+
+ FullBackupEntry(String pkg, long when) {
+ packageName = pkg;
+ lastBackup = when;
+ }
+
+ @Override
+ public int compareTo(FullBackupEntry other) {
+ if (lastBackup < other.lastBackup) return -1;
+ else if (lastBackup > other.lastBackup) return 1;
+ else return 0;
+ }
+ }
+
+ File mFullBackupScheduleFile;
+ // If we're running a schedule-driven full backup, this is the task instance doing it
+ PerformFullTransportBackupTask mRunningFullBackupTask; // inside mQueueLock
+ ArrayList<FullBackupEntry> mFullBackupQueue; // inside mQueueLock
+
// Utility: build a new random integer token
int generateToken() {
int token;
@@ -573,6 +619,17 @@ public class BackupManagerService extends IBackupManager.Stub {
return true;
}
+ /* does *not* check overall backup eligibility policy! */
+ public static boolean appGetsFullBackup(PackageInfo pkg) {
+ if (pkg.applicationInfo.backupAgentName != null) {
+ // If it has an agent, it gets full backups only if it says so
+ return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0;
+ }
+
+ // No agent means we do full backups for it
+ return true;
+ }
+
// ----- Asynchronous backup/restore handler thread -----
private class BackupHandler extends Handler {
@@ -892,8 +949,6 @@ public class BackupManagerService extends IBackupManager.Stub {
// Set up our bookkeeping
final ContentResolver resolver = context.getContentResolver();
- boolean areEnabled = Settings.Secure.getInt(resolver,
- Settings.Secure.BACKUP_ENABLED, 0) != 0;
mProvisioned = Settings.Global.getInt(resolver,
Settings.Global.DEVICE_PROVISIONED, 0) != 0;
mAutoRestore = Settings.Secure.getInt(resolver,
@@ -987,6 +1042,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mJournal = null; // will be created on first use
// Set up the various sorts of package tracking we do
+ mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
initPackageTracking();
// Build our mapping of uid to backup client services. This implicitly
@@ -1050,9 +1106,6 @@ public class BackupManagerService extends IBackupManager.Stub {
// Power management
mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
-
- // Start the backup passes going
- setBackupEnabled(areEnabled);
}
private class RunBackupReceiver extends BroadcastReceiver {
@@ -1192,6 +1245,9 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
+ // Resume the full-data backup queue
+ mFullBackupQueue = readFullBackupSchedule();
+
// Register for broadcasts about package install, etc., so we can
// update the provider list.
IntentFilter filter = new IntentFilter();
@@ -1206,6 +1262,96 @@ public class BackupManagerService extends IBackupManager.Stub {
mContext.registerReceiver(mBroadcastReceiver, sdFilter);
}
+ private ArrayList<FullBackupEntry> readFullBackupSchedule() {
+ ArrayList<FullBackupEntry> schedule = null;
+ synchronized (mQueueLock) {
+ if (mFullBackupScheduleFile.exists()) {
+ try {
+ FileInputStream fstream = new FileInputStream(mFullBackupScheduleFile);
+ BufferedInputStream bufStream = new BufferedInputStream(fstream);
+ DataInputStream in = new DataInputStream(bufStream);
+
+ int version = in.readInt();
+ if (version != SCHEDULE_FILE_VERSION) {
+ Slog.e(TAG, "Unknown backup schedule version " + version);
+ return null;
+ }
+
+ int N = in.readInt();
+ schedule = new ArrayList<FullBackupEntry>(N);
+ for (int i = 0; i < N; i++) {
+ String pkgName = in.readUTF();
+ long lastBackup = in.readLong();
+ schedule.add(new FullBackupEntry(pkgName, lastBackup));
+ }
+ Collections.sort(schedule);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to read backup schedule", e);
+ mFullBackupScheduleFile.delete();
+ schedule = null;
+ }
+ }
+
+ if (schedule == null) {
+ // no prior queue record, or unable to read it. Set up the queue
+ // from scratch.
+ List<PackageInfo> apps =
+ PackageManagerBackupAgent.getStorableApplications(mPackageManager);
+ final int N = apps.size();
+ schedule = new ArrayList<FullBackupEntry>(N);
+ for (int i = 0; i < N; i++) {
+ PackageInfo info = apps.get(i);
+ if (appGetsFullBackup(info)) {
+ schedule.add(new FullBackupEntry(info.packageName, 0));
+ }
+ }
+ writeFullBackupScheduleAsync();
+ }
+ }
+ return schedule;
+ }
+
+ Runnable mFullBackupScheduleWriter = new Runnable() {
+ @Override public void run() {
+ synchronized (mQueueLock) {
+ try {
+ ByteArrayOutputStream bufStream = new ByteArrayOutputStream(4096);
+ DataOutputStream bufOut = new DataOutputStream(bufStream);
+ bufOut.writeInt(SCHEDULE_FILE_VERSION);
+
+ // version 1:
+ //
+ // [int] # of packages in the queue = N
+ // N * {
+ // [utf8] package name
+ // [long] last backup time for this package
+ // }
+ int N = mFullBackupQueue.size();
+ bufOut.writeInt(N);
+
+ for (int i = 0; i < N; i++) {
+ FullBackupEntry entry = mFullBackupQueue.get(i);
+ bufOut.writeUTF(entry.packageName);
+ bufOut.writeLong(entry.lastBackup);
+ }
+ bufOut.flush();
+
+ AtomicFile af = new AtomicFile(mFullBackupScheduleFile);
+ FileOutputStream out = af.startWrite();
+ out.write(bufStream.toByteArray());
+ af.finishWrite(out);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to write backup schedule!", e);
+ }
+ }
+ }
+ };
+
+ private void writeFullBackupScheduleAsync() {
+ mBackupHandler.removeCallbacks(mFullBackupScheduleWriter);
+ mBackupHandler.post(mFullBackupScheduleWriter);
+ }
+
private void parseLeftoverJournals() {
for (File f : mJournalDir.listFiles()) {
if (mJournal == null || f.compareTo(mJournal) != 0) {
@@ -1633,6 +1779,22 @@ public class BackupManagerService extends IBackupManager.Stub {
}
addPackageParticipantsLocked(pkgList);
}
+ // If they're full-backup candidates, add them there instead
+ for (String packageName : pkgList) {
+ try {
+ PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
+ long now = System.currentTimeMillis();
+ if (appGetsFullBackup(app)) {
+ enqueueFullBackup(packageName, now);
+ scheduleNextFullBackupJob();
+ }
+ } catch (NameNotFoundException e) {
+ // doesn't really exist; ignore it
+ if (DEBUG) {
+ Slog.i(TAG, "Can't resolve new app " + packageName);
+ }
+ }
+ }
} else {
if (replacing) {
// The package is being updated. We'll receive a PACKAGE_ADDED shortly.
@@ -3437,12 +3599,19 @@ public class BackupManagerService extends IBackupManager.Stub {
class PerformFullTransportBackupTask extends FullBackupTask {
static final String TAG = "PFTBT";
ArrayList<PackageInfo> mPackages;
+ boolean mUpdateSchedule;
AtomicBoolean mLatch;
+ AtomicBoolean mKeepRunning; // signal from job scheduler
+ FullBackupJob mJob; // if a scheduled job needs to be finished afterwards
PerformFullTransportBackupTask(IFullBackupRestoreObserver observer,
- String[] whichPackages, AtomicBoolean latch) {
+ String[] whichPackages, boolean updateSchedule,
+ FullBackupJob runningJob, AtomicBoolean latch) {
super(observer);
+ mUpdateSchedule = updateSchedule;
mLatch = latch;
+ mKeepRunning = new AtomicBoolean(true);
+ mJob = runningJob;
mPackages = new ArrayList<PackageInfo>(whichPackages.length);
for (String pkg : whichPackages) {
@@ -3474,119 +3643,164 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
+ public void setRunning(boolean running) {
+ mKeepRunning.set(running);
+ }
+
@Override
public void run() {
- IBackupTransport transport = getTransport(mCurrentTransport);
- if (transport == null) {
- Slog.w(TAG, "Transport not present; full data backup not performed");
- return;
- }
-
// data from the app, passed to us for bridging to the transport
ParcelFileDescriptor[] enginePipes = null;
// Pipe through which we write data to the transport
ParcelFileDescriptor[] transportPipes = null;
+ PackageInfo currentPackage;
+
try {
- // Set up to send data to the transport
- if (transport != null) {
- for (PackageInfo target : mPackages) {
- if (DEBUG) {
- Slog.i(TAG, "Initiating full-data transport backup of "
- + target.packageName);
- }
- transportPipes = ParcelFileDescriptor.createPipe();
+ IBackupTransport transport = getTransport(mCurrentTransport);
+ if (transport == null) {
+ Slog.w(TAG, "Transport not present; full data backup not performed");
+ return;
+ }
- // Tell the transport the data's coming
- int result = transport.performFullBackup(target, transportPipes[0]);
- if (result == BackupTransport.TRANSPORT_OK) {
- // 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();
- AtomicBoolean runnerLatch = new AtomicBoolean(false);
- SinglePackageBackupRunner backupRunner =
- new SinglePackageBackupRunner(enginePipes[1], target,
- runnerLatch);
- // 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();
-
- // Read data off the engine pipe and pass it to the transport
- // pipe until we hit EOD on the input stream.
- FileInputStream in = new FileInputStream(
- enginePipes[0].getFileDescriptor());
- FileOutputStream out = new FileOutputStream(
- transportPipes[1].getFileDescriptor());
- byte[] buffer = new byte[8192];
- int nRead = 0;
- do {
- nRead = in.read(buffer);
- if (nRead > 0) {
- out.write(buffer, 0, nRead);
- result = transport.sendBackupData(nRead);
+ // Set up to send data to the transport
+ final int N = mPackages.size();
+ for (int i = 0; i < N; i++) {
+ currentPackage = mPackages.get(i);
+ if (DEBUG) {
+ Slog.i(TAG, "Initiating full-data transport backup of "
+ + currentPackage.packageName);
+ }
+ transportPipes = ParcelFileDescriptor.createPipe();
+
+ // Tell the transport the data's coming
+ int result = transport.performFullBackup(currentPackage,
+ transportPipes[0]);
+ if (result == BackupTransport.TRANSPORT_OK) {
+ // 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();
+ AtomicBoolean runnerLatch = new AtomicBoolean(false);
+ SinglePackageBackupRunner backupRunner =
+ new SinglePackageBackupRunner(enginePipes[1], currentPackage,
+ runnerLatch);
+ // 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();
+
+ // Read data off the engine pipe and pass it to the transport
+ // pipe until we hit EOD on the input stream.
+ FileInputStream in = new FileInputStream(
+ enginePipes[0].getFileDescriptor());
+ FileOutputStream out = new FileOutputStream(
+ transportPipes[1].getFileDescriptor());
+ byte[] buffer = new byte[8192];
+ int nRead = 0;
+ do {
+ if (!mKeepRunning.get()) {
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Full backup task told to stop");
}
- } while (nRead > 0 && result == BackupTransport.TRANSPORT_OK);
+ break;
+ }
+ nRead = in.read(buffer);
+ if (nRead > 0) {
+ out.write(buffer, 0, nRead);
+ result = transport.sendBackupData(nRead);
+ }
+ } while (nRead > 0 && result == BackupTransport.TRANSPORT_OK);
- // In all cases we need to give the transport its finish callback
- int finishResult = transport.finishBackup();
+ int finishResult;
- if (MORE_DEBUG) {
- Slog.i(TAG, "Done trying to send backup data: result="
- + result + " finishResult=" + finishResult);
- }
+ if (!mKeepRunning.get()) {
+ result = BackupTransport.TRANSPORT_ERROR;
+ // TODO: tell the transport to abort the backup
+ Slog.w(TAG, "TODO: tell transport to halt & roll back");
+ }
- // If we were otherwise in a good state, now interpret the final
- // result based on what finishBackup() returned. If we're in a
- // failure case already, preserve that result and ignore whatever
- // finishBackup() reported.
- if (result == BackupTransport.TRANSPORT_OK) {
- result = finishResult;
- }
+ // In all cases we need to give the transport its finish callback
+ finishResult = transport.finishBackup();
- if (result != BackupTransport.TRANSPORT_OK) {
- Slog.e(TAG, "Error " + result
- + " backing up " + target.packageName);
- }
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Done trying to send backup data: result="
+ + result + " finishResult=" + finishResult);
}
- if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
- if (DEBUG) {
- Slog.i(TAG, "Transport rejected backup of " + target.packageName
- + ", skipping");
- }
- // do nothing, clean up, and continue looping
- } else if (result != BackupTransport.TRANSPORT_OK) {
- if (DEBUG) {
- Slog.i(TAG, "Transport failed; aborting backup: " + result);
- return;
- }
+ // If we were otherwise in a good state, now interpret the final
+ // result based on what finishBackup() returned. If we're in a
+ // failure case already, preserve that result and ignore whatever
+ // finishBackup() reported.
+ if (result == BackupTransport.TRANSPORT_OK) {
+ result = finishResult;
+ }
+
+ if (result != BackupTransport.TRANSPORT_OK) {
+ Slog.e(TAG, "Error " + result
+ + " backing up " + currentPackage.packageName);
}
- cleanUpPipes(transportPipes);
- cleanUpPipes(enginePipes);
}
- if (DEBUG) {
- Slog.i(TAG, "Full backup completed.");
+ // Roll this package to the end of the backup queue if we're
+ // in a queue-driven mode (regardless of success/failure)
+ if (mUpdateSchedule) {
+ enqueueFullBackup(currentPackage.packageName,
+ System.currentTimeMillis());
+ }
+
+ if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ if (DEBUG) {
+ Slog.i(TAG, "Transport rejected backup of "
+ + currentPackage.packageName
+ + ", skipping");
+ }
+ // do nothing, clean up, and continue looping
+ } else if (result != BackupTransport.TRANSPORT_OK) {
+ if (DEBUG) {
+ Slog.i(TAG, "Transport failed; aborting backup: " + result);
+ return;
+ }
}
+ cleanUpPipes(transportPipes);
+ cleanUpPipes(enginePipes);
+ currentPackage = null;
+ }
+
+ if (DEBUG) {
+ Slog.i(TAG, "Full backup completed.");
}
} catch (Exception e) {
Slog.w(TAG, "Exception trying full transport backup", e);
} finally {
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
+
+ if (mJob != null) {
+ mJob.finishBackupPass();
+ }
+
+ synchronized (mQueueLock) {
+ mRunningFullBackupTask = null;
+ }
+
synchronized (mLatch) {
mLatch.set(true);
mLatch.notifyAll();
}
+
+ // Now that we're actually done with schedule-driven work, reschedule
+ // the next pass based on the new queue state.
+ if (mUpdateSchedule) {
+ scheduleNextFullBackupJob();
+ }
}
}
@@ -3653,6 +3867,146 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
+ // ----- Full-data backup scheduling -----
+
+ /**
+ * Schedule a job to tell us when it's a good time to run a full backup
+ */
+ void scheduleNextFullBackupJob() {
+ 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)
+ ? (MIN_FULL_BACKUP_INTERVAL - timeSinceLast) : 0;
+ Runnable r = new Runnable() {
+ @Override public void run() {
+ FullBackupJob.schedule(mContext, latency);
+ }
+ };
+ mBackupHandler.postDelayed(r, 2500);
+ } else {
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Full backup queue empty; not scheduling");
+ }
+ }
+ }
+ }
+
+ /**
+ * Enqueue full backup for the given app, with a note about when it last ran.
+ */
+ void enqueueFullBackup(String packageName, long lastBackedUp) {
+ FullBackupEntry newEntry = new FullBackupEntry(packageName, lastBackedUp);
+ synchronized (mQueueLock) {
+ int N = mFullBackupQueue.size();
+ // First, sanity check that we aren't adding a duplicate. Slow but
+ // straightforward; we'll have at most on the order of a few hundred
+ // items in this list.
+ for (int i = N-1; i >= 0; i--) {
+ final FullBackupEntry e = mFullBackupQueue.get(i);
+ if (packageName.equals(e.packageName)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Removing schedule queue dupe of " + packageName);
+ }
+ mFullBackupQueue.remove(i);
+ }
+ }
+
+ // This is also slow but easy for modest numbers of apps: work backwards
+ // from the end of the queue until we find an item whose last backup
+ // time was before this one, then insert this new entry after it.
+ int which;
+ for (which = mFullBackupQueue.size() - 1; which >= 0; which--) {
+ final FullBackupEntry entry = mFullBackupQueue.get(which);
+ if (entry.lastBackup <= lastBackedUp) {
+ mFullBackupQueue.add(which + 1, newEntry);
+ break;
+ }
+ }
+ if (which < 0) {
+ // this one is earlier than any existing one, so prepend
+ mFullBackupQueue.add(0, newEntry);
+ }
+ }
+ writeFullBackupScheduleAsync();
+ }
+
+ /**
+ * 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
+ * with zero latency as long as conditions remain right and we still have work to do.
+ *
+ * @return Whether ongoing work will continue. The return value here will be passed
+ * along as the return value to the scheduled job's onStartJob() callback.
+ */
+ boolean beginFullBackup(FullBackupJob scheduledJob) {
+ long now = System.currentTimeMillis();
+ FullBackupEntry entry = null;
+
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Beginning scheduled full backup operation");
+ }
+
+ // Great; we're able to run full backup jobs now. See if we have any work to do.
+ synchronized (mQueueLock) {
+ if (mRunningFullBackupTask != null) {
+ Slog.e(TAG, "Backup triggered but one already/still running!");
+ return false;
+ }
+
+ if (mFullBackupQueue.size() == 0) {
+ // no work to do so just bow out
+ if (DEBUG) {
+ Slog.i(TAG, "Backup queue empty; doing nothing");
+ }
+ 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
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Device ready but too early to back up next app");
+ }
+ final long latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+ mBackupHandler.post(new Runnable() {
+ @Override public void run() {
+ FullBackupJob.schedule(mContext, latency);
+ }
+ });
+ return false;
+ }
+
+ // Okay, the top thing is runnable now. Pop it off and get going.
+ mFullBackupQueue.remove(0);
+ AtomicBoolean latch = new AtomicBoolean(false);
+ String[] pkg = new String[] {entry.packageName};
+ mRunningFullBackupTask = new PerformFullTransportBackupTask(null, pkg, true,
+ scheduledJob, latch);
+ (new Thread(mRunningFullBackupTask)).start();
+ }
+
+ return true;
+ }
+
+ // The job scheduler says our constraints don't hold any more,
+ // so tear down any ongoing backup task right away.
+ void endFullBackup() {
+ synchronized (mQueueLock) {
+ if (mRunningFullBackupTask != null) {
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Telling running backup to stop");
+ }
+ mRunningFullBackupTask.setRunning(false);
+ }
+ }
+ }
+
// ----- Restore infrastructure -----
abstract class RestoreEngine {
@@ -7726,7 +8080,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
AtomicBoolean latch = new AtomicBoolean(false);
- PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(null, pkgNames, latch);
+ PerformFullTransportBackupTask task =
+ new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
(new Thread(task, "full-transport-master")).start();
synchronized (latch) {
try {
@@ -7915,6 +8270,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
if (enable && !wasEnabled && mProvisioned) {
// if we've just been enabled, start scheduling backup passes
startBackupAlarmsLocked(BACKUP_INTERVAL);
+ scheduleNextFullBackupJob();
} else if (!enable) {
// No longer enabled, so stop running backups
if (DEBUG) Slog.i(TAG, "Opting out of backup");
@@ -8653,10 +9009,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
pw.println(" " + pkg);
}
- pw.println("Pending backup: " + mPendingBackups.size());
+ pw.println("Pending key/value backup: " + mPendingBackups.size());
for (BackupRequest req : mPendingBackups.values()) {
pw.println(" " + req);
}
+
+ pw.println("Full backup queue:" + mFullBackupQueue.size());
+ for (FullBackupEntry entry : mFullBackupQueue) {
+ pw.print(" "); pw.print(entry.lastBackup);
+ pw.print(" : "); pw.println(entry.packageName);
+ }
}
}
}
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
new file mode 100644
index 0000000..deb2293
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 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.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.app.job.JobInfo.NetworkType;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+public class FullBackupJob extends JobService {
+ private static final String TAG = "FullBackupJob";
+ private static final boolean DEBUG = true;
+
+ private static ComponentName sIdleService =
+ new ComponentName("android", FullBackupJob.class.getName());
+
+ private static final int JOB_ID = 0x5038;
+
+ JobParameters mParams;
+
+ public static void schedule(Context ctx, long minDelay) {
+ JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sIdleService)
+ .setRequiresDeviceIdle(true)
+ .setRequiredNetworkCapabilities(NetworkType.UNMETERED)
+ .setRequiresCharging(true);
+ if (minDelay > 0) {
+ builder.setMinimumLatency(minDelay);
+ }
+ js.schedule(builder.build());
+ }
+
+ // callback from the Backup Manager Service: it's finished its work for this pass
+ public void finishBackupPass() {
+ if (mParams != null) {
+ jobFinished(mParams, false);
+ mParams = null;
+ }
+ }
+
+ // ----- scheduled job interface -----
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ mParams = params;
+ BackupManagerService service = BackupManagerService.getInstance();
+ return service.beginFullBackup(this);
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ if (mParams != null) {
+ mParams = null;
+ BackupManagerService service = BackupManagerService.getInstance();
+ service.endFullBackup();
+ }
+ return false;
+ }
+
+}
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 451960c..2213934 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -184,6 +184,16 @@ public class IdleController extends StateController {
@Override
public void dumpControllerState(PrintWriter pw) {
-
+ synchronized (mTrackedTasks) {
+ pw.print("Idle: ");
+ pw.println(mIdleTracker.isIdle() ? "true" : "false");
+ pw.println(mTrackedTasks.size());
+ for (int i = 0; i < mTrackedTasks.size(); i++) {
+ final JobStatus js = mTrackedTasks.get(i);
+ pw.print(" ");
+ pw.print(String.valueOf(js.hashCode()).substring(0, 3));
+ pw.println("..");
+ }
+ }
}
}