diff options
author | 2011-06-24 14:58:49 -0700 | |
---|---|---|
committer | 2011-07-06 14:40:32 -0700 | |
commit | 79ec80db70d788f35aa13346e4684ecbd401bd84 (patch) | |
tree | fd18f64033def7461692f9542bf9e5f01afe2fe0 | |
parent | be87cc945b5b094060cbc77b77383aefc60265e4 (diff) |
Make full backup API available to apps
New methods for full backup/restore have been added to BackupAgent
(still hidden): onFullBackup() and onRestoreFile(). The former is the
entry point for a full app backup to adb/socket/etc: the app then writes
all of its files, entire, to the output. During restore, the latter
new callback is invoked, once for each file being restored.
The full backup/restore interface does not use the previously-defined
BackupDataInput / BackupDataOutput classes, because those classes
provide an API designed for incremental key/value data structuring.
Instead, a new FullBackupDataOutput class has been introduced, through
which we restrict apps' abilities to write data during a full backup
operation to *only* writing entire on-disk files via a new BackupAgent
method called fullBackupFile().
"FullBackupAgent" exists now solely as a concrete shell class that
can be instantiated in the case of apps that do not have their own
BackupAgent implementations.
Along with the API change, responsibility for backing up the .apk
file and OBB container has been moved into the framework rather than
have the application side of the transaction do it.
Change-Id: I12849b06b1a6e4c44d080587c1e9828a52b70dae
18 files changed, 530 insertions, 387 deletions
diff --git a/api/current.txt b/api/current.txt index d009a64f355c..d7e3cb30e8ac 100644 --- a/api/current.txt +++ b/api/current.txt @@ -183,14 +183,14 @@ package android { public static final class R.attr { ctor public R.attr(); field public static final int absListViewStyle = 16842858; // 0x101006a - field public static final int accessibilityEventTypes = 16843650; // 0x1010382 - field public static final int accessibilityFeedbackType = 16843652; // 0x1010384 - field public static final int accessibilityFlags = 16843654; // 0x1010386 + field public static final int accessibilityEventTypes = 16843649; // 0x1010381 + field public static final int accessibilityFeedbackType = 16843651; // 0x1010383 + field public static final int accessibilityFlags = 16843653; // 0x1010385 field public static final int accountPreferences = 16843423; // 0x101029f field public static final int accountType = 16843407; // 0x101028f field public static final int action = 16842797; // 0x101002d field public static final int actionBarSize = 16843499; // 0x10102eb - field public static final int actionBarSplitStyle = 16843676; // 0x101039c + field public static final int actionBarSplitStyle = 16843675; // 0x101039b field public static final int actionBarStyle = 16843470; // 0x10102ce field public static final int actionBarTabBarStyle = 16843508; // 0x10102f4 field public static final int actionBarTabStyle = 16843507; // 0x10102f3 @@ -206,9 +206,9 @@ package android { field public static final int actionModeCopyDrawable = 16843538; // 0x1010312 field public static final int actionModeCutDrawable = 16843537; // 0x1010311 field public static final int actionModePasteDrawable = 16843539; // 0x1010313 - field public static final int actionModeSelectAllDrawable = 16843648; // 0x1010380 + field public static final int actionModeSelectAllDrawable = 16843647; // 0x101037f field public static final int actionOverflowButtonStyle = 16843510; // 0x10102f6 - field public static final int actionProviderClass = 16843678; // 0x101039e + field public static final int actionProviderClass = 16843677; // 0x101039d field public static final int actionViewClass = 16843516; // 0x10102fc field public static final int activatedBackgroundIndicator = 16843517; // 0x10102fd field public static final int activityCloseEnterAnimation = 16842938; // 0x10100ba @@ -220,7 +220,7 @@ package android { field public static final int alertDialogIcon = 16843605; // 0x1010355 field public static final int alertDialogStyle = 16842845; // 0x101005d field public static final int alertDialogTheme = 16843529; // 0x1010309 - field public static final int alignmentMode = 16843642; // 0x101037a + field public static final int alignmentMode = 16843641; // 0x1010379 field public static final int allContactsName = 16843468; // 0x10102cc field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 @@ -262,7 +262,7 @@ package android { field public static final int borderlessButtonStyle = 16843563; // 0x101032b field public static final int bottom = 16843184; // 0x10101b0 field public static final int bottomBright = 16842957; // 0x10100cd - field public static final int bottomChevronDrawable = 16843661; // 0x101038d + field public static final int bottomChevronDrawable = 16843660; // 0x101038c field public static final int bottomDark = 16842953; // 0x10100c9 field public static final int bottomLeftRadius = 16843179; // 0x10101ab field public static final int bottomMedium = 16842958; // 0x10100ce @@ -281,7 +281,7 @@ package android { field public static final int cacheColorHint = 16843009; // 0x1010101 field public static final int calendarViewShown = 16843596; // 0x101034c field public static final int calendarViewStyle = 16843613; // 0x101035d - field public static final int canRetrieveWindowContent = 16843655; // 0x1010387 + field public static final int canRetrieveWindowContent = 16843654; // 0x1010386 field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final deprecated int capitalize = 16843113; // 0x1010169 field public static final int centerBright = 16842956; // 0x10100cc @@ -314,9 +314,9 @@ package android { field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab field public static final int colorForeground = 16842800; // 0x1010030 field public static final int colorForegroundInverse = 16843270; // 0x1010206 - field public static final int columnCount = 16843639; // 0x1010377 + field public static final int columnCount = 16843638; // 0x1010376 field public static final int columnDelay = 16843215; // 0x10101cf - field public static final int columnOrderPreserved = 16843640; // 0x1010378 + field public static final int columnOrderPreserved = 16843639; // 0x1010377 field public static final int columnWidth = 16843031; // 0x1010117 field public static final int compatibleWidthLimitDp = 16843621; // 0x1010365 field public static final int completionHint = 16843122; // 0x1010172 @@ -429,7 +429,7 @@ package android { field public static final int fastScrollTextColor = 16843609; // 0x1010359 field public static final int fastScrollThumbDrawable = 16843574; // 0x1010336 field public static final int fastScrollTrackDrawable = 16843577; // 0x1010339 - field public static final int feedbackCount = 16843667; // 0x1010393 + field public static final int feedbackCount = 16843666; // 0x1010392 field public static final int fillAfter = 16843197; // 0x10101bd field public static final int fillBefore = 16843196; // 0x10101bc field public static final int fillEnabled = 16843343; // 0x101024f @@ -462,7 +462,6 @@ package android { field public static final int fromXScale = 16843202; // 0x10101c2 field public static final int fromYDelta = 16843208; // 0x10101c8 field public static final int fromYScale = 16843204; // 0x10101c4 - field public static final int fullBackupAgent = 16843635; // 0x1010373 field public static final int fullBright = 16842954; // 0x10100ca field public static final int fullDark = 16842950; // 0x10100c6 field public static final int functionalTest = 16842787; // 0x1010023 @@ -483,7 +482,7 @@ package android { field public static final int hand_hour = 16843011; // 0x1010103 field public static final int hand_minute = 16843012; // 0x1010104 field public static final int handle = 16843354; // 0x101025a - field public static final int handleDrawable = 16843657; // 0x1010389 + field public static final int handleDrawable = 16843656; // 0x1010388 field public static final int handleProfiling = 16842786; // 0x1010022 field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e field public static final int hardwareAccelerated = 16843475; // 0x10102d3 @@ -492,12 +491,12 @@ package android { field public static final int headerDividersEnabled = 16843310; // 0x101022e field public static final int height = 16843093; // 0x1010155 field public static final int hint = 16843088; // 0x1010150 - field public static final int hitRadius = 16843664; // 0x1010390 + field public static final int hitRadius = 16843663; // 0x101038f field public static final int homeAsUpIndicator = 16843531; // 0x101030b field public static final int homeLayout = 16843549; // 0x101031d field public static final int horizontalDivider = 16843053; // 0x101012d field public static final int horizontalGap = 16843327; // 0x101023f - field public static final int horizontalOffset = 16843669; // 0x1010395 + field public static final int horizontalOffset = 16843668; // 0x1010394 field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353 field public static final int horizontalSpacing = 16843028; // 0x1010114 field public static final int host = 16842792; // 0x1010028 @@ -543,7 +542,7 @@ package android { field public static final int installLocation = 16843447; // 0x10102b7 field public static final int interpolator = 16843073; // 0x1010141 field public static final int isAlwaysSyncable = 16843571; // 0x1010333 - field public static final int isAuxiliary = 16843649; // 0x1010381 + field public static final int isAuxiliary = 16843648; // 0x1010380 field public static final int isDefault = 16843297; // 0x1010221 field public static final int isIndicator = 16843079; // 0x1010147 field public static final int isModifier = 16843334; // 0x1010246 @@ -597,30 +596,30 @@ package android { field public static final int layout_centerInParent = 16843151; // 0x101018f field public static final int layout_centerVertical = 16843153; // 0x1010191 field public static final int layout_column = 16843084; // 0x101014c - field public static final int layout_columnSpan = 16843645; // 0x101037d + field public static final int layout_columnSpan = 16843644; // 0x101037c field public static final int layout_gravity = 16842931; // 0x10100b3 field public static final int layout_height = 16842997; // 0x10100f5 - field public static final int layout_heightSpec = 16843647; // 0x101037f + field public static final int layout_heightSpec = 16843646; // 0x101037e field public static final int layout_margin = 16842998; // 0x10100f6 field public static final int layout_marginBottom = 16843002; // 0x10100fa - field public static final int layout_marginEnd = 16843675; // 0x101039b + field public static final int layout_marginEnd = 16843674; // 0x101039a field public static final int layout_marginLeft = 16842999; // 0x10100f7 field public static final int layout_marginRight = 16843001; // 0x10100f9 - field public static final int layout_marginStart = 16843674; // 0x101039a + field public static final int layout_marginStart = 16843673; // 0x1010399 field public static final int layout_marginTop = 16843000; // 0x10100f8 - field public static final int layout_row = 16843643; // 0x101037b - field public static final int layout_rowSpan = 16843644; // 0x101037c + field public static final int layout_row = 16843642; // 0x101037a + field public static final int layout_rowSpan = 16843643; // 0x101037b field public static final int layout_scale = 16843155; // 0x1010193 field public static final int layout_span = 16843085; // 0x101014d field public static final int layout_toLeftOf = 16843138; // 0x1010182 field public static final int layout_toRightOf = 16843139; // 0x1010183 field public static final int layout_weight = 16843137; // 0x1010181 field public static final int layout_width = 16842996; // 0x10100f4 - field public static final int layout_widthSpec = 16843646; // 0x101037e + field public static final int layout_widthSpec = 16843645; // 0x101037d field public static final int layout_x = 16843135; // 0x101017f field public static final int layout_y = 16843136; // 0x1010180 field public static final int left = 16843181; // 0x10101ad - field public static final int leftChevronDrawable = 16843658; // 0x101038a + field public static final int leftChevronDrawable = 16843657; // 0x1010389 field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 field public static final int lines = 16843092; // 0x1010154 @@ -632,8 +631,8 @@ package android { field public static final int listDividerAlertDialog = 16843525; // 0x1010305 field public static final int listPopupWindowStyle = 16843519; // 0x10102ff field public static final int listPreferredItemHeight = 16842829; // 0x101004d - field public static final int listPreferredItemHeightLarge = 16843670; // 0x1010396 - field public static final int listPreferredItemHeightSmall = 16843671; // 0x1010397 + field public static final int listPreferredItemHeightLarge = 16843669; // 0x1010395 + field public static final int listPreferredItemHeightSmall = 16843670; // 0x1010396 field public static final int listSelector = 16843003; // 0x10100fb field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208 field public static final int listViewStyle = 16842868; // 0x1010074 @@ -679,7 +678,7 @@ package android { field public static final int nextFocusUp = 16842979; // 0x10100e3 field public static final int noHistory = 16843309; // 0x101022d field public static final int normalScreens = 16843397; // 0x1010285 - field public static final int notificationTimeout = 16843653; // 0x1010385 + field public static final int notificationTimeout = 16843652; // 0x1010384 field public static final int numColumns = 16843032; // 0x1010118 field public static final int numStars = 16843076; // 0x1010144 field public static final deprecated int numeric = 16843109; // 0x1010165 @@ -693,17 +692,17 @@ package android { field public static final int orderingFromXml = 16843239; // 0x10101e7 field public static final int orientation = 16842948; // 0x10100c4 field public static final int outAnimation = 16843128; // 0x1010178 - field public static final int outerRadius = 16843663; // 0x101038f + field public static final int outerRadius = 16843662; // 0x101038e field public static final int overScrollFooter = 16843459; // 0x10102c3 field public static final int overScrollHeader = 16843458; // 0x10102c2 field public static final int overScrollMode = 16843457; // 0x10102c1 - field public static final int packageNames = 16843651; // 0x1010383 + field public static final int packageNames = 16843650; // 0x1010382 field public static final int padding = 16842965; // 0x10100d5 field public static final int paddingBottom = 16842969; // 0x10100d9 - field public static final int paddingEnd = 16843673; // 0x1010399 + field public static final int paddingEnd = 16843672; // 0x1010398 field public static final int paddingLeft = 16842966; // 0x10100d6 field public static final int paddingRight = 16842968; // 0x10100d8 - field public static final int paddingStart = 16843672; // 0x1010398 + field public static final int paddingStart = 16843671; // 0x1010397 field public static final int paddingTop = 16842967; // 0x10100d7 field public static final int panelBackground = 16842846; // 0x101005e field public static final int panelColorBackground = 16842849; // 0x1010061 @@ -784,17 +783,17 @@ package android { field public static final int restoreAnyVersion = 16843450; // 0x10102ba field public static final deprecated int restoreNeedsApplication = 16843421; // 0x101029d field public static final int right = 16843183; // 0x10101af - field public static final int rightChevronDrawable = 16843659; // 0x101038b + field public static final int rightChevronDrawable = 16843658; // 0x101038a field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093 field public static final int ringtoneType = 16843257; // 0x10101f9 field public static final int rotation = 16843558; // 0x1010326 field public static final int rotationX = 16843559; // 0x1010327 field public static final int rotationY = 16843560; // 0x1010328 - field public static final int rowCount = 16843637; // 0x1010375 + field public static final int rowCount = 16843636; // 0x1010374 field public static final int rowDelay = 16843216; // 0x10101d0 field public static final int rowEdgeFlags = 16843329; // 0x1010241 field public static final int rowHeight = 16843058; // 0x1010132 - field public static final int rowOrderPreserved = 16843638; // 0x1010376 + field public static final int rowOrderPreserved = 16843637; // 0x1010375 field public static final int saveEnabled = 16842983; // 0x10100e7 field public static final int scaleGravity = 16843262; // 0x10101fe field public static final int scaleHeight = 16843261; // 0x10101fd @@ -860,7 +859,7 @@ package android { field public static final int smallIcon = 16843422; // 0x101029e field public static final int smallScreens = 16843396; // 0x1010284 field public static final int smoothScrollbar = 16843313; // 0x1010231 - field public static final int snapMargin = 16843666; // 0x1010392 + field public static final int snapMargin = 16843665; // 0x1010391 field public static final int soundEffectsEnabled = 16843285; // 0x1010215 field public static final int spacing = 16843027; // 0x1010113 field public static final int spinnerDropDownItemStyle = 16842887; // 0x1010087 @@ -908,7 +907,7 @@ package android { field public static final int subtitleTextStyle = 16843513; // 0x10102f9 field public static final int suggestActionMsg = 16843228; // 0x10101dc field public static final int suggestActionMsgColumn = 16843229; // 0x10101dd - field public static final int suggestionsEnabled = 16843636; // 0x1010374 + field public static final int suggestionsEnabled = 16843635; // 0x1010373 field public static final int summary = 16843241; // 0x10101e9 field public static final int summaryColumn = 16843426; // 0x10102a2 field public static final int summaryOff = 16843248; // 0x10101f0 @@ -925,7 +924,7 @@ package android { field public static final int tag = 16842961; // 0x10100d1 field public static final int targetActivity = 16843266; // 0x1010202 field public static final int targetClass = 16842799; // 0x101002f - field public static final int targetDrawables = 16843656; // 0x1010388 + field public static final int targetDrawables = 16843655; // 0x1010387 field public static final int targetPackage = 16842785; // 0x1010021 field public static final int targetSdkVersion = 16843376; // 0x1010270 field public static final int taskAffinity = 16842770; // 0x1010012 @@ -976,7 +975,7 @@ package android { field public static final int textColorTertiary = 16843282; // 0x1010212 field public static final int textColorTertiaryInverse = 16843283; // 0x1010213 field public static final int textCursorDrawable = 16843618; // 0x1010362 - field public static final int textDirection = 16843677; // 0x101039d + field public static final int textDirection = 16843676; // 0x101039c field public static final int textEditNoPasteWindowLayout = 16843541; // 0x1010315 field public static final int textEditPasteWindowLayout = 16843540; // 0x1010314 field public static final int textEditSideNoPasteWindowLayout = 16843615; // 0x101035f @@ -1016,7 +1015,7 @@ package android { field public static final int toYScale = 16843205; // 0x10101c5 field public static final int top = 16843182; // 0x10101ae field public static final int topBright = 16842955; // 0x10100cb - field public static final int topChevronDrawable = 16843660; // 0x101038c + field public static final int topChevronDrawable = 16843659; // 0x101038b field public static final int topDark = 16842951; // 0x10100c7 field public static final int topLeftRadius = 16843177; // 0x10101a9 field public static final int topOffset = 16843352; // 0x1010258 @@ -1032,7 +1031,7 @@ package android { field public static final int unfocusedMonthDateColor = 16843588; // 0x1010344 field public static final int unselectedAlpha = 16843278; // 0x101020e field public static final int updatePeriodMillis = 16843344; // 0x1010250 - field public static final int useDefaultMargins = 16843641; // 0x1010379 + field public static final int useDefaultMargins = 16843640; // 0x1010378 field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310 field public static final int useLevel = 16843167; // 0x101019f field public static final int userVisible = 16843409; // 0x1010291 @@ -1046,10 +1045,10 @@ package android { field public static final int verticalCorrection = 16843322; // 0x101023a field public static final int verticalDivider = 16843054; // 0x101012e field public static final int verticalGap = 16843328; // 0x1010240 - field public static final int verticalOffset = 16843668; // 0x1010394 + field public static final int verticalOffset = 16843667; // 0x1010393 field public static final int verticalScrollbarPosition = 16843572; // 0x1010334 field public static final int verticalSpacing = 16843029; // 0x1010115 - field public static final int vibrationDuration = 16843665; // 0x1010391 + field public static final int vibrationDuration = 16843664; // 0x1010390 field public static final int visibility = 16842972; // 0x10100dc field public static final int visible = 16843156; // 0x1010194 field public static final int vmSafeMode = 16843448; // 0x10102b8 @@ -1066,7 +1065,7 @@ package android { field public static final int wallpaperIntraOpenExitAnimation = 16843416; // 0x1010298 field public static final int wallpaperOpenEnterAnimation = 16843411; // 0x1010293 field public static final int wallpaperOpenExitAnimation = 16843412; // 0x1010294 - field public static final int waveDrawable = 16843662; // 0x101038e + field public static final int waveDrawable = 16843661; // 0x101038d field public static final int webTextViewStyle = 16843449; // 0x10102b9 field public static final int webViewStyle = 16842885; // 0x1010085 field public static final int weekDayTextAppearance = 16843592; // 0x1010348 @@ -3737,7 +3736,11 @@ package android.app.backup { 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(); + method public void onFullBackup(android.app.backup.FullBackupDataOutput) throws java.io.IOException; 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; + field public static final int TYPE_DIRECTORY = 2; // 0x2 + field public static final int TYPE_FILE = 1; // 0x1 } public class BackupAgentHelper extends android.app.backup.BackupAgent { @@ -3789,6 +3792,9 @@ package android.app.backup { method public void writeNewStateDescription(android.os.ParcelFileDescriptor); } + public class FullBackupDataOutput { + } + public abstract class RestoreObserver { ctor public RestoreObserver(); method public void onUpdate(int, java.lang.String); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1ec7a964e5cd..eee14fb1a5b3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2013,15 +2013,10 @@ public final class ActivityThread { BackupAgent agent = null; String classname = data.appInfo.backupAgentName; - if (data.backupMode == IApplicationThread.BACKUP_MODE_FULL - || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL) { + // full backup operation but no app-supplied agent? use the default implementation + if (classname == null && (data.backupMode == IApplicationThread.BACKUP_MODE_FULL + || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL)) { classname = "android.app.backup.FullBackupAgent"; - if ((data.appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - // system packages can supply their own full-backup agent - if (data.appInfo.fullBackupAgentName != null) { - classname = data.appInfo.fullBackupAgentName; - } - } } try { diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl index 8af78fadfc44..087f83c396dd 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -51,7 +51,6 @@ oneway interface IBackupAgent { void doBackup(in ParcelFileDescriptor oldState, in ParcelFileDescriptor data, in ParcelFileDescriptor newState, - boolean storeApk, int token, IBackupManager callbackBinder); /** @@ -81,6 +80,25 @@ oneway interface IBackupAgent { in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder); /** + * Perform a "full" backup to the given file descriptor. The output file is presumed + * to be a socket or other non-seekable, write-only data sink. When this method is + * called, the app should write all of its files to the output. + * + * @param data Write-only file to receive the backed-up file content stream. + * The data must be formatted correctly for the resulting archive to be + * legitimate, so that will be tightly controlled by the available API. + * + * @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 + * contents. + * + * @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); + + /** * 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 63f325870f91..65c73f9b5eb8 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -20,13 +20,22 @@ import android.app.IBackupAgent; import android.app.backup.IBackupManager; import android.content.Context; import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; +import java.io.File; import java.io.IOException; +import java.util.HashSet; +import java.util.LinkedList; + +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.io.OsConstants; +import libcore.io.StructStat; /** * Provides the central interface between an @@ -87,6 +96,24 @@ public abstract class BackupAgent extends ContextWrapper { private static final String TAG = "BackupAgent"; private static final boolean DEBUG = true; + /** @hide */ + public static final int TYPE_EOF = 0; + + /** + * During a full restore, indicates that the file system object being restored + * is an ordinary file. + */ + public static final int TYPE_FILE = 1; + + /** + * During a full restore, indicates that the file system object being restored + * is a directory. + */ + public static final int TYPE_DIRECTORY = 2; + + /** @hide */ + public static final int TYPE_SYMLINK = 3; + public BackupAgent() { super(null); } @@ -179,18 +206,240 @@ public abstract class BackupAgent extends ContextWrapper { throws IOException; /** + * The default implementation backs up the entirety of the application's "owned" + * file system trees to the output. + */ + public void onFullBackup(FullBackupDataOutput data) throws IOException { + ApplicationInfo appInfo = getApplicationInfo(); + + String rootDir = new File(appInfo.dataDir).getAbsolutePath(); + String filesDir = getFilesDir().getAbsolutePath(); + String databaseDir = getDatabasePath("foo").getParentFile().getAbsolutePath(); + String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getAbsolutePath(); + String cacheDir = getCacheDir().getAbsolutePath(); + String libDir = (appInfo.nativeLibraryDir != null) + ? new File(appInfo.nativeLibraryDir).getAbsolutePath() + : null; + + // Filters, the scan queue, and the set of resulting entities + HashSet<String> filterSet = new HashSet<String>(); + String packageName = getPackageName(); + + // Okay, start with the app's root tree, but exclude all of the canonical subdirs + if (libDir != null) { + filterSet.add(libDir); + } + filterSet.add(cacheDir); + filterSet.add(databaseDir); + filterSet.add(sharedPrefsDir); + filterSet.add(filesDir); + fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet, data); + + // Now do the same for the files dir, db dir, and shared prefs dir + filterSet.add(rootDir); + filterSet.remove(filesDir); + fullBackupFileTree(packageName, FullBackup.DATA_TREE_TOKEN, filesDir, filterSet, data); + + filterSet.add(filesDir); + filterSet.remove(databaseDir); + fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir, filterSet, data); + + filterSet.add(databaseDir); + filterSet.remove(sharedPrefsDir); + fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data); + } + + /** + * Write an entire file as part of a full-backup operation. The file's contents + * will be delivered to the backup destination along with the metadata necessary + * to place it with the proper location and permissions on the device where the + * data is restored. * @hide + * + * @param context The BackupAgent that is calling this method. It is an error to + * call it from something other than a running BackupAgent instance. + * @param file The file to be backed up. The file must exist and be readable by + * the caller. + * @param output The destination to which the backed-up file data will be sent. + */ + public final void fullBackupFile(File file, FullBackupDataOutput output) { + // Look up where all of our various well-defined dir trees live on this device + String mainDir; + String filesDir; + String dbDir; + String spDir; + String cacheDir; + String libDir; + + ApplicationInfo appInfo = getApplicationInfo(); + + mainDir = new File(appInfo.dataDir).getAbsolutePath(); + filesDir = getFilesDir().getAbsolutePath(); + dbDir = getDatabasePath("foo").getParentFile().getAbsolutePath(); + spDir = getSharedPrefsFile("foo").getParentFile().getAbsolutePath(); + cacheDir = getCacheDir().getAbsolutePath(); + libDir = (appInfo.nativeLibraryDir == null) ? null + : new File(appInfo.nativeLibraryDir).getAbsolutePath(); + + // Now figure out which well-defined tree the file is placed in, working from + // most to least specific. We also specifically exclude the lib and cache dirs. + String filePath = file.getAbsolutePath(); + + if (filePath.startsWith(cacheDir) || filePath.startsWith(libDir)) { + Log.w(TAG, "lib and cache files are not backed up"); + return; + } + + final String domain; + String rootpath = null; + if (filePath.startsWith(dbDir)) { + domain = FullBackup.DATABASE_TREE_TOKEN; + rootpath = dbDir; + } else if (filePath.startsWith(spDir)) { + domain = FullBackup.SHAREDPREFS_TREE_TOKEN; + rootpath = spDir; + } else if (filePath.startsWith(filesDir)) { + domain = FullBackup.DATA_TREE_TOKEN; + rootpath = filesDir; + } else if (filePath.startsWith(mainDir)) { + domain = FullBackup.ROOT_TREE_TOKEN; + rootpath = mainDir; + } else { + Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping"); + return; + } + + // And now that we know where it lives, semantically, back it up appropriately + Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain + + " rootpath=" + rootpath); + FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, + output.getData()); + } + + /** + * Scan the dir tree (if it actually exists) and process each entry we find. If the + * 'excludes' parameter is non-null, it is consulted each time a new file system entity + * is visited to see whether that entity (and its subtree, if appropriate) should be + * omitted from the backup process. + * + * @hide + */ + protected final void fullBackupFileTree(String packageName, String domain, String rootPath, + HashSet<String> excludes, FullBackupDataOutput output) { + File rootFile = new File(rootPath); + if (rootFile.exists()) { + LinkedList<File> scanQueue = new LinkedList<File>(); + scanQueue.add(rootFile); + + while (scanQueue.size() > 0) { + File file = scanQueue.remove(0); + String filePath = file.getAbsolutePath(); + + // prune this subtree? + if (excludes != null && excludes.contains(filePath)) { + continue; + } + + // If it's a directory, enqueue its contents for scanning. + try { + StructStat stat = Libcore.os.lstat(filePath); + if (OsConstants.S_ISLNK(stat.st_mode)) { + if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file); + continue; + } else if (OsConstants.S_ISDIR(stat.st_mode)) { + File[] contents = file.listFiles(); + if (contents != null) { + for (File entry : contents) { + scanQueue.add(0, entry); + } + } + } + } catch (ErrnoException e) { + if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e); + continue; + } + + // Finally, back this file up before proceeding + FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, + output.getData()); + } + } + } + + /** + * Handle the data delivered via the given file descriptor during a full restore + * operation. The agent is given the path to the file's original location as well + * as its size and metadata. + * <p> + * The file descriptor can only be read for {@code size} bytes; attempting to read + * more data has undefined behavior. + * <p> + * The default implementation creates the destination file/directory and populates it + * with the data from the file descriptor, then sets the file's access mode and + * modification time to match the restore arguments. + * + * @param data A read-only file descriptor from which the agent can read {@code size} + * bytes of file data. + * @param size The number of bytes of file content to be restored to the given + * destination. If the file system object being restored is a directory, {@code size} + * will be zero. + * @param destination The File on disk to be restored with the given data. + * @param type The kind of file system object being restored. This will be either + * {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}. + * @param mode The access mode to be assigned to the destination after its data is + * written. This is in the standard format used by {@code chmod()}. + * @param mtime The modification time of the file when it was backed up, suitable to + * be assigned to the file after its data is written. + * @throws IOException */ public void onRestoreFile(ParcelFileDescriptor data, long size, - int type, String domain, String path, long mode, long mtime) + File destination, int type, long mode, long mtime) throws IOException { - // empty stub implementation + FullBackup.restoreFile(data, size, type, mode, mtime, destination); } /** - * Package-private, used only for dispatching an extra step during full backup + * Only specialized platform agents should overload this entry point to support + * restores to crazy non-app locations. + * @hide */ - void onSaveApk(BackupDataOutput data) { + protected void onRestoreFile(ParcelFileDescriptor data, long size, + int type, String domain, String path, long mode, long mtime) + throws IOException { + String basePath = null; + + if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type + + " domain=" + domain + " relpath=" + path + " mode=" + mode + + " mtime=" + mtime); + + // Parse out the semantic domains into the correct physical location + if (domain.equals(FullBackup.DATA_TREE_TOKEN)) { + basePath = getFilesDir().getAbsolutePath(); + } else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) { + basePath = getDatabasePath("foo").getParentFile().getAbsolutePath(); + } else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) { + basePath = new File(getApplicationInfo().dataDir).getAbsolutePath(); + } else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) { + basePath = getSharedPrefsFile("foo").getParentFile().getAbsolutePath(); + } else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) { + basePath = getCacheDir().getAbsolutePath(); + } else { + // Not a supported location + Log.i(TAG, "Data restored from non-app domain " + domain + ", ignoring"); + } + + // Now that we've figured out where the data goes, send it on its way + if (basePath != null) { + File outFile = new File(basePath, path); + if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outFile.getPath()); + onRestoreFile(data, size, outFile, type, mode, mtime); + } else { + // Not a supported output location? We need to consume the data + // anyway, so just use the default "copy the data out" implementation + // with a null destination. + if (DEBUG) Log.i(TAG, "[ skipping data from unsupported domain " + domain + "]"); + FullBackup.restoreFile(data, size, type, mode, mtime, null); + } } // ----- Core implementation ----- @@ -215,7 +464,6 @@ public abstract class BackupAgent extends ContextWrapper { public void doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, - boolean storeApk, int token, IBackupManager callbackBinder) throws RemoteException { // Ensure that we're running with the app's normal permission level long ident = Binder.clearCallingIdentity(); @@ -223,10 +471,6 @@ public abstract class BackupAgent extends ContextWrapper { if (DEBUG) Log.v(TAG, "doBackup() invoked"); BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor()); - if (storeApk) { - onSaveApk(output); - } - try { BackupAgent.this.onBackup(oldState, output, newState); } catch (IOException ex) { @@ -273,6 +517,33 @@ public abstract class BackupAgent extends ContextWrapper { } @Override + public void doFullBackup(ParcelFileDescriptor data, + int token, IBackupManager callbackBinder) { + // Ensure that we're running with the app's normal permission level + long ident = Binder.clearCallingIdentity(); + + if (DEBUG) Log.v(TAG, "doFullBackup() invoked"); + BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor()); + + try { + BackupAgent.this.onFullBackup(new FullBackupDataOutput(data)); + } catch (IOException ex) { + Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); + throw new RuntimeException(ex); + } catch (RuntimeException ex) { + Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); + throw ex; + } finally { + Binder.restoreCallingIdentity(ident); + try { + callbackBinder.opComplete(token); + } catch (RemoteException e) { + // we'll time out anyway, so we're safe + } + } + } + + @Override public void doRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String path, long mode, long mtime, int token, IBackupManager callbackBinder) throws RemoteException { diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 3b70e19ad244..d7f1c9f0fab4 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -16,6 +16,9 @@ package android.app.backup; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.ParcelFileDescriptor; import android.util.Log; @@ -29,7 +32,8 @@ import libcore.io.Libcore; /** * Global constant definitions et cetera related to the full-backup-to-fd - * binary format. + * binary format. Nothing in this namespace is part of any API; it's all + * hidden details of the current implementation gathered into one location. * * @hide */ @@ -52,18 +56,41 @@ public class FullBackup { public static final String FULL_RESTORE_INTENT_ACTION = "fullrest"; public static final String CONF_TOKEN_INTENT_EXTRA = "conftoken"; - public static final int TYPE_EOF = 0; - public static final int TYPE_FILE = 1; - public static final int TYPE_DIRECTORY = 2; - public static final int TYPE_SYMLINK = 3; - + /** + * @hide + */ static public native int backupToTar(String packageName, String domain, String linkdomain, String rootpath, String path, BackupDataOutput output); - static public void restoreToFile(ParcelFileDescriptor data, - long size, int type, long mode, long mtime, File outFile, - boolean doChmod) throws IOException { - if (type == FullBackup.TYPE_DIRECTORY) { + /** + * Copy data from a socket to the given File location on permanent storage. The + * modification time and access mode of the resulting file will be set if desired. + * If the {@code type} parameter indicates that the result should be a directory, + * the socket parameter may be {@code null}; even if it is valid, no data will be + * read from it in this case. + * <p> + * If the {@code mode} argument is negative, then the resulting output file will not + * have its access mode or last modification time reset as part of this operation. + * + * @param data Socket supplying the data to be copied to the output file. If the + * output is a directory, this may be {@code null}. + * @param size Number of bytes of data to copy from the socket to the file. At least + * this much data must be available through the {@code data} parameter. + * @param type Must be either {@link BackupAgent#TYPE_FILE} for ordinary file data + * or {@link BackupAgent#TYPE_DIRECTORY} for a directory. + * @param mode Unix-style file mode (as used by the chmod(2) syscall) to be set on + * the output file or directory. If this parameter is negative then neither + * the mode nor the mtime parameters will be used. + * @param mtime A timestamp in the standard Unix epoch that will be imposed as the + * last modification time of the output file. if the {@code mode} parameter is + * negative then this parameter will be ignored. + * @param outFile Location within the filesystem to place the data. This must point + * to a location that is writeable by the caller, prefereably using an absolute path. + * @throws IOException + */ + static public void restoreFile(ParcelFileDescriptor data, + long size, int type, long mode, long mtime, File outFile) throws IOException { + if (type == BackupAgent.TYPE_DIRECTORY) { // Canonically a directory has no associated content, so we don't need to read // anything from the pipe in this case. Just create the directory here and // drop down to the final metadata adjustment. @@ -117,7 +144,7 @@ public class FullBackup { } // Now twiddle the state to match the backup, assuming all went well - if (doChmod && outFile != null) { + if (mode >= 0 && outFile != null) { try { Libcore.os.chmod(outFile.getPath(), (int)mode); } catch (ErrnoException e) { diff --git a/core/java/android/app/backup/FullBackupAgent.java b/core/java/android/app/backup/FullBackupAgent.java index df1c3639bc58..faea76aee148 100644 --- a/core/java/android/app/backup/FullBackupAgent.java +++ b/core/java/android/app/backup/FullBackupAgent.java @@ -16,210 +16,26 @@ package android.app.backup; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.util.Log; - -import libcore.io.Libcore; -import libcore.io.ErrnoException; -import libcore.io.OsConstants; -import libcore.io.StructStat; - -import java.io.File; import java.io.IOException; -import java.util.HashSet; -import java.util.LinkedList; /** - * Backs up an application's entire /data/data/<package>/... file system. This - * class is used by the desktop full backup mechanism and is not intended for direct - * use by applications. + * Simple concrete class that merely provides the default BackupAgent full backup/restore + * implementations for applications that do not supply their own. * * {@hide} */ public class FullBackupAgent extends BackupAgent { - // !!! TODO: turn off debugging - private static final String TAG = "FullBackupAgent"; - private static final boolean DEBUG = true; - - PackageManager mPm; - - private String mMainDir; - private String mFilesDir; - private String mDatabaseDir; - private String mSharedPrefsDir; - private String mCacheDir; - private String mLibDir; - - private File NULL_FILE; - - @Override - public void onCreate() { - NULL_FILE = new File("/dev/null"); - - mPm = getPackageManager(); - try { - ApplicationInfo appInfo = mPm.getApplicationInfo(getPackageName(), 0); - mMainDir = new File(appInfo.dataDir).getAbsolutePath(); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Unable to find package " + getPackageName()); - throw new RuntimeException(e); - } - - mFilesDir = getFilesDir().getAbsolutePath(); - mDatabaseDir = getDatabasePath("foo").getParentFile().getAbsolutePath(); - mSharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getAbsolutePath(); - mCacheDir = getCacheDir().getAbsolutePath(); - - ApplicationInfo app = getApplicationInfo(); - mLibDir = (app.nativeLibraryDir != null) - ? new File(app.nativeLibraryDir).getAbsolutePath() - : null; - } - @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { - // Filters, the scan queue, and the set of resulting entities - HashSet<String> filterSet = new HashSet<String>(); - String packageName = getPackageName(); - - // Okay, start with the app's root tree, but exclude all of the canonical subdirs - if (mLibDir != null) { - filterSet.add(mLibDir); - } - filterSet.add(mCacheDir); - filterSet.add(mDatabaseDir); - filterSet.add(mSharedPrefsDir); - filterSet.add(mFilesDir); - processTree(packageName, FullBackup.ROOT_TREE_TOKEN, mMainDir, filterSet, data); - - // Now do the same for the files dir, db dir, and shared prefs dir - filterSet.add(mMainDir); - filterSet.remove(mFilesDir); - processTree(packageName, FullBackup.DATA_TREE_TOKEN, mFilesDir, filterSet, data); - - filterSet.add(mFilesDir); - filterSet.remove(mDatabaseDir); - processTree(packageName, FullBackup.DATABASE_TREE_TOKEN, mDatabaseDir, filterSet, data); - - filterSet.add(mDatabaseDir); - filterSet.remove(mSharedPrefsDir); - processTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, mSharedPrefsDir, filterSet, data); + // Doesn't do incremental backup/restore } - // Scan the dir tree (if it actually exists) and process each entry we find. If the - // 'excludes' parameter is non-null, it is consulted each time a new file system entity - // is visited to see whether that entity (and its subtree, if appropriate) should be - // omitted from the backup process. - protected void processTree(String packageName, String domain, String rootPath, - HashSet<String> excludes, BackupDataOutput data) { - File rootFile = new File(rootPath); - if (rootFile.exists()) { - LinkedList<File> scanQueue = new LinkedList<File>(); - scanQueue.add(rootFile); - - while (scanQueue.size() > 0) { - File file = scanQueue.remove(0); - String filePath = file.getAbsolutePath(); - - // prune this subtree? - if (excludes != null && excludes.contains(filePath)) { - continue; - } - - // If it's a directory, enqueue its contents for scanning. - try { - StructStat stat = Libcore.os.lstat(filePath); - if (OsConstants.S_ISLNK(stat.st_mode)) { - if (DEBUG) Log.i(TAG, "Symlink (skipping)!: " + file); - continue; - } else if (OsConstants.S_ISDIR(stat.st_mode)) { - File[] contents = file.listFiles(); - if (contents != null) { - for (File entry : contents) { - scanQueue.add(0, entry); - } - } - } - } catch (ErrnoException e) { - if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e); - continue; - } - - // Finally, back this file up before proceeding - FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, data); - } - } - } - - @Override - void onSaveApk(BackupDataOutput data) { - ApplicationInfo app = getApplicationInfo(); - if (DEBUG) Log.i(TAG, "APK flags: system=" + ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) - + " updated=" + ((app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) - + " locked=" + ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) ); - if (DEBUG) Log.i(TAG, "codepath: " + getPackageCodePath()); - - // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here - final String pkgName = getPackageName(); - final String apkDir = new File(getPackageCodePath()).getParent(); - FullBackup.backupToTar(pkgName, FullBackup.APK_TREE_TOKEN, null, - apkDir, getPackageCodePath(), data); - - // Save associated .obb content if it exists and we did save the apk - // check for .obb and save those too - final File obbDir = Environment.getExternalStorageAppObbDirectory(pkgName); - if (obbDir != null) { - if (DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath()); - File[] obbFiles = obbDir.listFiles(); - if (obbFiles != null) { - final String obbDirName = obbDir.getAbsolutePath(); - for (File obb : obbFiles) { - FullBackup.backupToTar(pkgName, FullBackup.OBB_TREE_TOKEN, null, - obbDirName, obb.getAbsolutePath(), data); - } - } - } - } - - /** - * Dummy -- We're never used for restore of an incremental dataset - */ @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { - } - - /** - * Restore the described file from the given pipe. - */ - @Override - public void onRestoreFile(ParcelFileDescriptor data, long size, - int type, String domain, String relpath, long mode, long mtime) - throws IOException { - String basePath = null; - File outFile = null; - - if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type - + " domain=" + domain + " relpath=" + relpath + " mode=" + mode - + " mtime=" + mtime); - - // Parse out the semantic domains into the correct physical location - if (domain.equals(FullBackup.DATA_TREE_TOKEN)) basePath = mFilesDir; - else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) basePath = mDatabaseDir; - else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) basePath = mMainDir; - else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) basePath = mSharedPrefsDir; - - // Not a supported output location? We need to consume the data - // anyway, so send it to /dev/null - outFile = (basePath != null) ? new File(basePath, relpath) : null; - if (DEBUG) Log.i(TAG, "[" + domain + " : " + relpath + "] mapped to " + outFile.getPath()); - - // Now that we've figured out where the data goes, send it on its way - FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, true); + // Doesn't do incremental backup/restore } } diff --git a/core/java/android/app/backup/FullBackupDataOutput.java b/core/java/android/app/backup/FullBackupDataOutput.java new file mode 100644 index 000000000000..99dab1f76018 --- /dev/null +++ b/core/java/android/app/backup/FullBackupDataOutput.java @@ -0,0 +1,21 @@ +package android.app.backup; + +import android.os.ParcelFileDescriptor; + +/** + * Provides the interface through which a {@link BackupAgent} writes entire files + * to a full backup data set, via its {@link BackupAgent#onFullBackup(FullBackupDataOutput)} + * method. + */ +public class FullBackupDataOutput { + // Currently a name-scoping shim around BackupDataOutput + private BackupDataOutput mData; + + /** @hide */ + public FullBackupDataOutput(ParcelFileDescriptor fd) { + mData = new BackupDataOutput(fd.getFileDescriptor()); + } + + /** @hide */ + public BackupDataOutput getData() { return mData; } +} diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 454cb3195316..ddb6ef0d2f53 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -91,15 +91,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public String backupAgentName; /** - * Class implementing the package's *full* backup functionality. This - * is not usable except by system-installed packages. It can be the same - * as the backupAgent. - * - * @hide - */ - public String fullBackupAgentName; - - /** * Value for {@link #flags}: if set, this application is installed in the * device's system image. */ @@ -555,7 +546,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(installLocation); dest.writeString(manageSpaceActivityName); dest.writeString(backupAgentName); - dest.writeString(fullBackupAgentName); dest.writeInt(descriptionRes); } @@ -593,7 +583,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { installLocation = source.readInt(); manageSpaceActivityName = source.readString(); backupAgentName = source.readString(); - fullBackupAgentName = source.readString(); descriptionRes = source.readInt(); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 208869b6c2fd..53d6bb17a551 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1517,17 +1517,6 @@ public class PackageParser { } } - // fullBackupAgent is explicitly handled even if allowBackup is false - name = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestApplication_fullBackupAgent, 0); - if (name != null) { - ai.fullBackupAgentName = buildClassName(pkgName, name, outError); - if (false) { - Log.v(TAG, "android:fullBackupAgent=" + ai.fullBackupAgentName - + " from " + pkgName + "+" + name); - } - } - TypedValue v = sa.peekValue( com.android.internal.R.styleable.AndroidManifestApplication_label); if (v != null && (ai.labelRes=v.resourceId) == 0) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1a320608efae..49eaf1912617 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1418,7 +1418,6 @@ android:label="@string/android_system_label" android:allowClearUserData="false" android:backupAgent="com.android.server.SystemBackupAgent" - android:fullBackupAgent="com.android.server.SystemBackupAgent" android:killAfterRestore="false" android:icon="@drawable/ic_launcher_android"> <activity android:name="com.android.internal.app.ChooserActivity" diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 03b332ee75b6..dd16bd072df6 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -696,13 +696,6 @@ <p>The default value of this attribute is <code>false</code>. --> <attr name="restoreAnyVersion" format="boolean" /> - <!-- The agent to use for a *full* backup of the package. Only system applications - can use this to override the ordinary FullBackupAgent with a custom implementation. - It's needed strictly for packages with strongly device-specific data, such as the - Settings provider. - --> - <attr name="fullBackupAgent" format="string" /> - <!-- The default install location defined by an application. --> <attr name="installLocation"> <!-- Let the system decide ideal install location --> @@ -800,7 +793,6 @@ <attr name="killAfterRestore" /> <attr name="restoreNeedsApplication" /> <attr name="restoreAnyVersion" /> - <attr name="fullBackupAgent" /> <attr name="neverEncrypt" /> <!-- Request that your application's processes be created with a large Dalvik heap. This applies to <em>all</em> processes diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 54e484ee95de..20f5e7cf2eed 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1725,7 +1725,6 @@ <public type="attr" name="layoutDirection" /> - <public type="attr" name="fullBackupAgent" /> <public type="attr" name="suggestionsEnabled" /> <public type="attr" name="rowCount" /> @@ -1737,7 +1736,6 @@ <public type="attr" name="layout_row" /> <public type="attr" name="layout_rowSpan" /> - <public type="attr" name="layout_columnSpan" /> <public type="attr" name="layout_widthSpec" /> diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml index e5f52e24e807..dd0d064ceeef 100644 --- a/packages/SettingsProvider/AndroidManifest.xml +++ b/packages/SettingsProvider/AndroidManifest.xml @@ -6,7 +6,6 @@ android:label="@string/app_label" android:process="system" android:backupAgent="SettingsBackupAgent" - android:fullBackupAgent="SettingsBackupAgent" android:killAfterRestore="false" android:icon="@drawable/ic_launcher_settings"> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 946960122565..3a7a6e153803 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -36,7 +36,7 @@ import java.util.zip.CRC32; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.BackupAgentHelper; -import android.app.backup.FullBackup; +import android.app.backup.FullBackupDataOutput; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -132,58 +132,22 @@ public class SettingsBackupAgent extends BackupAgentHelper { byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT); byte[] wifiConfigData = getFileData(mWifiConfigFile); - // This same agent class is used for both full and incremental backups. A full - // backup is flagged by a 'null' oldState argument. In the case of a full backup, - // the output is structured as tarfile contents. - if (oldState != null) { - long[] stateChecksums = readOldChecksums(oldState); - - stateChecksums[STATE_SYSTEM] = - writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data); - stateChecksums[STATE_SECURE] = - writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data); - stateChecksums[STATE_LOCALE] = - writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data); - stateChecksums[STATE_WIFI_SUPPLICANT] = - writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT, - wifiSupplicantData, data); - stateChecksums[STATE_WIFI_CONFIG] = - writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData, - data); - - writeNewChecksums(stateChecksums, newState); - } else { - // Write the data to the staging file, then emit that as our tarfile - // representation of the backed-up settings. - String root = getFilesDir().getAbsolutePath(); - File stage = new File(root, STAGE_FILE); - try { - FileOutputStream filestream = new FileOutputStream(stage); - BufferedOutputStream bufstream = new BufferedOutputStream(filestream); - DataOutputStream out = new DataOutputStream(bufstream); - - out.writeInt(FULL_BACKUP_VERSION); - - out.writeInt(systemSettingsData.length); - out.write(systemSettingsData); - out.writeInt(secureSettingsData.length); - out.write(secureSettingsData); - out.writeInt(locale.length); - out.write(locale); - out.writeInt(wifiSupplicantData.length); - out.write(wifiSupplicantData); - out.writeInt(wifiConfigData.length); - out.write(wifiConfigData); - - out.flush(); // also flushes downstream - - // now we're set to emit the tar stream - FullBackup.backupToTar(getPackageName(), FullBackup.DATA_TREE_TOKEN, null, - root, stage.getAbsolutePath(), data); - } finally { - stage.delete(); - } - } + long[] stateChecksums = readOldChecksums(oldState); + + stateChecksums[STATE_SYSTEM] = + writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data); + stateChecksums[STATE_SECURE] = + writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data); + stateChecksums[STATE_LOCALE] = + writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data); + stateChecksums[STATE_WIFI_SUPPLICANT] = + writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT, + wifiSupplicantData, data); + stateChecksums[STATE_WIFI_CONFIG] = + writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData, + data); + + writeNewChecksums(stateChecksums, newState); } @Override @@ -221,6 +185,45 @@ public class SettingsBackupAgent extends BackupAgentHelper { } @Override + public void onFullBackup(FullBackupDataOutput data) throws IOException { + byte[] systemSettingsData = getSystemSettings(); + byte[] secureSettingsData = getSecureSettings(); + byte[] locale = mSettingsHelper.getLocaleData(); + byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT); + byte[] wifiConfigData = getFileData(mWifiConfigFile); + + // Write the data to the staging file, then emit that as our tarfile + // representation of the backed-up settings. + String root = getFilesDir().getAbsolutePath(); + File stage = new File(root, STAGE_FILE); + try { + FileOutputStream filestream = new FileOutputStream(stage); + BufferedOutputStream bufstream = new BufferedOutputStream(filestream); + DataOutputStream out = new DataOutputStream(bufstream); + + out.writeInt(FULL_BACKUP_VERSION); + + out.writeInt(systemSettingsData.length); + out.write(systemSettingsData); + out.writeInt(secureSettingsData.length); + out.write(secureSettingsData); + out.writeInt(locale.length); + out.write(locale); + out.writeInt(wifiSupplicantData.length); + out.write(wifiSupplicantData); + out.writeInt(wifiConfigData.length); + out.write(wifiConfigData); + + out.flush(); // also flushes downstream + + // now we're set to emit the tar stream + fullBackupFile(stage, data); + } finally { + stage.delete(); + } + } + + @Override public void onRestoreFile(ParcelFileDescriptor data, long size, int type, String domain, String relpath, long mode, long mtime) throws IOException { diff --git a/packages/SharedStorageBackup/AndroidManifest.xml b/packages/SharedStorageBackup/AndroidManifest.xml index 258059c54fce..39c36f1b2ec6 100644 --- a/packages/SharedStorageBackup/AndroidManifest.xml +++ b/packages/SharedStorageBackup/AndroidManifest.xml @@ -23,7 +23,7 @@ <application android:allowClearUserData="false" android:permission="android.permission.CONFIRM_FULL_BACKUP" - android:fullBackupAgent=".SharedStorageAgent" + android:backupAgent=".SharedStorageAgent" android:allowBackup="false" > </application> </manifest> diff --git a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java index b02ca2e761ec..6c677b878501 100644 --- a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java +++ b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java @@ -1,9 +1,10 @@ package com.android.sharedstoragebackup; -import android.app.backup.FullBackup; import android.app.backup.FullBackupAgent; +import android.app.backup.FullBackup; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.FullBackupDataOutput; import android.content.Context; import android.os.Environment; import android.os.ParcelFileDescriptor; @@ -30,9 +31,11 @@ public class SharedStorageAgent extends FullBackupAgent { } } + /** + * Full backup of the shared-storage filesystem + */ @Override - public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, - ParcelFileDescriptor newState) throws IOException { + public void onFullBackup(FullBackupDataOutput output) throws IOException { // If there are shared-storage volumes available, run the inherited directory- // hierarchy backup process on them. By convention in the Storage Manager, the // "primary" shared storage volume is first in the list. @@ -43,20 +46,12 @@ public class SharedStorageAgent extends FullBackupAgent { // shared/N/path/to/file // The restore will then extract to the given volume String domain = FullBackup.SHARED_PREFIX + i; - processTree(null, domain, v.getPath(), null, data); + fullBackupFileTree(null, domain, v.getPath(), null, output); } } } /** - * Incremental onRestore() implementation is not used. - */ - @Override - public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) - throws IOException { - } - - /** * Full restore of one file to shared storage */ @Override @@ -88,6 +83,6 @@ public class SharedStorageAgent extends FullBackupAgent { Slog.e(TAG, "Skipping data with malformed path " + relpath); } - FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, false); + FullBackup.restoreFile(data, size, type, -1, mtime, outFile); } } diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 3aa1239c8df9..6afccec4df04 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -23,6 +23,7 @@ import android.app.IActivityManager; import android.app.IApplicationThread; import android.app.IBackupAgent; import android.app.PendingIntent; +import android.app.backup.BackupAgent; import android.app.backup.BackupDataOutput; import android.app.backup.FullBackup; import android.app.backup.RestoreSet; @@ -64,6 +65,7 @@ import android.os.SystemClock; import android.os.WorkSource; import android.provider.Settings; import android.util.EventLog; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -1587,8 +1589,7 @@ class BackupManagerService extends IBackupManager.Stub { // Initiate the target's backup pass prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL); - agent.doBackup(savedState, backupData, newState, false, - token, mBackupManagerBinder); + agent.doBackup(savedState, backupData, newState, token, mBackupManagerBinder); boolean success = waitUntilOperationComplete(token); if (!success) { @@ -1764,30 +1765,31 @@ class BackupManagerService extends IBackupManager.Stub { if (agent != null) { try { ApplicationInfo app = pkg.applicationInfo; - boolean sendApk = mIncludeApks + final boolean sendApk = mIncludeApks && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0) && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); sendOnBackupPackage(pkg.packageName); - { - BackupDataOutput output = new BackupDataOutput( - mOutputFile.getFileDescriptor()); + BackupDataOutput output = new BackupDataOutput( + mOutputFile.getFileDescriptor()); - if (DEBUG) Slog.d(TAG, "Writing manifest for " + pkg.packageName); - writeAppManifest(pkg, mManifestFile, sendApk); - FullBackup.backupToTar(pkg.packageName, null, null, - mFilesDir.getAbsolutePath(), - mManifestFile.getAbsolutePath(), - output); + if (DEBUG) Slog.d(TAG, "Writing manifest for " + pkg.packageName); + writeAppManifest(pkg, mManifestFile, sendApk); + FullBackup.backupToTar(pkg.packageName, null, null, + mFilesDir.getAbsolutePath(), + mManifestFile.getAbsolutePath(), + output); + + if (sendApk) { + writeApkToBackup(pkg, output); } - if (DEBUG) Slog.d(TAG, "Calling doBackup()"); + if (DEBUG) Slog.d(TAG, "Calling doFullBackup()"); final int token = generateToken(); prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL); - agent.doBackup(null, mOutputFile, null, sendApk, - token, mBackupManagerBinder); + agent.doFullBackup(mOutputFile, token, mBackupManagerBinder); if (!waitUntilOperationComplete(token)) { Slog.e(TAG, "Full backup failed on package " + pkg.packageName); } else { @@ -1802,6 +1804,29 @@ class BackupManagerService extends IBackupManager.Stub { tearDown(pkg); } + private void writeApkToBackup(PackageInfo pkg, BackupDataOutput output) { + // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here + final String appSourceDir = pkg.applicationInfo.sourceDir; + final String apkDir = new File(appSourceDir).getParent(); + FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null, + apkDir, appSourceDir, output); + + // Save associated .obb content if it exists and we did save the apk + // check for .obb and save those too + final File obbDir = Environment.getExternalStorageAppObbDirectory(pkg.packageName); + if (obbDir != null) { + if (DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath()); + File[] obbFiles = obbDir.listFiles(); + if (obbFiles != null) { + final String obbDirName = obbDir.getAbsolutePath(); + for (File obb : obbFiles) { + FullBackup.backupToTar(pkg.packageName, FullBackup.OBB_TREE_TOKEN, null, + obbDirName, obb.getAbsolutePath(), output); + } + } + } + } + private void backupSharedStorage() throws RemoteException { PackageInfo pkg = null; try { @@ -1813,7 +1838,7 @@ class BackupManagerService extends IBackupManager.Stub { final int token = generateToken(); prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL); - agent.doBackup(null, mOutputFile, null, false, token, mBackupManagerBinder); + agent.doFullBackup(mOutputFile, token, mBackupManagerBinder); if (!waitUntilOperationComplete(token)) { Slog.e(TAG, "Full backup failed on shared storage"); } else { @@ -1933,7 +1958,7 @@ class BackupManagerService extends IBackupManager.Stub { static class FileMetadata { String packageName; // name of the owning app String installerPackageName; // name of the market-type app that installed the owner - int type; // e.g. FullBackup.TYPE_DIRECTORY + int type; // e.g. BackupAgent.TYPE_DIRECTORY String domain; // e.g. FullBackup.DATABASE_TREE_TOKEN String path; // subpath within the semantic domain long mode; // e.g. 0666 (actually int) @@ -2182,15 +2207,15 @@ class BackupManagerService extends IBackupManager.Stub { // If we haven't sent any data to this app yet, we probably // need to clear it first. Check that. if (!mClearedPackages.contains(pkg)) { - // apps with their own full backup agents are + // apps with their own backup agents are // responsible for coherently managing a full // restore. - if (mTargetApp.fullBackupAgentName == null) { + if (mTargetApp.backupAgentName == null) { if (DEBUG) Slog.d(TAG, "Clearing app data preparatory to full restore"); clearApplicationDataSynchronous(pkg); } else { - if (DEBUG) Slog.d(TAG, "full backup agent (" - + mTargetApp.fullBackupAgentName + ") => no clear"); + if (DEBUG) Slog.d(TAG, "backup agent (" + + mTargetApp.backupAgentName + ") => no clear"); } mClearedPackages.add(pkg); } else { @@ -2686,7 +2711,7 @@ class BackupManagerService extends IBackupManager.Stub { StringBuilder b = new StringBuilder(128); // mode string - b.append((info.type == FullBackup.TYPE_DIRECTORY) ? 'd' : '-'); + b.append((info.type == BackupAgent.TYPE_DIRECTORY) ? 'd' : '-'); b.append(((info.mode & 0400) != 0) ? 'r' : '-'); b.append(((info.mode & 0200) != 0) ? 'w' : '-'); b.append(((info.mode & 0100) != 0) ? 'x' : '-'); @@ -2746,9 +2771,9 @@ class BackupManagerService extends IBackupManager.Stub { } switch (typeChar) { - case '0': info.type = FullBackup.TYPE_FILE; break; + case '0': info.type = BackupAgent.TYPE_FILE; break; case '5': { - info.type = FullBackup.TYPE_DIRECTORY; + info.type = BackupAgent.TYPE_DIRECTORY; if (info.size != 0) { Slog.w(TAG, "Directory entry with nonzero size in header"); info.size = 0; diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java index 08c66996128e..950f3b68f6d5 100644 --- a/services/java/com/android/server/SystemBackupAgent.java +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -138,7 +138,7 @@ public class SystemBackupAgent extends BackupAgentHelper { if (outFile == null) { Slog.w(TAG, "Skipping unrecognized system file: [ " + domain + " : " + path + " ]"); } - FullBackup.restoreToFile(data, size, type, mode, mtime, outFile, true); + FullBackup.restoreFile(data, size, type, mode, mtime, outFile); if (restoredWallpaper) { WallpaperManagerService wallpaper = |