Merge "Quota exceeded API in BackupAgent"

This commit is contained in:
Sergey Poromov
2016-01-27 12:15:50 +00:00
committed by Android (Google) Code Review
11 changed files with 198 additions and 16 deletions

View File

@@ -6105,6 +6105,7 @@ package android.app.backup {
method public void onCreate();
method public void onDestroy();
method public void onFullBackup(android.app.backup.FullBackupDataOutput) throws java.io.IOException;
method public void onQuotaExceeded(long, long);
method public abstract void onRestore(android.app.backup.BackupDataInput, int, android.os.ParcelFileDescriptor) throws java.io.IOException;
method public void onRestoreFile(android.os.ParcelFileDescriptor, long, java.io.File, int, long, long) throws java.io.IOException;
method public void onRestoreFinished();

View File

@@ -6254,6 +6254,7 @@ package android.app.backup {
method public void onCreate();
method public void onDestroy();
method public void onFullBackup(android.app.backup.FullBackupDataOutput) throws java.io.IOException;
method public void onQuotaExceeded(long, long);
method public abstract void onRestore(android.app.backup.BackupDataInput, int, android.os.ParcelFileDescriptor) throws java.io.IOException;
method public void onRestoreFile(android.os.ParcelFileDescriptor, long, java.io.File, int, long, long) throws java.io.IOException;
method public void onRestoreFinished();
@@ -6316,6 +6317,7 @@ package android.app.backup {
field public static final int ERROR_PACKAGE_NOT_FOUND = -2002; // 0xfffff82e
field public static final int ERROR_TRANSPORT_ABORTED = -1000; // 0xfffffc18
field public static final int ERROR_TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16
field public static final int ERROR_TRANSPORT_QUOTA_EXCEEDED = -1005; // 0xfffffc13
field public static final int SUCCESS = 0; // 0x0
}
@@ -6348,6 +6350,7 @@ package android.app.backup {
method public int finishBackup();
method public void finishRestore();
method public android.app.backup.RestoreSet[] getAvailableRestoreSets();
method public long getBackupQuota(java.lang.String, boolean);
method public android.os.IBinder getBinder();
method public long getCurrentRestoreSet();
method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor);
@@ -6373,6 +6376,7 @@ package android.app.backup {
field public static final int TRANSPORT_NOT_INITIALIZED = -1001; // 0xfffffc17
field public static final int TRANSPORT_OK = 0; // 0x0
field public static final int TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16
field public static final int TRANSPORT_QUOTA_EXCEEDED = -1005; // 0xfffffc13
}
public class FileBackupHelper extends android.app.backup.FileBackupHelperBase implements android.app.backup.BackupHelper {

View File

@@ -6107,6 +6107,7 @@ package android.app.backup {
method public void onCreate();
method public void onDestroy();
method public void onFullBackup(android.app.backup.FullBackupDataOutput) throws java.io.IOException;
method public void onQuotaExceeded(long, long);
method public abstract void onRestore(android.app.backup.BackupDataInput, int, android.os.ParcelFileDescriptor) throws java.io.IOException;
method public void onRestoreFile(android.os.ParcelFileDescriptor, long, java.io.File, int, long, long) throws java.io.IOException;
method public void onRestoreFinished();

View File

@@ -104,6 +104,27 @@ oneway interface IBackupAgent {
*/
void doMeasureFullBackup(int token, IBackupManager callbackBinder);
/**
* Tells the application agent that the backup data size exceeded current transport quota.
* Later calls to {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}
* and {@link #onFullBackup(FullBackupDataOutput)} could use this information
* to reduce backup size under the limit.
* However, the quota can change, so do not assume that the value passed in here is absolute,
* similarly all subsequent backups should not be restricted to this size.
* This callback will be invoked before data has been put onto the wire in a preflight check,
* so it is relatively inexpensive to hit your quota.
* Apps that hit quota repeatedly without dealing with it can be subject to having their backup
* schedule reduced.
* The {@code quotaBytes} is a loose guideline b/c of metadata added by the backupmanager
* so apps should be more aggressive in trimming their backup set.
*
* @param backupDataBytes Expected or already processed amount of data.
* Could be less than total backup size if backup process was interrupted
* before finish of processing all backup data.
* @param quotaBytes Current amount of backup data that is allowed for the app.
*/
void doQuotaExceeded(long backupDataBytes, long quotaBytes);
/**
* Restore a single "file" to the application. The file was typically obtained from
* a full-backup dataset. The agent reads 'size' bytes of file content

View File

@@ -381,6 +381,28 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
/**
* Tells the application agent that the backup data size exceeded current transport quota.
* Later calls to {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}
* and {@link #onFullBackup(FullBackupDataOutput)} could use this information
* to reduce backup size under the limit.
* However, the quota can change, so do not assume that the value passed in here is absolute,
* similarly all subsequent backups should not be restricted to this size.
* This callback will be invoked before data has been put onto the wire in a preflight check,
* so it is relatively inexpensive to hit your quota.
* Apps that hit quota repeatedly without dealing with it can be subject to having their backup
* schedule reduced.
* The {@code quotaBytes} is a loose guideline b/c of metadata added by the backupmanager
* so apps should be more aggressive in trimming their backup set.
*
* @param backupDataBytes Expected or already processed amount of data.
* Could be less than total backup size if backup process was interrupted
* before finish of processing all backup data.
* @param quotaBytes Current amount of backup data that is allowed for the app.
*/
public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
}
/**
* Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
* If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
@@ -955,6 +977,21 @@ public abstract class BackupAgent extends ContextWrapper {
public void fail(String message) {
getHandler().post(new FailRunnable(message));
}
@Override
public void doQuotaExceeded(long backupDataBytes, long quotaBytes) {
long ident = Binder.clearCallingIdentity();
try {
BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes);
} catch (Exception e) {
Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw",
e);
throw e;
} finally {
waitForSharedPrefs();
Binder.restoreCallingIdentity(ident);
}
}
}
static class FailRunnable implements Runnable {

View File

@@ -109,6 +109,16 @@ public class BackupManager {
public static final int ERROR_TRANSPORT_PACKAGE_REJECTED =
BackupTransport.TRANSPORT_PACKAGE_REJECTED;
/**
* Returned when the transport reject the attempt to backup because
* backup data size exceeded current quota limit for this package.
*
* @hide
*/
@SystemApi
public static final int ERROR_TRANSPORT_QUOTA_EXCEEDED =
BackupTransport.TRANSPORT_QUOTA_EXCEEDED;
/**
* The {@link BackupAgent} for the requested package failed for some reason
* and didn't provide appropriate backup data.

View File

@@ -49,6 +49,7 @@ public class BackupTransport {
public static final int TRANSPORT_PACKAGE_REJECTED = -1002;
public static final int AGENT_ERROR = -1003;
public static final int AGENT_UNKNOWN = -1004;
public static final int TRANSPORT_QUOTA_EXCEEDED = -1005;
// Indicates that operation was initiated by user, not a scheduled one.
// Transport should ignore its own moratoriums for call with this flag set.
@@ -494,6 +495,18 @@ public class BackupTransport {
return true;
}
/**
* Ask the transport about current quota for backup size of the package.
*
* @param packageName ID of package to provide the quota.
* @param isFullBackup If set, transport should return limit for full data backup, otherwise
* for key-value backup.
* @return Current limit on full data backup size in bytes.
*/
public long getBackupQuota(String packageName, boolean isFullBackup) {
return Long.MAX_VALUE;
}
// ------------------------------------------------------------------------------------
// Full restore interfaces
@@ -676,6 +689,11 @@ public class BackupTransport {
return BackupTransport.this.isAppEligibleForBackup(targetPackage, isFullBackup);
}
@Override
public long getBackupQuota(String packageName, boolean isFullBackup) {
return BackupTransport.this.getBackupQuota(packageName, isFullBackup);
}
@Override
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
return BackupTransport.this.getNextFullRestoreDataChunk(socket);

View File

@@ -249,6 +249,16 @@ interface IBackupTransport {
*/
boolean isAppEligibleForBackup(in PackageInfo targetPackage, boolean isFullBackup);
/**
* Ask the transport about current quota for backup size of the package.
*
* @param packageName ID of package to provide the quota.
* @param isFullBackup If set, transport should return limit for full data backup, otherwise
* for key-value backup.
* @return Current limit on full data backup size in bytes.
*/
long getBackupQuota(String packageName, boolean isFullBackup);
// full restore stuff
/**

View File

@@ -71,6 +71,9 @@ public class LocalTransport extends BackupTransport {
// The currently-active restore set always has the same (nonzero!) token
private static final long CURRENT_SET_TOKEN = 1;
// Full backup size quota is set to reasonable value.
private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024;
private Context mContext;
private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN));
@@ -90,6 +93,7 @@ public class LocalTransport extends BackupTransport {
private FileInputStream mSocketInputStream;
private BufferedOutputStream mFullBackupOutputStream;
private byte[] mFullBackupBuffer;
private long mFullBackupSize;
private FileInputStream mCurFullRestoreStream;
private FileOutputStream mFullRestoreSocketStream;
@@ -314,8 +318,13 @@ public class LocalTransport extends BackupTransport {
@Override
public int checkFullBackupSize(long size) {
int result = TRANSPORT_OK;
// Decline zero-size "backups"
final int result = (size > 0) ? TRANSPORT_OK : TRANSPORT_PACKAGE_REJECTED;
if (size <= 0) {
result = TRANSPORT_PACKAGE_REJECTED;
} else if (size > FULL_BACKUP_SIZE_QUOTA) {
result = TRANSPORT_QUOTA_EXCEEDED;
}
if (result != TRANSPORT_OK) {
if (DEBUG) {
Log.v(TAG, "Declining backup of size " + size);
@@ -339,6 +348,7 @@ public class LocalTransport extends BackupTransport {
// sure to dup() our own copy of the socket fd. Transports which run in
// their own processes must not do this.
try {
mFullBackupSize = 0;
mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor());
mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor());
} catch (IOException e) {
@@ -359,6 +369,11 @@ public class LocalTransport extends BackupTransport {
return TRANSPORT_ERROR;
}
mFullBackupSize += numBytes;
if (mFullBackupSize > FULL_BACKUP_SIZE_QUOTA) {
return TRANSPORT_QUOTA_EXCEEDED;
}
if (numBytes > mFullBackupBuffer.length) {
mFullBackupBuffer = new byte[numBytes];
}
@@ -699,4 +714,8 @@ public class LocalTransport extends BackupTransport {
return TRANSPORT_OK;
}
@Override
public long getBackupQuota(String packageName, boolean isFullBackup) {
return isFullBackup ? FULL_BACKUP_SIZE_QUOTA : Long.MAX_VALUE;
}
}

View File

@@ -3168,8 +3168,9 @@ public class BackupManagerService {
ParcelFileDescriptor backupData = null;
mStatus = BackupTransport.TRANSPORT_OK;
long size = 0;
try {
int size = (int) mBackupDataName.length();
size = mBackupDataName.length();
if (size > 0) {
if (mStatus == BackupTransport.TRANSPORT_OK) {
backupData = ParcelFileDescriptor.open(mBackupDataName,
@@ -3214,6 +3215,10 @@ public class BackupManagerService {
sendBackupOnResult(mObserver, pkgName,
BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
} else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
sendBackupOnResult(mObserver, pkgName,
BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
} else {
// Actual transport-level failure to communicate the data to the backend
sendBackupOnResult(mObserver, pkgName, BackupManager.ERROR_TRANSPORT_ABORTED);
@@ -3234,6 +3239,20 @@ public class BackupManagerService {
// Success or single-package rejection. Proceed with the next app if any,
// otherwise we're done.
nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
} else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
if (MORE_DEBUG) {
Slog.d(TAG, "Package " + mCurrentPackage.packageName +
" hit quota limit on k/v backup");
}
if (mAgentBinder != null) {
try {
long quota = mTransport.getBackupQuota(mCurrentPackage.packageName, false);
mAgentBinder.doQuotaExceeded(size, quota);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to contact backup agent for quota exceeded");
}
}
nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
} else {
// Any other error here indicates a transport-level failure. That means
// we need to halt everything and reschedule everything for next time.
@@ -3476,6 +3495,7 @@ public class BackupManagerService {
OutputStream mOutput;
FullBackupPreflight mPreflightHook;
IFullBackupRestoreObserver mObserver;
IBackupAgent mAgent;
File mFilesDir;
File mManifestFile;
File mMetadataFile;
@@ -3562,14 +3582,14 @@ public class BackupManagerService {
int result = BackupTransport.TRANSPORT_OK;
Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName);
IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
mAgent = bindToAgentSynchronous(pkg.applicationInfo,
IApplicationThread.BACKUP_MODE_FULL);
if (agent != null) {
if (mAgent != null) {
ParcelFileDescriptor[] pipes = null;
try {
// Call the preflight hook, if any
if (mPreflightHook != null) {
result = mPreflightHook.preflightFullBackup(pkg, agent);
result = mPreflightHook.preflightFullBackup(pkg, mAgent);
if (MORE_DEBUG) {
Slog.v(TAG, "preflight returned " + result);
}
@@ -3592,7 +3612,7 @@ public class BackupManagerService {
UserHandle.USER_SYSTEM);
final int token = generateToken();
FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1],
FullBackupRunner runner = new FullBackupRunner(pkg, mAgent, pipes[1],
token, sendApk, !isSharedStorage, widgetBlob);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
@@ -3640,6 +3660,16 @@ public class BackupManagerService {
return result;
}
public void sendQuotaExceeded(final long backupDataBytes, final long quotaBytes) {
if (mAgent != null) {
try {
mAgent.doQuotaExceeded(backupDataBytes, quotaBytes);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception while telling agent about quota exceeded");
}
}
}
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
@@ -4344,6 +4374,15 @@ public class BackupManagerService {
}
} while (nRead > 0 && result == BackupTransport.TRANSPORT_OK);
if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
long quota = transport.getBackupQuota(currentPackage.packageName, true);
if (MORE_DEBUG) {
Slog.d(TAG, "Package hit quota limit " + currentPackage.packageName
+ ": " + totalRead + " of " + quota);
}
backupRunner.sendQuotaExceeded(totalRead, quota);
}
// If we've lost our running criteria, tell the transport to cancel
// and roll back this (partial) backup payload; otherwise tell it
// that we've reached the clean finish state.
@@ -4387,6 +4426,8 @@ public class BackupManagerService {
}
if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
sendBackupOnResult(mBackupObserver, currentPackage.packageName,
BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
if (DEBUG) {
Slog.i(TAG, "Transport rejected backup of "
+ currentPackage.packageName
@@ -4394,35 +4435,40 @@ public class BackupManagerService {
}
EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE,
currentPackage.packageName, "transport rejected");
sendBackupOnResult(mBackupObserver, currentPackage.packageName,
BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
// do nothing, clean up, and continue looping
} else if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
sendBackupOnResult(mBackupObserver, currentPackage.packageName,
BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
Slog.w(TAG, "Transport quota exceeded; aborting backup: " + result);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
currentPackage.packageName);
return;
} else if (result != BackupTransport.TRANSPORT_OK) {
Slog.w(TAG, "Transport failed; aborting backup: " + result);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
sendBackupOnResult(mBackupObserver, currentPackage.packageName,
BackupManager.ERROR_TRANSPORT_ABORTED);
Slog.w(TAG, "Transport failed; aborting backup: " + result);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
return;
} else {
// Success!
sendBackupOnResult(mBackupObserver, currentPackage.packageName,
BackupManager.SUCCESS);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_SUCCESS,
currentPackage.packageName);
logBackupComplete(currentPackage.packageName);
sendBackupOnResult(mBackupObserver, currentPackage.packageName,
BackupManager.SUCCESS);
}
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
currentPackage = null;
}
sendBackupFinished(mBackupObserver, BackupManager.SUCCESS);
if (DEBUG) {
Slog.i(TAG, "Full backup completed.");
}
sendBackupFinished(mBackupObserver, BackupManager.SUCCESS);
} catch (Exception e) {
Slog.w(TAG, "Exception trying full transport backup", e);
sendBackupFinished(mBackupObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
Slog.w(TAG, "Exception trying full transport backup", e);
} finally {
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
@@ -4503,6 +4549,14 @@ 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);
}
agent.doQuotaExceeded(totalSize, quota);
}
} catch (Exception e) {
Slog.w(TAG, "Exception preflighting " + pkg.packageName + ": " + e.getMessage());
result = BackupTransport.AGENT_ERROR;
@@ -4550,6 +4604,7 @@ public class BackupManagerService {
final PackageInfo mTarget;
final FullBackupPreflight mPreflight;
final CountDownLatch mLatch;
private FullBackupEngine mEngine;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
IBackupTransport transport, CountDownLatch latch) throws IOException {
@@ -4563,9 +4618,9 @@ public class BackupManagerService {
public void run() {
try {
FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
FullBackupEngine engine = new FullBackupEngine(out, mTarget.packageName,
mEngine = new FullBackupEngine(out, mTarget.packageName,
mPreflight, false);
engine.backupOnePackage(mTarget);
mEngine.backupOnePackage(mTarget);
} catch (Exception e) {
Slog.e(TAG, "Exception during full package backup of " + mTarget);
} finally {
@@ -4578,6 +4633,10 @@ public class BackupManagerService {
}
}
public void sendQuotaExceeded(final long backupDataBytes, final long quotaBytes) {
mEngine.sendQuotaExceeded(backupDataBytes, quotaBytes);
}
long expectedSize() {
return mPreflight.expectedSize();
}

View File

@@ -108,6 +108,7 @@ option java_package com.android.server
2826 backup_reset (Transport|3)
2827 backup_initialize
2828 backup_requested (Total|1|1),(Key-Value|1|1),(Full|1|1)
2829 backup_quota_exceeded (Package|3)
2830 restore_start (Transport|3),(Source|2|5)
2831 restore_transport_failure
2832 restore_agent_failure (Package|3),(Message|3)
@@ -119,6 +120,7 @@ option java_package com.android.server
2842 full_backup_transport_failure
2843 full_backup_success (Package|3)
2844 full_restore_package (Package|3)
2845 full_backup_quota_exceeded (Package|3)
2850 backup_transport_lifecycle (Transport|3),(Bound|1|1)