Merge "BackupAgent#getBackupQuota() API"
diff --git a/api/current.txt b/api/current.txt
index e0d895e..65f80ee 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6604,6 +6604,7 @@
   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 a5f3081..edf778b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6841,6 +6841,7 @@
   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 a983cf5..6c699bb 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6627,6 +6627,7 @@
   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 eda9603..a07374b 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -41,6 +41,8 @@
      * @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 @@
     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 @@
      *        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 @@
      * @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 45d9fb7..11636a5 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -133,6 +133,8 @@
 
     Handler mHandler = null;
 
+    private long mBackupQuotaBytes = -1;
+
     Handler getHandler() {
         if (mHandler == null) {
             mHandler = new Handler(Looper.getMainLooper());
@@ -184,6 +186,21 @@
     }
 
     /**
+     * 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 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 @@
                 // guarantee themselves).
                 waitForSharedPrefs();
 
+                // Unset quota after onBackup is done.
+                mBackupQuotaBytes = -1;
+
                 Binder.restoreCallingIdentity(ident);
                 try {
                     callbackBinder.opComplete(token, 0);
@@ -971,10 +993,12 @@
 
         @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 @@
                 // ... 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 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 @@
                 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 5e8f4a2..f76b702 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -73,6 +73,8 @@
     // 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 @@
 
     @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 c4e2a53..6bf0e8d 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 @@
             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 @@
                         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 @@
                                 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 @@
                     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 @@
 
         }
 
-        void agentErrorCleanup() {
+        void errorCleanup() {
             mBackupDataName.delete();
             mNewStateName.delete();
             clearAgentState();
@@ -3473,6 +3480,7 @@
         File mMetadataFile;
         boolean mIncludeApks;
         PackageInfo mPkg;
+        private final long mQuota;
 
         class FullBackupRunner implements Runnable {
             PackageInfo mPackage;
@@ -3528,7 +3536,7 @@
                     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 @@
         }
 
         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 @@
             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 @@
                     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 @@
                     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 @@
 
                         // 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 @@
 
                             // 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 @@
             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 @@
                     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 @@
 
                     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 @@
             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();