diff options
Diffstat (limited to 'services/backup/java/com/android/server')
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); |