/* * Copyright (C) 2017 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.fullbackup; import static com.android.server.backup.RefactoredBackupManagerService.DEBUG; import static com.android.server.backup.RefactoredBackupManagerService.DEBUG_SCHEDULING; import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG; import static com.android.server.backup.RefactoredBackupManagerService.OP_PENDING; import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP; import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT; import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL; import android.app.IBackupAgent; import android.app.backup.BackupManager; import android.app.backup.BackupManagerMonitor; import android.app.backup.BackupProgress; import android.app.backup.BackupTransport; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.EventLog; import android.util.Log; import android.util.Slog; import com.android.internal.backup.IBackupTransport; import com.android.server.EventLogTags; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.FullBackupJob; import com.android.server.backup.RefactoredBackupManagerService; import com.android.server.backup.internal.Operation; import com.android.server.backup.utils.AppBackupUtils; import com.android.server.backup.utils.BackupManagerMonitorUtils; import com.android.server.backup.utils.BackupObserverUtils; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * Full backup task extension used for transport-oriented operation. * * Flow: * For each requested package: * - Spin off a new SinglePackageBackupRunner (mBackupRunner) for the current package. * - Wait until preflight is complete. (mBackupRunner.getPreflightResultBlocking()) * - If preflight data size is within limit, start reading data from agent pipe and writing * to transport pipe. While there is data to send, call transport.sendBackupData(int) to * tell the transport how many bytes to expect on its pipe. * - After sending all data, call transport.finishBackup() if things went well. And * transport.cancelFullBackup() otherwise. * * Interactions with mCurrentOperations: * - An entry for this object is added to mCurrentOperations for the entire lifetime of this * object. Used to cancel the operation. * - SinglePackageBackupRunner and SinglePackageBackupPreflight will put ephemeral entries * to get timeouts or operation complete callbacks. * * Handling cancels: * - The contract we provide is that the task won't interact with the transport after * handleCancel() is done executing. * - This task blocks at 3 points: 1. Preflight result check 2. Reading on agent side pipe * and 3. Get backup result from mBackupRunner. * - Bubbling up handleCancel to mBackupRunner handles all 3: 1. Calls handleCancel on the * preflight operation which counts down on the preflight latch. 2. Tears down the agent, * so read() returns -1. 3. Notifies mCurrentOpLock which unblocks * mBackupRunner.getBackupResultBlocking(). */ public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask { private static final String TAG = "PFTBT"; private RefactoredBackupManagerService backupManagerService; private final Object mCancelLock = new Object(); ArrayList mPackages; PackageInfo mCurrentPackage; boolean mUpdateSchedule; CountDownLatch mLatch; FullBackupJob mJob; // if a scheduled job needs to be finished afterwards IBackupObserver mBackupObserver; IBackupManagerMonitor mMonitor; boolean mUserInitiated; private volatile IBackupTransport mTransport; SinglePackageBackupRunner mBackupRunner; private final int mBackupRunnerOpToken; // This is true when a backup operation for some package is in progress. private volatile boolean mIsDoingBackup; private volatile boolean mCancelAll; private final int mCurrentOpToken; public PerformFullTransportBackupTask(RefactoredBackupManagerService backupManagerService, IFullBackupRestoreObserver observer, String[] whichPackages, boolean updateSchedule, FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver, IBackupManagerMonitor monitor, boolean userInitiated) { super(observer); this.backupManagerService = backupManagerService; mUpdateSchedule = updateSchedule; mLatch = latch; mJob = runningJob; mPackages = new ArrayList<>(whichPackages.length); mBackupObserver = backupObserver; mMonitor = monitor; mUserInitiated = userInitiated; mCurrentOpToken = backupManagerService.generateRandomIntegerToken(); mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken(); if (backupManagerService.isBackupOperationInProgress()) { if (DEBUG) { Slog.d(TAG, "Skipping full backup. A backup is already in progress."); } mCancelAll = true; return; } registerTask(); for (String pkg : whichPackages) { try { PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(pkg, PackageManager.GET_SIGNATURES); mCurrentPackage = info; if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo)) { // Cull any packages that have indicated that backups are not permitted, // that run as system-domain uids but do not define their own backup agents, // as well as any explicit mention of the 'special' shared-storage agent // package (we handle that one at the end). if (MORE_DEBUG) { Slog.d(TAG, "Ignoring ineligible package " + pkg); } mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null); BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg, BackupManager.ERROR_BACKUP_NOT_ALLOWED); continue; } else if (!AppBackupUtils.appGetsFullBackup(info)) { // Cull any packages that are found in the queue but now aren't supposed // to get full-data backup operations. if (MORE_DEBUG) { Slog.d(TAG, "Ignoring full-data backup of key/value participant " + pkg); } mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null); BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg, BackupManager.ERROR_BACKUP_NOT_ALLOWED); continue; } else if (AppBackupUtils.appIsStopped(info.applicationInfo)) { // 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); } mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null); BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg, BackupManager.ERROR_BACKUP_NOT_ALLOWED); continue; } mPackages.add(info); } catch (NameNotFoundException e) { Slog.i(TAG, "Requested package " + pkg + " not found; ignoring"); mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null); } } } private void registerTask() { synchronized (backupManagerService.getCurrentOpLock()) { Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken)); backupManagerService.getCurrentOperations().put( mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP)); } } public void unregisterTask() { backupManagerService.removeOperation(mCurrentOpToken); } @Override public void execute() { // Nothing to do. } @Override public void handleCancel(boolean cancelAll) { synchronized (mCancelLock) { // We only support 'cancelAll = true' case for this task. Cancelling of a single package // due to timeout is handled by SinglePackageBackupRunner and // SinglePackageBackupPreflight. if (!cancelAll) { Slog.wtf(TAG, "Expected cancelAll to be true."); } if (mCancelAll) { Slog.d(TAG, "Ignoring duplicate cancel call."); return; } mCancelAll = true; if (mIsDoingBackup) { backupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll); try { mTransport.cancelFullBackup(); } catch (RemoteException e) { Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e); // Can't do much. } } } } @Override public void operationComplete(long result) { // Nothing to do. } @Override public void run() { // data from the app, passed to us for bridging to the transport ParcelFileDescriptor[] enginePipes = null; // Pipe through which we write data to the transport ParcelFileDescriptor[] transportPipes = null; long backoff = 0; int backupRunStatus = BackupManager.SUCCESS; try { if (!backupManagerService.isEnabled() || !backupManagerService.isProvisioned()) { // Backups are globally disabled, so don't proceed. if (DEBUG) { Slog.i(TAG, "full backup requested but enabled=" + backupManagerService .isEnabled() + " provisioned=" + backupManagerService.isProvisioned() + "; ignoring"); } int monitoringEvent; if (backupManagerService.isProvisioned()) { monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED; } else { monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED; } mMonitor = BackupManagerMonitorUtils .monitorEvent(mMonitor, monitoringEvent, null, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null); mUpdateSchedule = false; backupRunStatus = BackupManager.ERROR_BACKUP_NOT_ALLOWED; return; } mTransport = backupManagerService.getTransportManager().getCurrentTransportBinder(); if (mTransport == null) { Slog.w(TAG, "Transport not present; full data backup not performed"); backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED; mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); return; } // Set up to send data to the transport final int N = mPackages.size(); final byte[] buffer = new byte[8192]; for (int i = 0; i < N; i++) { PackageInfo currentPackage = mPackages.get(i); String packageName = currentPackage.packageName; if (DEBUG) { Slog.i(TAG, "Initiating full-data transport backup of " + packageName + " token: " + mCurrentOpToken); } EventLog.writeEvent(EventLogTags.FULL_BACKUP_PACKAGE, packageName); transportPipes = ParcelFileDescriptor.createPipe(); // Tell the transport the data's coming int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; int backupPackageStatus; long quota = Long.MAX_VALUE; synchronized (mCancelLock) { if (mCancelAll) { break; } backupPackageStatus = mTransport.performFullBackup(currentPackage, transportPipes[0], flags); if (backupPackageStatus == BackupTransport.TRANSPORT_OK) { quota = mTransport.getBackupQuota(currentPackage.packageName, true /* isFullBackup */); // Now set up the backup engine / data source end of things enginePipes = ParcelFileDescriptor.createPipe(); mBackupRunner = new SinglePackageBackupRunner(enginePipes[1], currentPackage, mTransport, quota, mBackupRunnerOpToken); // The runner dup'd the pipe half, so we close it here enginePipes[1].close(); enginePipes[1] = null; mIsDoingBackup = true; } } if (backupPackageStatus == BackupTransport.TRANSPORT_OK) { // The transport has its own copy of the read end of the pipe, // so close ours now transportPipes[0].close(); transportPipes[0] = null; // Spin off the runner to fetch the app's data and pipe it // into the engine pipes (new Thread(mBackupRunner, "package-backup-bridge")).start(); // Read data off the engine pipe and pass it to the transport // pipe until we hit EOD on the input stream. We do not take // close() responsibility for these FDs into these stream wrappers. FileInputStream in = new FileInputStream( enginePipes[0].getFileDescriptor()); FileOutputStream out = new FileOutputStream( transportPipes[1].getFileDescriptor()); long totalRead = 0; final long preflightResult = mBackupRunner.getPreflightResultBlocking(); // Preflight result is negative if some error happened on preflight. if (preflightResult < 0) { if (MORE_DEBUG) { Slog.d(TAG, "Backup error after preflight of package " + packageName + ": " + preflightResult + ", not running backup."); } mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, BackupManagerMonitorUtils.putMonitoringExtra(null, BackupManagerMonitor.EXTRA_LOG_PREFLIGHT_ERROR, preflightResult)); backupPackageStatus = (int) preflightResult; } else { int nRead = 0; do { nRead = in.read(buffer); if (MORE_DEBUG) { Slog.v(TAG, "in.read(buffer) from app: " + nRead); } if (nRead > 0) { out.write(buffer, 0, nRead); synchronized (mCancelLock) { if (!mCancelAll) { backupPackageStatus = mTransport.sendBackupData(nRead); } } totalRead += nRead; if (mBackupObserver != null && preflightResult > 0) { BackupObserverUtils .sendBackupOnUpdate(mBackupObserver, packageName, new BackupProgress(preflightResult, totalRead)); } } } while (nRead > 0 && backupPackageStatus == BackupTransport.TRANSPORT_OK); // Despite preflight succeeded, package still can hit quota on flight. if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) { Slog.w(TAG, "Package hit quota limit in-flight " + packageName + ": " + totalRead + " of " + quota); mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null); mBackupRunner.sendQuotaExceeded(totalRead, quota); } } final int backupRunnerResult = mBackupRunner.getBackupResultBlocking(); synchronized (mCancelLock) { mIsDoingBackup = false; // If mCancelCurrent is true, we have already called cancelFullBackup(). if (!mCancelAll) { if (backupRunnerResult == BackupTransport.TRANSPORT_OK) { // If we were otherwise in a good state, now interpret the final // result based on what finishBackup() returns. If we're in a // failure case already, preserve that result and ignore whatever // finishBackup() reports. final int finishResult = mTransport.finishBackup(); if (backupPackageStatus == BackupTransport.TRANSPORT_OK) { backupPackageStatus = finishResult; } } else { mTransport.cancelFullBackup(); } } } // A transport-originated error here means that we've hit an error that the // runner doesn't know about, so it's still moving data but we're pulling the // rug out from under it. Don't ask for its result: we already know better // and we'll hang if we block waiting for it, since it relies on us to // read back the data it's writing into the engine. Just proceed with // a graceful failure. The runner/engine mechanism will tear itself // down cleanly when we close the pipes from this end. Transport-level // errors take precedence over agent/app-specific errors for purposes of // determining our course of action. if (backupPackageStatus == BackupTransport.TRANSPORT_OK) { // We still could fail in backup runner thread. if (backupRunnerResult != BackupTransport.TRANSPORT_OK) { // If there was an error in runner thread and // not TRANSPORT_ERROR here, overwrite it. backupPackageStatus = backupRunnerResult; } } else { if (MORE_DEBUG) { Slog.i(TAG, "Transport-level failure; cancelling agent work"); } } if (MORE_DEBUG) { Slog.i(TAG, "Done delivering backup data: result=" + backupPackageStatus); } if (backupPackageStatus != BackupTransport.TRANSPORT_OK) { Slog.e(TAG, "Error " + backupPackageStatus + " backing up " + packageName); } // Also ask the transport how long it wants us to wait before // moving on to the next package, if any. backoff = mTransport.requestFullBackupTime(); if (DEBUG_SCHEDULING) { Slog.i(TAG, "Transport suggested backoff=" + backoff); } } // Roll this package to the end of the backup queue if we're // in a queue-driven mode (regardless of success/failure) if (mUpdateSchedule) { backupManagerService.enqueueFullBackup(packageName, System.currentTimeMillis()); } if (backupPackageStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) { BackupObserverUtils .sendBackupOnPackageResult(mBackupObserver, packageName, BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED); if (DEBUG) { Slog.i(TAG, "Transport rejected backup of " + packageName + ", skipping"); } EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE, packageName, "transport rejected"); // Do nothing, clean up, and continue looping. } else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) { BackupObserverUtils .sendBackupOnPackageResult(mBackupObserver, packageName, BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED); if (DEBUG) { Slog.i(TAG, "Transport quota exceeded for package: " + packageName); EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED, packageName); } // Do nothing, clean up, and continue looping. } else if (backupPackageStatus == BackupTransport.AGENT_ERROR) { BackupObserverUtils .sendBackupOnPackageResult(mBackupObserver, packageName, BackupManager.ERROR_AGENT_FAILURE); Slog.w(TAG, "Application failure for package: " + packageName); EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName); backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo); // Do nothing, clean up, and continue looping. } else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) { BackupObserverUtils .sendBackupOnPackageResult(mBackupObserver, packageName, BackupManager.ERROR_BACKUP_CANCELLED); Slog.w(TAG, "Backup cancelled. package=" + packageName + ", cancelAll=" + mCancelAll); EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName); backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo); // Do nothing, clean up, and continue looping. } else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) { BackupObserverUtils .sendBackupOnPackageResult(mBackupObserver, packageName, BackupManager.ERROR_TRANSPORT_ABORTED); Slog.w(TAG, "Transport failed; aborting backup: " + backupPackageStatus); EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE); // Abort entire backup pass. backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED; return; } else { // Success! BackupObserverUtils .sendBackupOnPackageResult(mBackupObserver, packageName, BackupManager.SUCCESS); EventLog.writeEvent(EventLogTags.FULL_BACKUP_SUCCESS, packageName); backupManagerService.logBackupComplete(packageName); } cleanUpPipes(transportPipes); cleanUpPipes(enginePipes); if (currentPackage.applicationInfo != null) { Slog.i(TAG, "Unbinding agent in " + packageName); backupManagerService.addBackupTrace("unbinding " + packageName); try { backupManagerService.getActivityManager().unbindBackupAgent( currentPackage.applicationInfo); } catch (RemoteException e) { /* can't happen; activity manager is local */ } } } } catch (Exception e) { backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED; Slog.w(TAG, "Exception trying full transport backup", e); mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, BackupManagerMonitorUtils.putMonitoringExtra(null, BackupManagerMonitor.EXTRA_LOG_EXCEPTION_FULL_BACKUP, Log.getStackTraceString(e))); } finally { if (mCancelAll) { backupRunStatus = BackupManager.ERROR_BACKUP_CANCELLED; } if (DEBUG) { Slog.i(TAG, "Full backup completed with status: " + backupRunStatus); } BackupObserverUtils.sendBackupFinished(mBackupObserver, backupRunStatus); cleanUpPipes(transportPipes); cleanUpPipes(enginePipes); unregisterTask(); if (mJob != null) { mJob.finishBackupPass(); } synchronized (backupManagerService.getQueueLock()) { backupManagerService.setRunningFullBackupTask(null); } mLatch.countDown(); // Now that we're actually done with schedule-driven work, reschedule // the next pass based on the new queue state. if (mUpdateSchedule) { backupManagerService.scheduleNextFullBackupJob(backoff); } Slog.i(TAG, "Full data backup pass finished."); backupManagerService.getWakelock().release(); } } void cleanUpPipes(ParcelFileDescriptor[] pipes) { if (pipes != null) { if (pipes[0] != null) { ParcelFileDescriptor fd = pipes[0]; pipes[0] = null; try { fd.close(); } catch (IOException e) { Slog.w(TAG, "Unable to close pipe!"); } } if (pipes[1] != null) { ParcelFileDescriptor fd = pipes[1]; pipes[1] = null; try { fd.close(); } catch (IOException e) { Slog.w(TAG, "Unable to close pipe!"); } } } } // 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 AtomicLong mResult = new AtomicLong(BackupTransport.AGENT_ERROR); final CountDownLatch mLatch = new CountDownLatch(1); final IBackupTransport mTransport; final long mQuota; private final int mCurrentOpToken; SinglePackageBackupPreflight(IBackupTransport transport, long quota, int currentOpToken) { mTransport = transport; mQuota = quota; mCurrentOpToken = currentOpToken; } @Override public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) { int result; try { backupManagerService.prepareOperationTimeout( mCurrentOpToken, TIMEOUT_FULL_BACKUP_INTERVAL, this, OP_TYPE_BACKUP_WAIT); backupManagerService.addBackupTrace("preflighting"); if (MORE_DEBUG) { Slog.d(TAG, "Preflighting full payload of " + pkg.packageName); } agent.doMeasureFullBackup(mQuota, mCurrentOpToken, backupManagerService.getBackupManagerBinder()); // Now wait to get our result back. If this backstop timeout is reached without // the latch being thrown, flow will continue as though a result or "normal" // timeout had been produced. In case of a real backstop timeout, mResult // will still contain the value it was constructed with, AGENT_ERROR, which // intentionaly falls into the "just report failure" code. mLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS); long totalSize = mResult.get(); // If preflight timed out, mResult will contain error code as int. if (totalSize < 0) { return (int) totalSize; } if (MORE_DEBUG) { Slog.v(TAG, "Got preflight response; size=" + totalSize); } result = mTransport.checkFullBackupSize(totalSize); if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) { if (MORE_DEBUG) { Slog.d(TAG, "Package hit quota limit on preflight " + pkg.packageName + ": " + totalSize + " of " + mQuota); } agent.doQuotaExceeded(totalSize, mQuota); } } catch (Exception e) { Slog.w(TAG, "Exception preflighting " + pkg.packageName + ": " + e.getMessage()); result = BackupTransport.AGENT_ERROR; } return result; } @Override public void execute() { // Unused. } @Override public void operationComplete(long 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(); backupManagerService.removeOperation(mCurrentOpToken); } @Override public void handleCancel(boolean cancelAll) { if (MORE_DEBUG) { Slog.i(TAG, "Preflight cancelled; failing"); } mResult.set(BackupTransport.AGENT_ERROR); mLatch.countDown(); backupManagerService.removeOperation(mCurrentOpToken); } @Override public long getExpectedSizeOrErrorCode() { try { mLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS); return mResult.get(); } catch (InterruptedException e) { return BackupTransport.NO_MORE_DATA; } } } class SinglePackageBackupRunner implements Runnable, BackupRestoreTask { final ParcelFileDescriptor mOutput; final PackageInfo mTarget; final SinglePackageBackupPreflight mPreflight; final CountDownLatch mPreflightLatch; final CountDownLatch mBackupLatch; private final int mCurrentOpToken; private final int mEphemeralToken; private FullBackupEngine mEngine; private volatile int mPreflightResult; private volatile int mBackupResult; private final long mQuota; private volatile boolean mIsCancelled; SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target, IBackupTransport transport, long quota, int currentOpToken) throws IOException { mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor()); mTarget = target; mCurrentOpToken = currentOpToken; mEphemeralToken = backupManagerService.generateRandomIntegerToken(); mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken); mPreflightLatch = new CountDownLatch(1); mBackupLatch = new CountDownLatch(1); mPreflightResult = BackupTransport.AGENT_ERROR; mBackupResult = BackupTransport.AGENT_ERROR; mQuota = quota; registerTask(); } void registerTask() { synchronized (backupManagerService.getCurrentOpLock()) { backupManagerService.getCurrentOperations().put( mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP_WAIT)); } } void unregisterTask() { synchronized (backupManagerService.getCurrentOpLock()) { backupManagerService.getCurrentOperations().remove(mCurrentOpToken); } } @Override public void run() { FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor()); mEngine = new FullBackupEngine(backupManagerService, out, mPreflight, mTarget, false, this, mQuota, mCurrentOpToken); try { try { if (!mIsCancelled) { mPreflightResult = mEngine.preflightCheck(); } } finally { mPreflightLatch.countDown(); } // If there is no error on preflight, continue backup. if (mPreflightResult == BackupTransport.TRANSPORT_OK) { if (!mIsCancelled) { mBackupResult = mEngine.backupOnePackage(); } } } catch (Exception e) { Slog.e(TAG, "Exception during full package backup of " + mTarget.packageName); } finally { unregisterTask(); mBackupLatch.countDown(); try { mOutput.close(); } catch (IOException e) { Slog.w(TAG, "Error closing transport pipe in runner"); } } } public void sendQuotaExceeded(final long backupDataBytes, final long quotaBytes) { mEngine.sendQuotaExceeded(backupDataBytes, quotaBytes); } // If preflight succeeded, returns positive number - preflight size, // otherwise return negative error code. long getPreflightResultBlocking() { try { mPreflightLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS); if (mIsCancelled) { return BackupManager.ERROR_BACKUP_CANCELLED; } if (mPreflightResult == BackupTransport.TRANSPORT_OK) { return mPreflight.getExpectedSizeOrErrorCode(); } else { return mPreflightResult; } } catch (InterruptedException e) { return BackupTransport.AGENT_ERROR; } } int getBackupResultBlocking() { try { mBackupLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS); if (mIsCancelled) { return BackupManager.ERROR_BACKUP_CANCELLED; } return mBackupResult; } catch (InterruptedException e) { return BackupTransport.AGENT_ERROR; } } // BackupRestoreTask interface: specifically, timeout detection @Override public void execute() { /* intentionally empty */ } @Override public void operationComplete(long result) { /* intentionally empty */ } @Override public void handleCancel(boolean cancelAll) { if (DEBUG) { Slog.w(TAG, "Full backup cancel of " + mTarget.packageName); } mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null); mIsCancelled = true; // Cancel tasks spun off by this task. backupManagerService.handleCancel(mEphemeralToken, cancelAll); backupManagerService.tearDownAgentAndKill(mTarget.applicationInfo); // Free up everyone waiting on this task and its children. mPreflightLatch.countDown(); mBackupLatch.countDown(); // We are done with this operation. backupManagerService.removeOperation(mCurrentOpToken); } } }