diff options
15 files changed, 316 insertions, 112 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 71474a3..d7c5562 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5908,6 +5908,7 @@ package android.app.backup { ctor public BackupTransport(); method public int abortFullRestore(); method public void cancelFullBackup(); + method public int checkFullBackupSize(long); method public int clearBackupData(android.content.pm.PackageInfo); method public android.content.Intent configurationIntent(); method public java.lang.String currentDestinationString(); @@ -32771,6 +32772,7 @@ package android.test.mock { method public android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle); method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle); method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); + method public void grantPermission(java.lang.String, java.lang.String, android.os.UserHandle); method public boolean hasSystemFeature(java.lang.String); method public boolean isSafeMode(); method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -32786,6 +32788,7 @@ package android.test.mock { method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int); method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int); method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int); + method public void revokePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public void setApplicationEnabledSetting(java.lang.String, int, int); method public void setComponentEnabledSetting(android.content.ComponentName, int, int); method public void setInstallerPackageName(java.lang.String, java.lang.String); diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl index 451af99..fe8e228 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -100,6 +100,11 @@ oneway interface IBackupAgent { void doFullBackup(in ParcelFileDescriptor data, int token, IBackupManager callbackBinder); /** + * Estimate how much data a full backup will deliver + */ + void doMeasureFullBackup(int token, IBackupManager callbackBinder); + + /** * 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 * from the provided file descriptor. diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 7f89100..2bf267a 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -424,10 +424,12 @@ public abstract class BackupAgent extends ContextWrapper { } // And now that we know where it lives, semantically, back it up appropriately - Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain + // In the measurement case, backupToTar() updates the size in output and returns + // without transmitting any file data. + if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain + " rootpath=" + rootpath); - FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, - output.getData()); + + FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output); } /** @@ -477,9 +479,8 @@ public abstract class BackupAgent extends ContextWrapper { continue; } - // Finally, back this file up before proceeding - FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, - output.getData()); + // Finally, back this file up (or measure it) before proceeding + FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, output); } } } @@ -640,7 +641,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } @@ -670,7 +671,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } @@ -692,10 +693,10 @@ public abstract class BackupAgent extends ContextWrapper { try { BackupAgent.this.onFullBackup(new FullBackupDataOutput(data)); } catch (IOException ex) { - Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); + Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); throw new RuntimeException(ex); } catch (RuntimeException ex) { - Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); + Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); throw ex; } finally { // ... and then again after, as in the doBackup() case @@ -713,13 +714,37 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } } } + public void doMeasureFullBackup(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(); + + waitForSharedPrefs(); + try { + BackupAgent.this.onFullBackup(measureOutput); + } catch (IOException ex) { + Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex); + throw new RuntimeException(ex); + } catch (RuntimeException ex) { + Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex); + throw ex; + } finally { + Binder.restoreCallingIdentity(ident); + try { + callbackBinder.opComplete(token, measureOutput.getSize()); + } catch (RemoteException e) { + // timeout, so we're safe + } + } + } + @Override public void doRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime, @@ -728,6 +753,7 @@ public abstract class BackupAgent extends ContextWrapper { try { BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime); } catch (IOException e) { + Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e); throw new RuntimeException(e); } finally { // Ensure that any side-effect SharedPreferences writes have landed @@ -735,7 +761,7 @@ public abstract class BackupAgent extends ContextWrapper { Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } @@ -747,13 +773,16 @@ public abstract class BackupAgent extends ContextWrapper { long ident = Binder.clearCallingIdentity(); try { BackupAgent.this.onRestoreFinished(); + } catch (Exception e) { + Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e); + throw e; } finally { // Ensure that any side-effect SharedPreferences writes have landed waitForSharedPrefs(); Binder.restoreCallingIdentity(ident); try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { // we'll time out anyway, so we're safe } diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index e853540..ca6dc69 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -393,6 +393,26 @@ public class BackupTransport { } /** + * Called after {@link #performFullBackup} to make sure that the transport is willing to + * handle a full-data backup operation of the specified size on the current package. + * If the transport returns anything other than TRANSPORT_OK, the package's backup + * operation will be skipped (and {@link #finishBackup() invoked} with no data for that + * package being passed to {@link #sendBackupData}. + * + * Added in MNC (API 23). + * + * @param size The estimated size of the full-data payload for this app. This includes + * manifest and archive format overhead, but is not guaranteed to be precise. + * @return TRANSPORT_OK if the platform is to proceed with the full-data backup, + * TRANSPORT_PACKAGE_REJECTED if the proposed payload size is too large for + * the transport to handle, or TRANSPORT_ERROR to indicate a fatal error + * condition that means the platform cannot perform a backup at this time. + */ + public int checkFullBackupSize(long size) { + return BackupTransport.TRANSPORT_OK; + } + + /** * Tells the transport to read {@code numBytes} bytes of data from the socket file * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)} * call, and deliver those bytes to the datastore. @@ -588,6 +608,11 @@ public class BackupTransport { } @Override + public int checkFullBackupSize(long size) { + return BackupTransport.this.checkFullBackupSize(size); + } + + @Override public int sendBackupData(int numBytes) throws RemoteException { return BackupTransport.this.sendBackupData(numBytes); } diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index e5b47c6..259884e 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -58,7 +58,7 @@ public class FullBackup { * @hide */ static public native int backupToTar(String packageName, String domain, - String linkdomain, String rootpath, String path, BackupDataOutput output); + String linkdomain, String rootpath, String path, FullBackupDataOutput output); /** * Copy data from a socket to the given File location on permanent storage. The diff --git a/core/java/android/app/backup/FullBackupDataOutput.java b/core/java/android/app/backup/FullBackupDataOutput.java index 99dab1f..94704b9 100644 --- a/core/java/android/app/backup/FullBackupDataOutput.java +++ b/core/java/android/app/backup/FullBackupDataOutput.java @@ -9,7 +9,14 @@ import android.os.ParcelFileDescriptor; */ public class FullBackupDataOutput { // Currently a name-scoping shim around BackupDataOutput - private BackupDataOutput mData; + private final BackupDataOutput mData; + private long mSize; + + /** @hide - used only in measure operation */ + public FullBackupDataOutput() { + mData = null; + mSize = 0; + } /** @hide */ public FullBackupDataOutput(ParcelFileDescriptor fd) { @@ -18,4 +25,14 @@ public class FullBackupDataOutput { /** @hide */ public BackupDataOutput getData() { return mData; } + + /** @hide - used for measurement pass */ + public void addSize(long size) { + if (size > 0) { + mSize += size; + } + } + + /** @hide - used for measurement pass */ + public long getSize() { return mSize; } } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index 41ad936..8f36dc4 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -286,11 +286,14 @@ interface IBackupManager { * Notify the backup manager that a BackupAgent has completed the operation * corresponding to the given token. * - * @param token The transaction token passed to a BackupAgent's doBackup() or - * doRestore() method. + * @param token The transaction token passed to the BackupAgent method being + * invoked. + * @param result In the case of a full backup measure operation, the estimated + * total file size that would result from the operation. Unused in all other + * cases. * {@hide} */ - void opComplete(int token); + void opComplete(int token, long result); /** * Make the device's backup and restore machinery (in)active. When it is inactive, diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 6158a7b..083d6c7 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -238,6 +238,7 @@ interface IBackupTransport { long requestFullBackupTime(); int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket); + int checkFullBackupSize(long size); int sendBackupData(int numBytes); void cancelFullBackup(); diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java index b5f2f37..037fd66 100644 --- a/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/core/java/com/android/server/backup/SystemBackupAgent.java @@ -104,9 +104,9 @@ public class SystemBackupAgent extends BackupAgentHelper { // steps during restore; the restore will happen properly when the individual // files are restored piecemeal. FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null, - WALLPAPER_INFO_DIR, WALLPAPER_INFO, output.getData()); + WALLPAPER_INFO_DIR, WALLPAPER_INFO, output); FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null, - WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output.getData()); + WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output); } @Override diff --git a/core/jni/android_app_backup_FullBackup.cpp b/core/jni/android_app_backup_FullBackup.cpp index 2c02b37..63b2e2a 100644 --- a/core/jni/android_app_backup_FullBackup.cpp +++ b/core/jni/android_app_backup_FullBackup.cpp @@ -15,6 +15,8 @@ */ #define LOG_TAG "FullBackup_native" +#include <sys/stat.h> + #include <utils/Log.h> #include <utils/String8.h> @@ -30,6 +32,12 @@ namespace android { +// android.app.backup.FullBackupDataOutput +static struct { + jfieldID mData; // type android.app.backup.BackupDataOutput + jmethodID addSize; +} sFullBackupDataOutput; + // android.app.backup.BackupDataOutput static struct { // This is actually a native pointer to the underlying BackupDataWriter instance @@ -70,7 +78,7 @@ static struct { * linkdomain: where a symlink points for purposes of rewriting; current unused * rootpath: prefix to be snipped from full path when encoding in tar * path: absolute path to the file to be saved - * dataOutput: the BackupDataOutput object that we're saving into + * dataOutput: the FullBackupDataOutput object that we're saving into */ static jint backupToTar(JNIEnv* env, jobject clazz, jstring packageNameObj, jstring domainObj, jstring linkdomain, @@ -91,15 +99,11 @@ static jint backupToTar(JNIEnv* env, jobject clazz, jstring packageNameObj, if (rootchars) env->ReleaseStringUTFChars(rootpathObj, rootchars); if (packagenamechars) env->ReleaseStringUTFChars(packageNameObj, packagenamechars); - // Extract the data output fd - BackupDataWriter* writer = (BackupDataWriter*) env->GetLongField(dataOutputObj, - sBackupDataOutput.mBackupWriter); - - // Validate - if (!writer) { - ALOGE("No output stream provided [%s]", path.string()); - return (jint) -1; - } + // Extract the data output fd. 'writer' ends up NULL in the measure-only case. + jobject bdo = env->GetObjectField(dataOutputObj, sFullBackupDataOutput.mData); + BackupDataWriter* writer = (bdo != NULL) + ? (BackupDataWriter*) env->GetLongField(bdo, sBackupDataOutput.mBackupWriter) + : NULL; if (path.length() < rootpath.length()) { ALOGE("file path [%s] shorter than root path [%s]", @@ -107,20 +111,30 @@ static jint backupToTar(JNIEnv* env, jobject clazz, jstring packageNameObj, return (jint) -1; } - return (jint) write_tarfile(packageName, domain, rootpath, path, writer); + off_t tarSize = 0; + jint err = write_tarfile(packageName, domain, rootpath, path, &tarSize, writer); + if (!err) { + //ALOGI("measured [%s] at %lld", path.string(), (long long) tarSize); + env->CallVoidMethod(dataOutputObj, sFullBackupDataOutput.addSize, (jlong) tarSize); + } + + return err; } static const JNINativeMethod g_methods[] = { { "backupToTar", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/app/backup/BackupDataOutput;)I", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/app/backup/FullBackupDataOutput;)I", (void*)backupToTar }, }; int register_android_app_backup_FullBackup(JNIEnv* env) { - jclass clazz = FindClassOrDie(env, "android/app/backup/BackupDataOutput"); + jclass fbdoClazz = FindClassOrDie(env, "android/app/backup/FullBackupDataOutput"); + sFullBackupDataOutput.mData = GetFieldIDOrDie(env, fbdoClazz, "mData", "Landroid/app/backup/BackupDataOutput;"); + sFullBackupDataOutput.addSize = GetMethodIDOrDie(env, fbdoClazz, "addSize", "(J)V"); - sBackupDataOutput.mBackupWriter = GetFieldIDOrDie(env, clazz, "mBackupWriter", "J"); + jclass bdoClazz = FindClassOrDie(env, "android/app/backup/BackupDataOutput"); + sBackupDataOutput.mBackupWriter = GetFieldIDOrDie(env, bdoClazz, "mBackupWriter", "J"); return RegisterMethodsOrDie(env, "android/app/backup/FullBackup", g_methods, NELEM(g_methods)); } diff --git a/include/androidfw/BackupHelpers.h b/include/androidfw/BackupHelpers.h index 0841af6..fc1ad47 100644 --- a/include/androidfw/BackupHelpers.h +++ b/include/androidfw/BackupHelpers.h @@ -17,6 +17,8 @@ #ifndef _UTILS_BACKUP_HELPERS_H #define _UTILS_BACKUP_HELPERS_H +#include <sys/stat.h> + #include <utils/Errors.h> #include <utils/String8.h> #include <utils/KeyedVector.h> @@ -135,7 +137,8 @@ int back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapsh char const* const* files, char const* const *keys, int fileCount); int write_tarfile(const String8& packageName, const String8& domain, - const String8& rootPath, const String8& filePath, BackupDataWriter* outputStream); + const String8& rootPath, const String8& filePath, off_t* outSize, + BackupDataWriter* outputStream); class RestoreHelperBase { diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp index 227de3b..9300794 100644 --- a/libs/androidfw/BackupHelpers.cpp +++ b/libs/androidfw/BackupHelpers.cpp @@ -478,7 +478,8 @@ void send_tarfile_chunk(BackupDataWriter* writer, const char* buffer, size_t siz } int write_tarfile(const String8& packageName, const String8& domain, - const String8& rootpath, const String8& filepath, BackupDataWriter* writer) + const String8& rootpath, const String8& filepath, off_t* outSize, + BackupDataWriter* writer) { // In the output stream everything is stored relative to the root const char* relstart = filepath.string() + rootpath.length(); @@ -488,6 +489,7 @@ int write_tarfile(const String8& packageName, const String8& domain, // If relpath is empty, it means this is the top of one of the standard named // domain directories, so we should just skip it if (relpath.length() == 0) { + *outSize = 0; return 0; } @@ -517,12 +519,25 @@ int write_tarfile(const String8& packageName, const String8& domain, return err; } + // very large files need a pax extended size header + if (s.st_size > 077777777777LL) { + needExtended = true; + } + String8 fullname; // for pax later on String8 prefix; const int isdir = S_ISDIR(s.st_mode); if (isdir) s.st_size = 0; // directories get no actual data in the tar stream + // Report the size, including a rough tar overhead estimation: 512 bytes for the + // overall tar file-block header, plus 2 blocks if using the pax extended format, + // plus the raw content size rounded up to a multiple of 512. + *outSize = 512 + (needExtended ? 1024 : 0) + 512*((s.st_size + 511)/512); + + // Measure case: we've returned the size; now return without moving data + if (!writer) return 0; + // !!! TODO: use mmap when possible to avoid churning the buffer cache // !!! TODO: this will break with symlinks; need to use readlink(2) int fd = open(filepath.string(), O_RDONLY); @@ -560,10 +575,6 @@ int write_tarfile(const String8& packageName, const String8& domain, snprintf(buf + 116, 8, "0%lo", (unsigned long)s.st_gid); // [ 124 : 12 ] file size in bytes - if (s.st_size > 077777777777LL) { - // very large files need a pax extended size header - needExtended = true; - } snprintf(buf + 124, 12, "%011llo", (isdir) ? 0LL : s.st_size); // [ 136 : 12 ] last mod time as a UTC time_t diff --git a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java index 0485334..0f8ccd7 100644 --- a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java +++ b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/ObbBackupService.java @@ -17,8 +17,8 @@ package com.android.sharedstoragebackup; import android.app.Service; -import android.app.backup.BackupDataOutput; import android.app.backup.FullBackup; +import android.app.backup.FullBackupDataOutput; import android.app.backup.IBackupManager; import android.content.Intent; import android.os.Environment; @@ -67,7 +67,7 @@ public class ObbBackupService extends Service { Log.i(TAG, obbList.size() + " files to back up"); } final String rootPath = obbDir.getCanonicalPath(); - final BackupDataOutput out = new BackupDataOutput(outFd); + final FullBackupDataOutput out = new FullBackupDataOutput(data); for (File f : obbList) { final String filePath = f.getCanonicalPath(); if (DEBUG) { @@ -92,7 +92,7 @@ public class ObbBackupService extends Service { } try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { } } @@ -119,7 +119,7 @@ public class ObbBackupService extends Service { Log.i(TAG, "Exception restoring OBB " + path, e); } finally { try { - callbackBinder.opComplete(token); + callbackBinder.opComplete(token, 0); } catch (RemoteException e) { } } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 31f9e22..1a71fad 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -29,6 +29,7 @@ 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; @@ -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; @@ -726,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); } @@ -2224,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(); @@ -2792,7 +2794,7 @@ public class BackupManagerService { } @Override - public void operationComplete() { + public void operationComplete(int unusedResult) { // The agent reported back to us! if (mBackupData == null) { @@ -3128,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; @@ -3160,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; @@ -3204,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); @@ -3222,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; @@ -3282,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(); @@ -3781,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); @@ -3828,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; @@ -3944,10 +3971,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; @@ -3972,6 +3999,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); @@ -4055,10 +4085,7 @@ 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. @@ -4094,15 +4121,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 { + IBackupTransport transport, CountDownLatch latch) throws IOException { mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor()); mTarget = target; + mPreflight = new SinglePackageBackupPreflight(transport); mLatch = latch; } @@ -4110,15 +4201,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) { @@ -4126,7 +4215,6 @@ public class BackupManagerService { } } } - } } @@ -4258,7 +4346,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); @@ -7895,7 +7983,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 @@ -8395,17 +8483,18 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF Slog.d(TAG, "fullTransportBackup()"); } - AtomicBoolean latch = new AtomicBoolean(false); + CountDownLatch latch = new CountDownLatch(1); PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(null, pkgNames, false, null, latch); (new Thread(task, "full-transport-master")).start(); - synchronized (latch) { + do { try { - while (latch.get() == false) { - latch.wait(); - } - } catch (InterruptedException e) {} - } + latch.await(); + break; + } catch (InterruptedException e) { + // Just go back to waiting for the latch to indicate completion + } + } while (true); if (DEBUG) { Slog.d(TAG, "Done with full transport backup."); } @@ -8948,8 +9037,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); @@ -8962,6 +9053,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); } } diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 2e84fbe..99bbdae 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -309,10 +309,10 @@ 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); } } |