diff options
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | api/test-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/app/IBackupAgent.aidl | 10 | ||||
| -rw-r--r-- | core/java/android/app/backup/BackupAgent.java | 37 | ||||
| -rw-r--r-- | core/java/com/android/internal/backup/LocalTransport.java | 4 | ||||
| -rw-r--r-- | services/backup/java/com/android/server/backup/BackupManagerService.java | 58 |
7 files changed, 84 insertions, 28 deletions
diff --git a/api/current.txt b/api/current.txt index 1a56525ab153..756fdde8b064 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6603,6 +6603,7 @@ package android.app.backup { public abstract class BackupAgent extends android.content.ContextWrapper { ctor public BackupAgent(); method public final void fullBackupFile(java.io.File, android.app.backup.FullBackupDataOutput); + method public long getBackupQuota(); method public abstract void onBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor) throws java.io.IOException; method public void onCreate(); method public void onDestroy(); diff --git a/api/system-current.txt b/api/system-current.txt index 06411d08dccd..7a1aad75076a 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6840,6 +6840,7 @@ package android.app.backup { public abstract class BackupAgent extends android.content.ContextWrapper { ctor public BackupAgent(); method public final void fullBackupFile(java.io.File, android.app.backup.FullBackupDataOutput); + method public long getBackupQuota(); method public abstract void onBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor) throws java.io.IOException; method public void onCreate(); method public void onDestroy(); diff --git a/api/test-current.txt b/api/test-current.txt index cde9bdc63faa..5d8c105ed95a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6626,6 +6626,7 @@ package android.app.backup { public abstract class BackupAgent extends android.content.ContextWrapper { ctor public BackupAgent(); method public final void fullBackupFile(java.io.File, android.app.backup.FullBackupDataOutput); + method public long getBackupQuota(); method public abstract void onBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor) throws java.io.IOException; method public void onCreate(); method public void onDestroy(); diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl index eda960385aca..a07374b80408 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -41,6 +41,8 @@ oneway interface IBackupAgent { * @param newState Read-write file, empty when onBackup() is called, * where the new state blob is to be recorded. * + * @param quota Quota reported by the transport for this backup operation (in bytes). + * * @param token Opaque token identifying this transaction. This must * be echoed back to the backup service binder once the new * data has been written to the data and newState files. @@ -51,7 +53,7 @@ oneway interface IBackupAgent { void doBackup(in ParcelFileDescriptor oldState, in ParcelFileDescriptor data, in ParcelFileDescriptor newState, - int token, IBackupManager callbackBinder); + long quotaBytes, int token, IBackupManager callbackBinder); /** * Restore an entire data snapshot to the application. @@ -89,6 +91,8 @@ oneway interface IBackupAgent { * The data must be formatted correctly for the resulting archive to be * legitimate, so that will be tightly controlled by the available API. * + * @param quota Quota reported by the transport for this backup operation (in bytes). + * * @param token Opaque token identifying this transaction. This must * be echoed back to the backup service binder once the agent is * finished restoring the application based on the restore data @@ -97,12 +101,12 @@ oneway interface IBackupAgent { * @param callbackBinder Binder on which to indicate operation completion, * passed here as a convenience to the agent. */ - void doFullBackup(in ParcelFileDescriptor data, int token, IBackupManager callbackBinder); + void doFullBackup(in ParcelFileDescriptor data, long quotaBytes, int token, IBackupManager callbackBinder); /** * Estimate how much data a full backup will deliver */ - void doMeasureFullBackup(int token, IBackupManager callbackBinder); + void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder); /** * Tells the application agent that the backup data size exceeded current transport quota. diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 45d9fb718c97..11636a5a7c0c 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -133,6 +133,8 @@ public abstract class BackupAgent extends ContextWrapper { Handler mHandler = null; + private long mBackupQuotaBytes = -1; + Handler getHandler() { if (mHandler == null) { mHandler = new Handler(Looper.getMainLooper()); @@ -184,6 +186,21 @@ public abstract class BackupAgent extends ContextWrapper { } /** + * Returns the quota in bytes for the currently requested backup operation. The value can + * vary for each operation depending on the type of backup being done. + * + * <p>Can be called only from {@link BackupAgent#onFullBackup(FullBackupDataOutput)} or + * {@link BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}. + */ + public long getBackupQuota() { + if (mBackupQuotaBytes < 0) { + throw new IllegalStateException( + "Backup quota is available only during backup operations."); + } + return mBackupQuotaBytes; + } + + /** * The application is being asked to write any data changed since the last * time it performed a backup operation. The state data recorded during the * last backup pass is provided in the <code>oldState</code> file @@ -897,10 +914,12 @@ public abstract class BackupAgent extends ContextWrapper { public void doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, - int token, IBackupManager callbackBinder) throws RemoteException { + long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException { // Ensure that we're running with the app's normal permission level long ident = Binder.clearCallingIdentity(); + mBackupQuotaBytes = quotaBytes; + if (DEBUG) Log.v(TAG, "doBackup() invoked"); BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor()); @@ -918,6 +937,9 @@ public abstract class BackupAgent extends ContextWrapper { // guarantee themselves). waitForSharedPrefs(); + // Unset quota after onBackup is done. + mBackupQuotaBytes = -1; + Binder.restoreCallingIdentity(ident); try { callbackBinder.opComplete(token, 0); @@ -971,10 +993,12 @@ public abstract class BackupAgent extends ContextWrapper { @Override public void doFullBackup(ParcelFileDescriptor data, - int token, IBackupManager callbackBinder) { + long quotaBytes, int token, IBackupManager callbackBinder) { // Ensure that we're running with the app's normal permission level long ident = Binder.clearCallingIdentity(); + mBackupQuotaBytes = quotaBytes; + if (DEBUG) Log.v(TAG, "doFullBackup() invoked"); // Ensure that any SharedPreferences writes have landed *before* @@ -993,6 +1017,9 @@ public abstract class BackupAgent extends ContextWrapper { // ... and then again after, as in the doBackup() case waitForSharedPrefs(); + // Unset quota after onFullBackup is done. + mBackupQuotaBytes = -1; + // Send the EOD marker indicating that there is no more data // forthcoming from this agent. try { @@ -1016,11 +1043,13 @@ public abstract class BackupAgent extends ContextWrapper { } } - public void doMeasureFullBackup(int token, IBackupManager callbackBinder) { + public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) { // Ensure that we're running with the app's normal permission level final long ident = Binder.clearCallingIdentity(); FullBackupDataOutput measureOutput = new FullBackupDataOutput(); + mBackupQuotaBytes = quotaBytes; + waitForSharedPrefs(); try { BackupAgent.this.onFullBackup(measureOutput); @@ -1031,6 +1060,8 @@ public abstract class BackupAgent extends ContextWrapper { Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex); throw ex; } finally { + // Unset quota after onFullBackup is done. + mBackupQuotaBytes = -1; Binder.restoreCallingIdentity(ident); try { callbackBinder.opComplete(token, measureOutput.getSize()); diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 5e8f4a250816..f76b702a1ccc 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -73,6 +73,8 @@ public class LocalTransport extends BackupTransport { // Full backup size quota is set to reasonable value. private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024; + private static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024; + private Context mContext; private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN)); @@ -712,6 +714,6 @@ public class LocalTransport extends BackupTransport { @Override public long getBackupQuota(String packageName, boolean isFullBackup) { - return isFullBackup ? FULL_BACKUP_SIZE_QUOTA : Long.MAX_VALUE; + return isFullBackup ? FULL_BACKUP_SIZE_QUOTA : KEY_VALUE_BACKUP_SIZE_QUOTA; } } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index c4e2a5399042..6bf0e8d8c2cd 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -2906,6 +2906,7 @@ public class BackupManagerService { mNewState = null; final int token = generateToken(); + boolean callingAgent = false; try { // Look up the package info & signatures. This is first so that if it // throws an exception, there's no file setup yet that would need to @@ -2939,18 +2940,24 @@ public class BackupManagerService { ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE); + final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */); + callingAgent = true; + // Initiate the target's backup pass addBackupTrace("setting timeout"); prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, this); addBackupTrace("calling agent doBackup()"); - agent.doBackup(mSavedState, mBackupData, mNewState, token, mBackupManagerBinder); + + agent.doBackup(mSavedState, mBackupData, mNewState, quota, token, + mBackupManagerBinder); } catch (Exception e) { - Slog.e(TAG, "Error invoking for backup on " + packageName); + Slog.e(TAG, "Error invoking for backup on " + packageName + ". " + e); addBackupTrace("exception: " + e); EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, e.toString()); - agentErrorCleanup(); - return BackupTransport.AGENT_ERROR; + errorCleanup(); + return callingAgent ? BackupTransport.AGENT_ERROR + : BackupTransport.TRANSPORT_ERROR; } finally { if (mNonIncremental) { blankStateName.delete(); @@ -3093,8 +3100,8 @@ public class BackupManagerService { mBackupHandler.removeMessages(MSG_TIMEOUT); sendBackupOnPackageResult(mObserver, pkgName, BackupManager.ERROR_AGENT_FAILURE); - agentErrorCleanup(); - // agentErrorCleanup() implicitly executes next state properly + errorCleanup(); + // errorCleanup() implicitly executes next state properly return; } in.skipEntityData(); @@ -3240,7 +3247,7 @@ public class BackupManagerService { BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT, mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT); addBackupTrace("timeout of " + mCurrentPackage.packageName); - agentErrorCleanup(); + errorCleanup(); dataChangedImpl(mCurrentPackage.packageName); } @@ -3265,7 +3272,7 @@ public class BackupManagerService { } - void agentErrorCleanup() { + void errorCleanup() { mBackupDataName.delete(); mNewStateName.delete(); clearAgentState(); @@ -3473,6 +3480,7 @@ public class BackupManagerService { File mMetadataFile; boolean mIncludeApks; PackageInfo mPkg; + private final long mQuota; class FullBackupRunner implements Runnable { PackageInfo mPackage; @@ -3528,7 +3536,7 @@ public class BackupManagerService { if (DEBUG) Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName); prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL, mTimeoutMonitor /* in parent class */); - mAgent.doFullBackup(mPipe, mToken, mBackupManagerBinder); + mAgent.doFullBackup(mPipe, mQuota, mToken, mBackupManagerBinder); } catch (IOException e) { Slog.e(TAG, "Error running full backup for " + mPackage.packageName); } catch (RemoteException e) { @@ -3543,7 +3551,7 @@ public class BackupManagerService { } FullBackupEngine(OutputStream output, FullBackupPreflight preflightHook, PackageInfo pkg, - boolean alsoApks, BackupRestoreTask timeoutMonitor) { + boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota) { mOutput = output; mPreflightHook = preflightHook; mPkg = pkg; @@ -3552,6 +3560,7 @@ public class BackupManagerService { mFilesDir = new File("/data/system"); mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME); mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME); + mQuota = quota; } public int preflightCheck() throws RemoteException { @@ -4147,7 +4156,8 @@ public class BackupManagerService { final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); - mBackupEngine = new FullBackupEngine(out, null, pkg, mIncludeApks, this); + mBackupEngine = new FullBackupEngine(out, null, pkg, mIncludeApks, + this /* BackupRestoreTask */, Long.MAX_VALUE /* quota */); sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName); // Don't need to check preflight result as there is no preflight hook. @@ -4340,6 +4350,9 @@ public class BackupManagerService { int backupPackageStatus = transport.performFullBackup(currentPackage, transportPipes[0], flags); if (backupPackageStatus == BackupTransport.TRANSPORT_OK) { + final long quota = transport.getBackupQuota(currentPackage.packageName, + true /* isFullBackup */); + // The transport has its own copy of the read end of the pipe, // so close ours now transportPipes[0].close(); @@ -4347,9 +4360,10 @@ public class BackupManagerService { // Now set up the backup engine / data source end of things enginePipes = ParcelFileDescriptor.createPipe(); + SinglePackageBackupRunner backupRunner = new SinglePackageBackupRunner(enginePipes[1], currentPackage, - transport); + transport, quota); // The runner dup'd the pipe half, so we close it here enginePipes[1].close(); enginePipes[1] = null; @@ -4402,7 +4416,6 @@ public class BackupManagerService { // Despite preflight succeeded, package still can hit quota on flight. if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) { - long quota = transport.getBackupQuota(packageName, true); Slog.w(TAG, "Package hit quota limit in-flight " + packageName + ": " + totalRead + " of " + quota); backupRunner.sendQuotaExceeded(totalRead, quota); @@ -4587,9 +4600,11 @@ public class BackupManagerService { final AtomicLong mResult = new AtomicLong(BackupTransport.AGENT_ERROR); final CountDownLatch mLatch = new CountDownLatch(1); final IBackupTransport mTransport; + final long mQuota; - public SinglePackageBackupPreflight(IBackupTransport transport) { + public SinglePackageBackupPreflight(IBackupTransport transport, long quota) { mTransport = transport; + mQuota = quota; } @Override @@ -4602,7 +4617,7 @@ public class BackupManagerService { if (MORE_DEBUG) { Slog.d(TAG, "Preflighting full payload of " + pkg.packageName); } - agent.doMeasureFullBackup(token, mBackupManagerBinder); + agent.doMeasureFullBackup(mQuota, token, mBackupManagerBinder); // Now wait to get our result back. If this backstop timeout is reached without // the latch being thrown, flow will continue as though a result or "normal" @@ -4622,12 +4637,11 @@ public class BackupManagerService { result = mTransport.checkFullBackupSize(totalSize); if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) { - final long quota = mTransport.getBackupQuota(pkg.packageName, true); if (MORE_DEBUG) { Slog.d(TAG, "Package hit quota limit on preflight " + - pkg.packageName + ": " + totalSize + " of " + quota); + pkg.packageName + ": " + totalSize + " of " + mQuota); } - agent.doQuotaExceeded(totalSize, quota); + agent.doQuotaExceeded(totalSize, mQuota); } } catch (Exception e) { Slog.w(TAG, "Exception preflighting " + pkg.packageName + ": " + e.getMessage()); @@ -4680,22 +4694,24 @@ public class BackupManagerService { private FullBackupEngine mEngine; private volatile int mPreflightResult; private volatile int mBackupResult; + private final long mQuota; SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target, - IBackupTransport transport) throws IOException { + IBackupTransport transport, long quota) throws IOException { mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor()); mTarget = target; - mPreflight = new SinglePackageBackupPreflight(transport); + mPreflight = new SinglePackageBackupPreflight(transport, quota); mPreflightLatch = new CountDownLatch(1); mBackupLatch = new CountDownLatch(1); mPreflightResult = BackupTransport.AGENT_ERROR; mBackupResult = BackupTransport.AGENT_ERROR; + mQuota = quota; } @Override public void run() { FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor()); - mEngine = new FullBackupEngine(out, mPreflight, mTarget, false, this); + mEngine = new FullBackupEngine(out, mPreflight, mTarget, false, this, mQuota); try { try { mPreflightResult = mEngine.preflightCheck(); |