summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Williams <mjwilliams@google.com>2015-04-17 18:22:51 -0700
committerMatthew Williams <mjwilliams@google.com>2015-05-03 16:19:27 -0700
commit303650c9cdb7cec88e7ec20747b161d9fff10719 (patch)
tree310f95b0e6ff91830cebfdc015ee158015fc5d27
parentca030f8ed5fd52f2821d159b9c16d0c514dc0688 (diff)
downloadframeworks_base-303650c9cdb7cec88e7ec20747b161d9fff10719.zip
frameworks_base-303650c9cdb7cec88e7ec20747b161d9fff10719.tar.gz
frameworks_base-303650c9cdb7cec88e7ec20747b161d9fff10719.tar.bz2
Add full backup criteria to android manifest
BUG: 20010079 Api change: ApplicationInfo now has a fullBackupContent int where -1 is (off) 0 is (on) and >0 indicates an xml resource that should be parsed in order for a developer to indicate exactly which files they want to include/exclude from the backup set. dd: https://docs.google.com/document/d/1dnNctwhWOI-_qtZ7I3iNRtrbShmERj2GFTzwV4xXtOk/edit#heading=h.wcfw1q2pbmae Change-Id: I90273dc0aef5e9a3230c6b074a45e8f5409ed5ce
-rw-r--r--api/current.txt34
-rw-r--r--api/system-current.txt34
-rw-r--r--core/java/android/app/backup/BackupAgent.java276
-rw-r--r--core/java/android/app/backup/FullBackup.java401
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java23
-rw-r--r--core/java/android/content/pm/PackageParser.java18
-rw-r--r--core/res/res/values/attrs_manifest.xml6
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--core/tests/coretests/src/android/app/backup/FullBackupTest.java258
-rw-r--r--packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java8
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java2
11 files changed, 958 insertions, 103 deletions
diff --git a/api/current.txt b/api/current.txt
index de401b2..8057af2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -280,7 +280,7 @@ package android {
field public static final int allowParallelSyncs = 16843570; // 0x1010332
field public static final int allowSingleTap = 16843353; // 0x1010259
field public static final int allowTaskReparenting = 16843268; // 0x1010204
- field public static final int allowUndo = 16844005; // 0x10104e5
+ field public static final int allowUndo = 16844006; // 0x10104e6
field public static final int alpha = 16843551; // 0x101031f
field public static final int alphabeticShortcut = 16843235; // 0x10101e3
field public static final int alwaysDrawnWithCache = 16842991; // 0x10100ef
@@ -301,7 +301,7 @@ package android {
field public static final int anyDensity = 16843372; // 0x101026c
field public static final int apduServiceBanner = 16843757; // 0x10103ed
field public static final int apiKey = 16843281; // 0x1010211
- field public static final int assistBlocked = 16844019; // 0x10104f3
+ field public static final int assistBlocked = 16844020; // 0x10104f4
field public static final int author = 16843444; // 0x10102b4
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
@@ -312,7 +312,7 @@ package android {
field public static final int autoStart = 16843445; // 0x10102b5
field public static final deprecated int autoText = 16843114; // 0x101016a
field public static final int autoUrlDetect = 16843404; // 0x101028c
- field public static final int autoVerify = 16844009; // 0x10104e9
+ field public static final int autoVerify = 16844010; // 0x10104ea
field public static final int background = 16842964; // 0x10100d4
field public static final int backgroundDimAmount = 16842802; // 0x1010032
field public static final int backgroundDimEnabled = 16843295; // 0x101021f
@@ -336,7 +336,7 @@ package android {
field public static final int bottomRightRadius = 16843180; // 0x10101ac
field public static final int breadCrumbShortTitle = 16843524; // 0x1010304
field public static final int breadCrumbTitle = 16843523; // 0x1010303
- field public static final int breakStrategy = 16844010; // 0x10104ea
+ field public static final int breakStrategy = 16844011; // 0x10104eb
field public static final int bufferType = 16843086; // 0x101014e
field public static final int button = 16843015; // 0x1010107
field public static final int buttonBarButtonStyle = 16843567; // 0x101032f
@@ -398,7 +398,7 @@ package android {
field public static final int colorActivatedHighlight = 16843664; // 0x1010390
field public static final int colorBackground = 16842801; // 0x1010031
field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
- field public static final int colorBackgroundFloating = 16844006; // 0x10104e6
+ field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
field public static final int colorButtonNormal = 16843819; // 0x101042b
field public static final int colorControlActivated = 16843818; // 0x101042a
field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -507,7 +507,7 @@ package android {
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
- field public static final int dynamicResources = 16844018; // 0x10104f2
+ field public static final int dynamicResources = 16844019; // 0x10104f3
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -541,7 +541,7 @@ package android {
field public static final int expandableListViewWhiteStyle = 16843446; // 0x10102b6
field public static final int exported = 16842768; // 0x1010010
field public static final int extraTension = 16843371; // 0x101026b
- field public static final int extractNativeLibs = 16844007; // 0x10104e7
+ field public static final int extractNativeLibs = 16844008; // 0x10104e8
field public static final int factor = 16843219; // 0x10101d3
field public static final int fadeDuration = 16843384; // 0x1010278
field public static final int fadeEnabled = 16843390; // 0x101027e
@@ -610,6 +610,7 @@ 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 fullBackupContent = 16844005; // 0x10104e5
field public static final int fullBackupOnly = 16843891; // 0x1010473
field public static final int fullBright = 16842954; // 0x10100ca
field public static final int fullDark = 16842950; // 0x10100c6
@@ -796,7 +797,7 @@ package android {
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 leftIndents = 16844015; // 0x10104ef
+ field public static final int leftIndents = 16844016; // 0x10104f0
field public static final int letterSpacing = 16843958; // 0x10104b6
field public static final int lineSpacingExtra = 16843287; // 0x1010217
field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -819,7 +820,7 @@ package android {
field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208
field public static final int listViewStyle = 16842868; // 0x1010074
field public static final int listViewWhiteStyle = 16842869; // 0x1010075
- field public static final int lockTaskMode = 16844014; // 0x10104ee
+ field public static final int lockTaskMode = 16844015; // 0x10104ef
field public static final int logo = 16843454; // 0x10102be
field public static final int longClickable = 16842982; // 0x10100e6
field public static final int loopViews = 16843527; // 0x1010307
@@ -997,7 +998,7 @@ package android {
field public static final int readPermission = 16842759; // 0x1010007
field public static final int recognitionService = 16843932; // 0x101049c
field public static final int relinquishTaskIdentity = 16843894; // 0x1010476
- field public static final int removeBeforeMRelease = 16844013; // 0x10104ed
+ field public static final int removeBeforeMRelease = 16844014; // 0x10104ee
field public static final int reparent = 16843964; // 0x10104bc
field public static final int reparentWithOverlay = 16843965; // 0x10104bd
field public static final int repeatCount = 16843199; // 0x10101bf
@@ -1025,7 +1026,7 @@ package android {
field public static final int reversible = 16843851; // 0x101044b
field public static final int revisionCode = 16843989; // 0x10104d5
field public static final int right = 16843183; // 0x10101af
- field public static final int rightIndents = 16844016; // 0x10104f0
+ field public static final int rightIndents = 16844017; // 0x10104f1
field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
field public static final int ringtoneType = 16843257; // 0x10101f9
field public static final int rotation = 16843558; // 0x1010326
@@ -1101,7 +1102,7 @@ package android {
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
- field public static final int showForAllUsers = 16844017; // 0x10104f1
+ field public static final int showForAllUsers = 16844018; // 0x10104f2
field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
field public static final int showSilent = 16843259; // 0x10101fb
field public static final int showText = 16843949; // 0x10104ad
@@ -1173,7 +1174,7 @@ package android {
field public static final int strokeLineJoin = 16843788; // 0x101040c
field public static final int strokeMiterLimit = 16843789; // 0x101040d
field public static final int strokeWidth = 16843783; // 0x1010407
- field public static final int stylusButtonPressable = 16844020; // 0x10104f4
+ field public static final int stylusButtonPressable = 16844021; // 0x10104f5
field public static final int submitBackground = 16843912; // 0x1010488
field public static final int subtitle = 16843473; // 0x10102d1
field public static final int subtitleTextAppearance = 16843823; // 0x101042f
@@ -1188,7 +1189,7 @@ package android {
field public static final int summaryColumn = 16843426; // 0x10102a2
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
- field public static final int supportsAssist = 16844011; // 0x10104eb
+ field public static final int supportsAssist = 16844012; // 0x10104ec
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
field public static final int supportsUploading = 16843419; // 0x101029b
@@ -1289,7 +1290,7 @@ package android {
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
- field public static final int thumbPosition = 16844012; // 0x10104ec
+ field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -1353,7 +1354,7 @@ package android {
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
field public static final int useLevel = 16843167; // 0x101019f
field public static final int userVisible = 16843409; // 0x1010291
- field public static final int usesCleartextTraffic = 16844008; // 0x10104e8
+ field public static final int usesCleartextTraffic = 16844009; // 0x10104e9
field public static final int value = 16842788; // 0x1010024
field public static final int valueFrom = 16843486; // 0x10102de
field public static final int valueTo = 16843487; // 0x10102df
@@ -9010,6 +9011,7 @@ package android.content.pm {
field public int descriptionRes;
field public boolean enabled;
field public int flags;
+ field public int fullBackupContent;
field public boolean hardwareAccelerated;
field public int largestWidthLimitDp;
field public java.lang.String manageSpaceActivityName;
diff --git a/api/system-current.txt b/api/system-current.txt
index 59578c2..ac4e04c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -351,7 +351,7 @@ package android {
field public static final int allowParallelSyncs = 16843570; // 0x1010332
field public static final int allowSingleTap = 16843353; // 0x1010259
field public static final int allowTaskReparenting = 16843268; // 0x1010204
- field public static final int allowUndo = 16844005; // 0x10104e5
+ field public static final int allowUndo = 16844006; // 0x10104e6
field public static final int alpha = 16843551; // 0x101031f
field public static final int alphabeticShortcut = 16843235; // 0x10101e3
field public static final int alwaysDrawnWithCache = 16842991; // 0x10100ef
@@ -372,7 +372,7 @@ package android {
field public static final int anyDensity = 16843372; // 0x101026c
field public static final int apduServiceBanner = 16843757; // 0x10103ed
field public static final int apiKey = 16843281; // 0x1010211
- field public static final int assistBlocked = 16844019; // 0x10104f3
+ field public static final int assistBlocked = 16844020; // 0x10104f4
field public static final int author = 16843444; // 0x10102b4
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
@@ -383,7 +383,7 @@ package android {
field public static final int autoStart = 16843445; // 0x10102b5
field public static final deprecated int autoText = 16843114; // 0x101016a
field public static final int autoUrlDetect = 16843404; // 0x101028c
- field public static final int autoVerify = 16844009; // 0x10104e9
+ field public static final int autoVerify = 16844010; // 0x10104ea
field public static final int background = 16842964; // 0x10100d4
field public static final int backgroundDimAmount = 16842802; // 0x1010032
field public static final int backgroundDimEnabled = 16843295; // 0x101021f
@@ -407,7 +407,7 @@ package android {
field public static final int bottomRightRadius = 16843180; // 0x10101ac
field public static final int breadCrumbShortTitle = 16843524; // 0x1010304
field public static final int breadCrumbTitle = 16843523; // 0x1010303
- field public static final int breakStrategy = 16844010; // 0x10104ea
+ field public static final int breakStrategy = 16844011; // 0x10104eb
field public static final int bufferType = 16843086; // 0x101014e
field public static final int button = 16843015; // 0x1010107
field public static final int buttonBarButtonStyle = 16843567; // 0x101032f
@@ -469,7 +469,7 @@ package android {
field public static final int colorActivatedHighlight = 16843664; // 0x1010390
field public static final int colorBackground = 16842801; // 0x1010031
field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
- field public static final int colorBackgroundFloating = 16844006; // 0x10104e6
+ field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
field public static final int colorButtonNormal = 16843819; // 0x101042b
field public static final int colorControlActivated = 16843818; // 0x101042a
field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -578,7 +578,7 @@ package android {
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
- field public static final int dynamicResources = 16844018; // 0x10104f2
+ field public static final int dynamicResources = 16844019; // 0x10104f3
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -612,7 +612,7 @@ package android {
field public static final int expandableListViewWhiteStyle = 16843446; // 0x10102b6
field public static final int exported = 16842768; // 0x1010010
field public static final int extraTension = 16843371; // 0x101026b
- field public static final int extractNativeLibs = 16844007; // 0x10104e7
+ field public static final int extractNativeLibs = 16844008; // 0x10104e8
field public static final int factor = 16843219; // 0x10101d3
field public static final int fadeDuration = 16843384; // 0x1010278
field public static final int fadeEnabled = 16843390; // 0x101027e
@@ -681,6 +681,7 @@ 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 fullBackupContent = 16844005; // 0x10104e5
field public static final int fullBackupOnly = 16843891; // 0x1010473
field public static final int fullBright = 16842954; // 0x10100ca
field public static final int fullDark = 16842950; // 0x10100c6
@@ -867,7 +868,7 @@ package android {
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 leftIndents = 16844015; // 0x10104ef
+ field public static final int leftIndents = 16844016; // 0x10104f0
field public static final int letterSpacing = 16843958; // 0x10104b6
field public static final int lineSpacingExtra = 16843287; // 0x1010217
field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -890,7 +891,7 @@ package android {
field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208
field public static final int listViewStyle = 16842868; // 0x1010074
field public static final int listViewWhiteStyle = 16842869; // 0x1010075
- field public static final int lockTaskMode = 16844014; // 0x10104ee
+ field public static final int lockTaskMode = 16844015; // 0x10104ef
field public static final int logo = 16843454; // 0x10102be
field public static final int longClickable = 16842982; // 0x10100e6
field public static final int loopViews = 16843527; // 0x1010307
@@ -1068,7 +1069,7 @@ package android {
field public static final int readPermission = 16842759; // 0x1010007
field public static final int recognitionService = 16843932; // 0x101049c
field public static final int relinquishTaskIdentity = 16843894; // 0x1010476
- field public static final int removeBeforeMRelease = 16844013; // 0x10104ed
+ field public static final int removeBeforeMRelease = 16844014; // 0x10104ee
field public static final int reparent = 16843964; // 0x10104bc
field public static final int reparentWithOverlay = 16843965; // 0x10104bd
field public static final int repeatCount = 16843199; // 0x10101bf
@@ -1096,7 +1097,7 @@ package android {
field public static final int reversible = 16843851; // 0x101044b
field public static final int revisionCode = 16843989; // 0x10104d5
field public static final int right = 16843183; // 0x10101af
- field public static final int rightIndents = 16844016; // 0x10104f0
+ field public static final int rightIndents = 16844017; // 0x10104f1
field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
field public static final int ringtoneType = 16843257; // 0x10101f9
field public static final int rotation = 16843558; // 0x1010326
@@ -1176,7 +1177,7 @@ package android {
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
- field public static final int showForAllUsers = 16844017; // 0x10104f1
+ field public static final int showForAllUsers = 16844018; // 0x10104f2
field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
field public static final int showSilent = 16843259; // 0x10101fb
field public static final int showText = 16843949; // 0x10104ad
@@ -1248,7 +1249,7 @@ package android {
field public static final int strokeLineJoin = 16843788; // 0x101040c
field public static final int strokeMiterLimit = 16843789; // 0x101040d
field public static final int strokeWidth = 16843783; // 0x1010407
- field public static final int stylusButtonPressable = 16844020; // 0x10104f4
+ field public static final int stylusButtonPressable = 16844021; // 0x10104f5
field public static final int submitBackground = 16843912; // 0x1010488
field public static final int subtitle = 16843473; // 0x10102d1
field public static final int subtitleTextAppearance = 16843823; // 0x101042f
@@ -1263,7 +1264,7 @@ package android {
field public static final int summaryColumn = 16843426; // 0x10102a2
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
- field public static final int supportsAssist = 16844011; // 0x10104eb
+ field public static final int supportsAssist = 16844012; // 0x10104ec
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
field public static final int supportsUploading = 16843419; // 0x101029b
@@ -1364,7 +1365,7 @@ package android {
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
- field public static final int thumbPosition = 16844012; // 0x10104ec
+ field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -1428,7 +1429,7 @@ package android {
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
field public static final int useLevel = 16843167; // 0x101019f
field public static final int userVisible = 16843409; // 0x1010291
- field public static final int usesCleartextTraffic = 16844008; // 0x10104e8
+ field public static final int usesCleartextTraffic = 16844009; // 0x10104e9
field public static final int value = 16842788; // 0x1010024
field public static final int valueFrom = 16843486; // 0x10102de
field public static final int valueTo = 16843487; // 0x10102df
@@ -9239,6 +9240,7 @@ package android.content.pm {
field public int descriptionRes;
field public boolean enabled;
field public int flags;
+ field public int fullBackupContent;
field public boolean hardwareAccelerated;
field public int largestWidthLimitDp;
field public java.lang.String manageSpaceActivityName;
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index d8556a2..6fca0de 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -33,15 +33,21 @@ import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;
+import android.util.ArraySet;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.util.HashSet;
+import java.util.Collection;
import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import org.xmlpull.v1.XmlPullParserException;
+
/**
* Provides the central interface between an
* application and Android's data backup infrastructure. An application that wishes
@@ -164,7 +170,6 @@ public abstract class BackupAgent extends ContextWrapper {
* to do one-time initialization before the actual backup or restore operation
* is begun.
* <p>
- * Agents do not need to override this method.
*/
public void onCreate() {
}
@@ -268,19 +273,41 @@ public abstract class BackupAgent extends ContextWrapper {
* listed above. Apps only need to override this method if they need to impose special
* limitations on which files are being stored beyond the control that
* {@link #getNoBackupFilesDir()} offers.
+ * Alternatively they can provide an xml resource to specify what data to include or exclude.
+ *
*
* @param data A structured wrapper pointing to the backup destination.
* @throws IOException
*
* @see Context#getNoBackupFilesDir()
+ * @see ApplicationInfo#fullBackupContent
* @see #fullBackupFile(File, FullBackupDataOutput)
* @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
*/
public void onFullBackup(FullBackupDataOutput data) throws IOException {
- ApplicationInfo appInfo = getApplicationInfo();
+ FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
+ if (!backupScheme.isFullBackupContentEnabled()) {
+ return;
+ }
+
+ Map<String, Set<String>> manifestIncludeMap;
+ ArraySet<String> manifestExcludeSet;
+ try {
+ manifestIncludeMap =
+ backupScheme.maybeParseAndGetCanonicalIncludePaths();
+ manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths();
+ } catch (IOException | XmlPullParserException e) {
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER,
+ "Exception trying to parse fullBackupContent xml file!"
+ + " Aborting full backup.", e);
+ }
+ return;
+ }
+
+ final String packageName = getPackageName();
+ final ApplicationInfo appInfo = getApplicationInfo();
- // Note that we don't need to think about the no_backup dir because it's outside
- // all of the ones we will be traversing
String rootDir = new File(appInfo.dataDir).getCanonicalPath();
String filesDir = getFilesDir().getCanonicalPath();
String nobackupDir = getNoBackupFilesDir().getCanonicalPath();
@@ -292,34 +319,49 @@ public abstract class BackupAgent extends ContextWrapper {
? new File(appInfo.nativeLibraryDir).getCanonicalPath()
: null;
- // Filters, the scan queue, and the set of resulting entities
- HashSet<String> filterSet = new HashSet<String>();
- String packageName = getPackageName();
+ // Maintain a set of excluded directories so that as we traverse the tree we know we're not
+ // going places we don't expect, and so the manifest includes can't take precedence over
+ // what the framework decides is not to be included.
+ final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
- // Okay, start with the app's root tree, but exclude all of the canonical subdirs
+ // Add the directories we always exclude.
+ traversalExcludeSet.add(cacheDir);
+ traversalExcludeSet.add(codeCacheDir);
+ traversalExcludeSet.add(nobackupDir);
if (libDir != null) {
- filterSet.add(libDir);
+ traversalExcludeSet.add(libDir);
}
- filterSet.add(cacheDir);
- filterSet.add(codeCacheDir);
- filterSet.add(databaseDir);
- filterSet.add(sharedPrefsDir);
- filterSet.add(filesDir);
- filterSet.add(nobackupDir);
- 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);
+
+ traversalExcludeSet.add(databaseDir);
+ traversalExcludeSet.add(sharedPrefsDir);
+ traversalExcludeSet.add(filesDir);
+
+ // Root dir first.
+ applyXmlFiltersAndDoFullBackupForDomain(
+ packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
+ manifestExcludeSet, traversalExcludeSet, data);
+ traversalExcludeSet.add(rootDir);
+
+ // Data dir next.
+ traversalExcludeSet.remove(filesDir);
+ applyXmlFiltersAndDoFullBackupForDomain(
+ packageName, FullBackup.DATA_TREE_TOKEN, manifestIncludeMap,
+ manifestExcludeSet, traversalExcludeSet, data);
+ traversalExcludeSet.add(filesDir);
+
+ // Database directory.
+ traversalExcludeSet.remove(databaseDir);
+ applyXmlFiltersAndDoFullBackupForDomain(
+ packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap,
+ manifestExcludeSet, traversalExcludeSet, data);
+ traversalExcludeSet.add(databaseDir);
+
+ // SharedPrefs.
+ traversalExcludeSet.remove(sharedPrefsDir);
+ applyXmlFiltersAndDoFullBackupForDomain(
+ packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
+ manifestExcludeSet, traversalExcludeSet, data);
+ traversalExcludeSet.add(sharedPrefsDir);
// getExternalFilesDir() location associated with this app. Technically there should
// not be any files here if the app does not properly have permission to access
@@ -331,8 +373,36 @@ public abstract class BackupAgent extends ContextWrapper {
if (Process.myUid() != Process.SYSTEM_UID) {
File efLocation = getExternalFilesDir(null);
if (efLocation != null) {
- fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN,
- efLocation.getCanonicalPath(), null, data);
+ applyXmlFiltersAndDoFullBackupForDomain(
+ packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap,
+ manifestExcludeSet, traversalExcludeSet, data);
+ }
+
+ }
+ }
+
+ /**
+ * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
+ * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
+ * is a directory.
+ */
+ private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
+ Map<String, Set<String>> includeMap,
+ ArraySet<String> filterSet,
+ ArraySet<String> traversalExcludeSet,
+ FullBackupDataOutput data)
+ throws IOException {
+ if (includeMap == null || includeMap.size() == 0) {
+ // Do entire sub-tree for the provided token.
+ fullBackupFileTree(packageName, domainToken,
+ FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
+ filterSet, traversalExcludeSet, data);
+ } else if (includeMap.get(domainToken) != null) {
+ // This will be null if the xml parsing didn't yield any rules for
+ // this domain (there may still be rules for other domains).
+ for (String includeFile : includeMap.get(domainToken)) {
+ fullBackupFileTree(packageName, domainToken, includeFile, filterSet,
+ traversalExcludeSet, data);
}
}
}
@@ -430,21 +500,31 @@ public abstract class BackupAgent extends ContextWrapper {
// 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);
}
/**
* 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
+ * 'excludes' parameters are non-null, they are 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.
*
+ * @param systemExcludes An optional list of excludes.
* @hide
*/
- protected final void fullBackupFileTree(String packageName, String domain, String rootPath,
- HashSet<String> excludes, FullBackupDataOutput output) {
- File rootFile = new File(rootPath);
+ protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
+ ArraySet<String> manifestExcludes,
+ ArraySet<String> systemExcludes,
+ FullBackupDataOutput output) {
+ // Pull out the domain and set it aside to use when making the tarball.
+ String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+ if (domainPath == null) {
+ // Should never happen.
+ return;
+ }
+
+ File rootFile = new File(startingPath);
if (rootFile.exists()) {
LinkedList<File> scanQueue = new LinkedList<File>();
scanQueue.add(rootFile);
@@ -456,7 +536,10 @@ public abstract class BackupAgent extends ContextWrapper {
filePath = file.getCanonicalPath();
// prune this subtree?
- if (excludes != null && excludes.contains(filePath)) {
+ if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
+ continue;
+ }
+ if (systemExcludes != null && systemExcludes.contains(filePath)) {
continue;
}
@@ -475,14 +558,20 @@ public abstract class BackupAgent extends ContextWrapper {
}
} catch (IOException e) {
if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file);
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file);
+ }
continue;
} catch (ErrnoException e) {
if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e);
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e);
+ }
continue;
}
// Finally, back this file up (or measure it) before proceeding
- FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, output);
+ FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output);
}
}
}
@@ -516,10 +605,91 @@ public abstract class BackupAgent extends ContextWrapper {
public void onRestoreFile(ParcelFileDescriptor data, long size,
File destination, int type, long mode, long mtime)
throws IOException {
+ FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
+ if (!bs.isFullBackupContentEnabled()) {
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER,
+ "onRestoreFile \"" + destination.getCanonicalPath()
+ + "\" : fullBackupContent not enabled for " + getPackageName());
+ }
+ return;
+ }
+ Map<String, Set<String>> includes = null;
+ ArraySet<String> excludes = null;
+ final String destinationCanonicalPath = destination.getCanonicalPath();
+ try {
+ includes = bs.maybeParseAndGetCanonicalIncludePaths();
+ excludes = bs.maybeParseAndGetCanonicalExcludePaths();
+ } catch (XmlPullParserException e) {
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER,
+ "onRestoreFile \"" + destinationCanonicalPath
+ + "\" : Exception trying to parse fullBackupContent xml file!"
+ + " Aborting onRestoreFile.", e);
+ }
+ return;
+ }
+
+ if (excludes != null &&
+ isFileSpecifiedInPathList(destination, excludes)) {
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER,
+ "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in"
+ + " excludes; skipping.");
+ }
+ return;
+ }
+
+ if (includes != null && !includes.isEmpty()) {
+ // Rather than figure out the <include/> domain based on the path (a lot of code, and
+ // it's a small list), we'll go through and look for it.
+ boolean explicitlyIncluded = false;
+ for (Set<String> domainIncludes : includes.values()) {
+ explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
+ if (explicitlyIncluded) {
+ break;
+ }
+ }
+ if (!explicitlyIncluded) {
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER,
+ "onRestoreFile: Trying to restore \""
+ + destinationCanonicalPath + "\" but it isn't specified"
+ + " in the included files; skipping.");
+ }
+ return;
+ }
+ }
FullBackup.restoreFile(data, size, type, mode, mtime, destination);
}
/**
+ * @return True if the provided file is either directly in the provided list, or the provided
+ * file is within a directory in the list.
+ */
+ private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)
+ throws IOException {
+ for (String canonicalPath : canonicalPathList) {
+ File fileFromList = new File(canonicalPath);
+ if (fileFromList.isDirectory()) {
+ if (file.isDirectory()) {
+ // If they are both directories check exact equals.
+ return file.equals(fileFromList);
+ } else {
+ // O/w we have to check if the file is within the directory from the list.
+ return file.getCanonicalPath().startsWith(canonicalPath);
+ }
+ } else {
+ if (file.equals(fileFromList)) {
+ // Need to check the explicit "equals" so we don't end up with substrings.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
* Only specialized platform agents should overload this entry point to support
* restores to crazy non-app locations.
* @hide
@@ -533,31 +703,9 @@ public abstract class BackupAgent extends ContextWrapper {
+ " 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().getCanonicalPath();
- } else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) {
- basePath = getDatabasePath("foo").getParentFile().getCanonicalPath();
- } else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) {
- basePath = new File(getApplicationInfo().dataDir).getCanonicalPath();
- } else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) {
- basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
- } else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
- basePath = getCacheDir().getCanonicalPath();
- } else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
- // make sure we can try to restore here before proceeding
- if (Process.myUid() != Process.SYSTEM_UID) {
- File efLocation = getExternalFilesDir(null);
- if (efLocation != null) {
- basePath = getExternalFilesDir(null).getCanonicalPath();
- mode = -1; // < 0 is a token to skip attempting a chmod()
- }
- }
- } else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
- basePath = getNoBackupFilesDir().getCanonicalPath();
- } else {
- // Not a supported location
- Log.i(TAG, "Unrecognized domain " + domain);
+ basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+ if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
+ mode = -1; // < 0 is a token to skip attempting a chmod()
}
// Now that we've figured out where the data goes, send it on its way
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 259884e..7718a36 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,16 +16,31 @@
package android.app.backup;
-import android.os.ParcelFileDescriptor;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
+import android.os.*;
+import android.os.Process;
import android.system.ErrnoException;
import android.system.Os;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParser;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.xmlpull.v1.XmlPullParserException;
/**
* Global constant definitions et cetera related to the full-backup-to-fd
* binary format. Nothing in this namespace is part of any API; it's all
@@ -35,6 +50,8 @@ import java.io.IOException;
*/
public class FullBackup {
static final String TAG = "FullBackup";
+ /** Enable this log tag to get verbose information while parsing the client xml. */
+ static final String TAG_XML_PARSER = "BackupXmlParserLogging";
public static final String APK_TREE_TOKEN = "a";
public static final String OBB_TREE_TOKEN = "obb";
@@ -60,6 +77,27 @@ public class FullBackup {
static public native int backupToTar(String packageName, String domain,
String linkdomain, String rootpath, String path, FullBackupDataOutput output);
+ private static final Map<String, BackupScheme> kPackageBackupSchemeMap =
+ new ArrayMap<String, BackupScheme>();
+
+ static synchronized BackupScheme getBackupScheme(Context context) {
+ BackupScheme backupSchemeForPackage =
+ kPackageBackupSchemeMap.get(context.getPackageName());
+ if (backupSchemeForPackage == null) {
+ backupSchemeForPackage = new BackupScheme(context);
+ kPackageBackupSchemeMap.put(context.getPackageName(), backupSchemeForPackage);
+ }
+ return backupSchemeForPackage;
+ }
+
+ public static BackupScheme getBackupSchemeForTest(Context context) {
+ BackupScheme testing = new BackupScheme(context);
+ testing.mExcludes = new ArraySet();
+ testing.mIncludes = new ArrayMap();
+ return testing;
+ }
+
+
/**
* 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,
@@ -106,6 +144,8 @@ public class FullBackup {
if (!parent.exists()) {
// in practice this will only be for the default semantic directories,
// and using the default mode for those is appropriate.
+ // This can also happen for the case where a parent directory has been
+ // excluded, but a file within that directory has been included.
parent.mkdirs();
}
out = new FileOutputStream(outFile);
@@ -154,4 +194,363 @@ public class FullBackup {
outFile.setLastModified(mtime);
}
}
+
+ @VisibleForTesting
+ public static class BackupScheme {
+ private final File FILES_DIR;
+ private final File DATABASE_DIR;
+ private final File ROOT_DIR;
+ private final File SHAREDPREF_DIR;
+ private final File EXTERNAL_DIR;
+ private final File CACHE_DIR;
+ private final File NOBACKUP_DIR;
+
+ final int mFullBackupContent;
+ final PackageManager mPackageManager;
+ final String mPackageName;
+
+ /**
+ * Parse out the semantic domains into the correct physical location.
+ */
+ String tokenToDirectoryPath(String domainToken) {
+ try {
+ if (domainToken.equals(FullBackup.DATA_TREE_TOKEN)) {
+ return FILES_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.DATABASE_TREE_TOKEN)) {
+ return DATABASE_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.ROOT_TREE_TOKEN)) {
+ return ROOT_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) {
+ return SHAREDPREF_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.CACHE_TREE_TOKEN)) {
+ return CACHE_DIR.getCanonicalPath();
+ } else if (domainToken.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
+ if (EXTERNAL_DIR != null) {
+ return EXTERNAL_DIR.getCanonicalPath();
+ } else {
+ return null;
+ }
+ } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
+ return NOBACKUP_DIR.getCanonicalPath();
+ }
+ // Not a supported location
+ Log.i(TAG, "Unrecognized domain " + domainToken);
+ return null;
+ } catch (IOException e) {
+ Log.i(TAG, "Error reading directory for domain: " + domainToken);
+ return null;
+ }
+
+ }
+ /**
+ * A map of domain -> list of canonical file names in that domain that are to be included.
+ * We keep track of the domain so that we can go through the file system in order later on.
+ */
+ Map<String, Set<String>> mIncludes;
+ /**e
+ * List that will be populated with the canonical names of each file or directory that is
+ * to be excluded.
+ */
+ ArraySet<String> mExcludes;
+
+ BackupScheme(Context context) {
+ mFullBackupContent = context.getApplicationInfo().fullBackupContent;
+ mPackageManager = context.getPackageManager();
+ mPackageName = context.getPackageName();
+ FILES_DIR = context.getFilesDir();
+ DATABASE_DIR = context.getDatabasePath("foo").getParentFile();
+ ROOT_DIR = new File(context.getApplicationInfo().dataDir);
+ SHAREDPREF_DIR = context.getSharedPrefsFile("foo").getParentFile();
+ CACHE_DIR = context.getCacheDir();
+ NOBACKUP_DIR = context.getNoBackupFilesDir();
+ if (android.os.Process.myUid() != Process.SYSTEM_UID) {
+ EXTERNAL_DIR = context.getExternalFilesDir(null);
+ } else {
+ EXTERNAL_DIR = null;
+ }
+ }
+
+ boolean isFullBackupContentEnabled() {
+ if (mFullBackupContent < 0) {
+ // android:fullBackupContent="false", bail.
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"false\"");
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return A mapping of domain -> canonical paths within that domain. Each of these paths
+ * specifies a file that the client has explicitly included in their backup set. If this
+ * map is empty we will back up the entire data directory (including managed external
+ * storage).
+ */
+ public synchronized Map<String, Set<String>> maybeParseAndGetCanonicalIncludePaths()
+ throws IOException, XmlPullParserException {
+ if (mIncludes == null) {
+ maybeParseBackupSchemeLocked();
+ }
+ return mIncludes;
+ }
+
+ /**
+ * @return A set of canonical paths that are to be excluded from the backup/restore set.
+ */
+ public synchronized ArraySet<String> maybeParseAndGetCanonicalExcludePaths()
+ throws IOException, XmlPullParserException {
+ if (mExcludes == null) {
+ maybeParseBackupSchemeLocked();
+ }
+ return mExcludes;
+ }
+
+ private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException {
+ // This not being null is how we know that we've tried to parse the xml already.
+ mIncludes = new ArrayMap<String, Set<String>>();
+ mExcludes = new ArraySet<String>();
+
+ if (mFullBackupContent == 0) {
+ // android:fullBackupContent="true" which means that we'll do everything.
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"true\"");
+ }
+ } else {
+ // android:fullBackupContent="@xml/some_resource".
+ if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(FullBackup.TAG_XML_PARSER,
+ "android:fullBackupContent - found xml resource");
+ }
+ XmlResourceParser parser = null;
+ try {
+ parser = mPackageManager
+ .getResourcesForApplication(mPackageName)
+ .getXml(mFullBackupContent);
+ parseBackupSchemeFromXmlLocked(parser, mExcludes, mIncludes);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Throw it as an IOException
+ throw new IOException(e);
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public void parseBackupSchemeFromXmlLocked(XmlPullParser parser,
+ Set<String> excludes,
+ Map<String, Set<String>> includes)
+ throws IOException, XmlPullParserException {
+ int event = parser.getEventType(); // START_DOCUMENT
+ while (event != XmlPullParser.START_TAG) {
+ event = parser.next();
+ }
+
+ if (!"full-backup-content".equals(parser.getName())) {
+ throw new XmlPullParserException("Xml file didn't start with correct tag" +
+ " (<full-backup-content>). Found \"" + parser.getName() + "\"");
+ }
+
+ if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(TAG_XML_PARSER, "\n");
+ Log.v(TAG_XML_PARSER, "====================================================");
+ Log.v(TAG_XML_PARSER, "Found valid fullBackupContent; parsing xml resource.");
+ Log.v(TAG_XML_PARSER, "====================================================");
+ Log.v(TAG_XML_PARSER, "");
+ }
+
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ switch (event) {
+ case XmlPullParser.START_TAG:
+ validateInnerTagContents(parser);
+ final String domainFromXml = parser.getAttributeValue(null, "domain");
+ final File domainDirectory =
+ getDirectoryForCriteriaDomain(domainFromXml);
+ if (domainDirectory == null) {
+ if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(TAG_XML_PARSER, "...parsing \"" + parser.getName() + "\": "
+ + "domain=\"" + domainFromXml + "\" invalid; skipping");
+ }
+ break;
+ }
+ final File canonicalFile =
+ extractCanonicalFile(domainDirectory,
+ parser.getAttributeValue(null, "path"));
+ if (canonicalFile == null) {
+ break;
+ }
+
+ Set<String> activeSet = parseCurrentTagForDomain(
+ parser, excludes, includes, domainFromXml);
+ activeSet.add(canonicalFile.getCanonicalPath());
+ if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(TAG_XML_PARSER, "...parsed " + canonicalFile.getCanonicalPath()
+ + " for domain \"" + domainFromXml + "\"");
+ }
+
+ // Special case journal files (not dirs) for sqlite database. frowny-face.
+ // Note that for a restore, the file is never a directory (b/c it doesn't
+ // exist). We have no way of knowing a priori whether or not to expect a
+ // dir, so we add the -journal anyway to be safe.
+ if ("database".equals(domainFromXml) && !canonicalFile.isDirectory()) {
+ final String canonicalJournalPath =
+ canonicalFile.getCanonicalPath() + "-journal";
+ activeSet.add(canonicalJournalPath);
+ if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(TAG_XML_PARSER, "...automatically generated "
+ + canonicalJournalPath + ". Ignore if nonexistant.");
+ }
+ }
+ }
+ }
+ if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(TAG_XML_PARSER, "\n");
+ Log.v(TAG_XML_PARSER, "Xml resource parsing complete.");
+ Log.v(TAG_XML_PARSER, "Final tally.");
+ Log.v(TAG_XML_PARSER, "Includes:");
+ if (includes.isEmpty()) {
+ Log.v(TAG_XML_PARSER, " ...nothing specified (This means the entirety of app"
+ + " data minus excludes)");
+ } else {
+ for (Map.Entry<String, Set<String>> entry : includes.entrySet()) {
+ Log.v(TAG_XML_PARSER, " domain=" + entry.getKey());
+ for (String includeData : entry.getValue()) {
+ Log.v(TAG_XML_PARSER, " " + includeData);
+ }
+ }
+ }
+
+ Log.v(TAG_XML_PARSER, "Excludes:");
+ if (excludes.isEmpty()) {
+ Log.v(TAG_XML_PARSER, " ...nothing to exclude.");
+ } else {
+ for (String excludeData : excludes) {
+ Log.v(TAG_XML_PARSER, " " + excludeData);
+ }
+ }
+
+ Log.v(TAG_XML_PARSER, " ");
+ Log.v(TAG_XML_PARSER, "====================================================");
+ Log.v(TAG_XML_PARSER, "\n");
+ }
+ }
+
+ private Set<String> parseCurrentTagForDomain(XmlPullParser parser,
+ Set<String> excludes,
+ Map<String, Set<String>> includes,
+ String domain)
+ throws XmlPullParserException {
+ if ("include".equals(parser.getName())) {
+ final String domainToken = getTokenForXmlDomain(domain);
+ Set<String> includeSet = includes.get(domainToken);
+ if (includeSet == null) {
+ includeSet = new ArraySet<String>();
+ includes.put(domainToken, includeSet);
+ }
+ return includeSet;
+ } else if ("exclude".equals(parser.getName())) {
+ return excludes;
+ } else {
+ // Unrecognised tag => hard failure.
+ if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(TAG_XML_PARSER, "Invalid tag found in xml \""
+ + parser.getName() + "\"; aborting operation.");
+ }
+ throw new XmlPullParserException("Unrecognised tag in backup" +
+ " criteria xml (" + parser.getName() + ")");
+ }
+ }
+
+ /**
+ * Map xml specified domain (human-readable, what clients put in their manifest's xml) to
+ * BackupAgent internal data token.
+ * @return null if the xml domain was invalid.
+ */
+ private String getTokenForXmlDomain(String xmlDomain) {
+ if ("root".equals(xmlDomain)) {
+ return FullBackup.ROOT_TREE_TOKEN;
+ } else if ("file".equals(xmlDomain)) {
+ return FullBackup.DATA_TREE_TOKEN;
+ } else if ("database".equals(xmlDomain)) {
+ return FullBackup.DATABASE_TREE_TOKEN;
+ } else if ("sharedpref".equals(xmlDomain)) {
+ return FullBackup.SHAREDPREFS_TREE_TOKEN;
+ } else if ("external".equals(xmlDomain)) {
+ return FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ *
+ * @param domain Directory where the specified file should exist. Not null.
+ * @param filePathFromXml parsed from xml. Not sanitised before calling this function so may be
+ * null.
+ * @return The canonical path of the file specified or null if no such file exists.
+ */
+ private File extractCanonicalFile(File domain, String filePathFromXml) {
+ if (filePathFromXml == null) {
+ // Allow things like <include domain="sharedpref"/>
+ filePathFromXml = "";
+ }
+ if (filePathFromXml.contains("..")) {
+ if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(TAG_XML_PARSER, "...resolved \"" + domain.getPath() + " " + filePathFromXml
+ + "\", but the \"..\" path is not permitted; skipping.");
+ }
+ return null;
+ }
+ if (filePathFromXml.contains("//")) {
+ if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+ Log.v(TAG_XML_PARSER, "...resolved \"" + domain.getPath() + " " + filePathFromXml
+ + "\", which contains the invalid \"//\" sequence; skipping.");
+ }
+ return null;
+ }
+ return new File(domain, filePathFromXml);
+ }
+
+ /**
+ * @param domain parsed from xml. Not sanitised before calling this function so may be null.
+ * @return The directory relevant to the domain specified.
+ */
+ private File getDirectoryForCriteriaDomain(String domain) {
+ if (TextUtils.isEmpty(domain)) {
+ return null;
+ }
+ if ("file".equals(domain)) {
+ return FILES_DIR;
+ } else if ("database".equals(domain)) {
+ return DATABASE_DIR;
+ } else if ("root".equals(domain)) {
+ return ROOT_DIR;
+ } else if ("sharedpref".equals(domain)) {
+ return SHAREDPREF_DIR;
+ } else if ("external".equals(domain)) {
+ return EXTERNAL_DIR;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Let's be strict about the type of xml the client can write. If we see anything untoward,
+ * throw an XmlPullParserException.
+ */
+ private void validateInnerTagContents(XmlPullParser parser)
+ throws XmlPullParserException {
+ if (parser.getAttributeCount() > 2) {
+ throw new XmlPullParserException("At most 2 tag attributes allowed for \""
+ + parser.getName() + "\" tag (\"domain\" & \"path\".");
+ }
+ if (!"include".equals(parser.getName()) && !"exclude".equals(parser.getName())) {
+ throw new XmlPullParserException("A valid tag is one of \"<include/>\" or" +
+ " \"<exclude/>. You provided \"" + parser.getName() + "\"");
+ }
+ }
+ }
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 6c32873..707ef30 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -96,6 +96,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public String backupAgentName;
/**
+ * An optional attribute that indicates the app supports automatic backup of app data.
+ * <p>0 is the default and means the app's entire data folder + managed external storage will
+ * be backed up;
+ * Any negative value indicates the app does not support full-data backup, though it may still
+ * want to participate via the traditional key/value backup API;
+ * A positive number specifies an xml resource in which the application has defined its backup
+ * include/exclude criteria.
+ * <p>If android:allowBackup is set to false, this attribute is ignored.
+ *
+ * @see {@link android.content.Context#getNoBackupFilesDir}
+ * @see {@link #FLAG_ALLOW_BACKUP}
+ */
+ public int fullBackupContent = 0;
+
+ /**
* The default extra UI options for activities in this application.
* Set from the {@link android.R.attr#uiOptions} attribute in the
* activity's manifest.
@@ -686,6 +701,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
pw.println(prefix + "uiOptions=0x" + Integer.toHexString(uiOptions));
}
pw.println(prefix + "supportsRtl=" + (hasRtlSupport() ? "true" : "false"));
+ if (fullBackupContent > 0) {
+ pw.println(prefix + "fullBackupContent=@xml/" + fullBackupContent);
+ } else {
+ pw.println(prefix + "fullBackupContent=" + (fullBackupContent < 0 ? "false" : "true"));
+ }
super.dumpBack(pw, prefix);
}
@@ -763,6 +783,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
uiOptions = orig.uiOptions;
backupAgentName = orig.backupAgentName;
hardwareAccelerated = orig.hardwareAccelerated;
+ fullBackupContent = orig.fullBackupContent;
}
@@ -816,6 +837,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(descriptionRes);
dest.writeInt(uiOptions);
dest.writeInt(hardwareAccelerated ? 1 : 0);
+ dest.writeInt(fullBackupContent);
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -868,6 +890,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
descriptionRes = source.readInt();
uiOptions = source.readInt();
hardwareAccelerated = source.readInt() != 0;
+ fullBackupContent = source.readInt();
}
/**
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9596c42..acc27c3 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2421,8 +2421,8 @@ public class PackageParser {
if (allowBackup) {
ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
- // backupAgent, killAfterRestore, and restoreAnyVersion are only relevant
- // if backup is possible for the given application.
+ // backupAgent, killAfterRestore, fullBackupContent and restoreAnyVersion are only
+ // relevant if backup is possible for the given application.
String backupAgent = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestApplication_backupAgent,
Configuration.NATIVE_CONFIG_VERSION);
@@ -2449,6 +2449,20 @@ public class PackageParser {
ai.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
}
}
+
+ TypedValue v = sa.peekValue(
+ com.android.internal.R.styleable.AndroidManifestApplication_fullBackupContent);
+ if (v != null && (ai.fullBackupContent = v.resourceId) == 0) {
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "fullBackupContent specified as boolean=" +
+ (v.data == 0 ? "false" : "true"));
+ }
+ // "false" => -1, "true" => 0
+ ai.fullBackupContent = (v.data == 0 ? -1 : 0);
+ }
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "fullBackupContent=" + ai.fullBackupContent + " for " + pkgName);
+ }
}
TypedValue v = sa.peekValue(
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 4631427..59c6e4f 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -842,6 +842,11 @@
via adb. The default value of this attribute is <code>true</code>. -->
<attr name="allowBackup" format="boolean" />
+ <!-- Applications will set this in their manifest to opt-in to or out of full app data back-up
+ and restore. Alternatively they can set it to an xml resource within their app that will
+ be parsed by the BackupAgent to selectively backup files indicated within that xml. -->
+ <attr name="fullBackupContent" format="reference|boolean" />
+
<!-- Indicates that even though the application provides a <code>BackupAgent</code>,
only full-data streaming backup operations are to be performed to save the app's
data. This lets the app rely on full-data backups while still participating in
@@ -1189,6 +1194,7 @@
<attr name="backupAgent" />
<attr name="allowBackup" />
<attr name="fullBackupOnly" />
+ <attr name="fullBackupContent" />
<attr name="killAfterRestore" />
<attr name="restoreNeedsApplication" />
<attr name="restoreAnyVersion" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 79b81a7..1a5977f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2620,6 +2620,7 @@
<public type="attr" name="overflowTintMode" />
<public type="attr" name="navigationTint" />
<public type="attr" name="navigationTintMode" />
+ <public type="attr" name="fullBackupContent" />
<public type="style" name="Widget.Material.Button.Colored" />
diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
new file mode 100644
index 0000000..8c9c63c
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.File;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class FullBackupTest extends AndroidTestCase {
+ private XmlPullParserFactory mFactory;
+ private XmlPullParser mXpp;
+ private Context mContext;
+
+ Map<String, Set<String>> includeMap;
+ Set<String> excludesSet;
+
+ @Override
+ public void setUp() throws Exception {
+ mFactory = XmlPullParserFactory.newInstance();
+ mXpp = mFactory.newPullParser();
+ mContext = getContext();
+
+ includeMap = new ArrayMap();
+ excludesSet = new ArraySet();
+ }
+
+ public void testparseBackupSchemeFromXml_onlyInclude() throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<include path=\"onlyInclude.txt\" domain=\"file\"/>" +
+ "</full-backup-content>"));
+
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+
+ assertEquals("Excluding files when there was no <exclude/> tag.", 0, excludesSet.size());
+ assertEquals("Unexpected number of <include/>s", 1, includeMap.size());
+
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
+ assertEquals("Invalid path parsed for <include/>",
+ new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
+ fileDomainIncludes.iterator().next());
+ }
+
+ public void testparseBackupSchemeFromXml_onlyExclude() throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<exclude path=\"onlyExclude.txt\" domain=\"file\"/>" +
+ "</full-backup-content>"));
+
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+
+ assertEquals("Including files when there was no <include/> tag.", 0, includeMap.size());
+ assertEquals("Unexpected number of <exclude/>s", 1, excludesSet.size());
+ assertEquals("Invalid path parsed for <exclude/>",
+ new File(mContext.getFilesDir(), "onlyExclude.txt").getCanonicalPath(),
+ excludesSet.iterator().next());
+ }
+
+ public void testparseBackupSchemeFromXml_includeAndExclude() throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<exclude path=\"exclude.txt\" domain=\"file\"/>" +
+ "<include path=\"include.txt\" domain=\"file\"/>" +
+ "</full-backup-content>"));
+
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
+ assertEquals("Invalid path parsed for <include/>",
+ new File(mContext.getFilesDir(), "include.txt").getCanonicalPath(),
+ fileDomainIncludes.iterator().next());
+
+ assertEquals("Unexpected number of <exclude/>s", 1, excludesSet.size());
+ assertEquals("Invalid path parsed for <exclude/>",
+ new File(mContext.getFilesDir(), "exclude.txt").getCanonicalPath(),
+ excludesSet.iterator().next());
+ }
+
+ public void testparseBackupSchemeFromXml_lotsOfIncludesAndExcludes() throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<exclude path=\"exclude1.txt\" domain=\"file\"/>" +
+ "<include path=\"include1.txt\" domain=\"file\"/>" +
+ "<exclude path=\"exclude2.txt\" domain=\"database\"/>" +
+ "<include path=\"include2.txt\" domain=\"database\"/>" +
+ "<exclude path=\"exclude3.txt\" domain=\"sharedpref\"/>" +
+ "<include path=\"include3.txt\" domain=\"sharedpref\"/>" +
+ "</full-backup-content>"));
+
+
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
+ assertEquals("Invalid path parsed for <include/>",
+ new File(mContext.getFilesDir(), "include1.txt").getCanonicalPath(),
+ fileDomainIncludes.iterator().next());
+
+ Set<String> databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN);
+ assertEquals("Didn't find expected database domain include.",
+ 2, databaseDomainIncludes.size()); // two expected here because of "-journal" file
+ assertTrue("Invalid path parsed for <include/>",
+ databaseDomainIncludes.contains(
+ new File(mContext.getDatabasePath("foo").getParentFile(), "include2.txt")
+ .getCanonicalPath()));
+ assertTrue("Invalid path parsed for <include/>",
+ databaseDomainIncludes.contains(
+ new File(
+ mContext.getDatabasePath("foo").getParentFile(),
+ "include2.txt-journal")
+ .getCanonicalPath()));
+
+ Set<String> sharedPrefDomainIncludes = includeMap.get(FullBackup.SHAREDPREFS_TREE_TOKEN);
+ assertEquals("Didn't find expected sharedpref domain include.",
+ 1, sharedPrefDomainIncludes.size());
+ assertEquals("Invalid path parsed for <include/>",
+ new File(mContext.getSharedPrefsFile("foo").getParentFile(), "include3.txt")
+ .getCanonicalPath(),
+ sharedPrefDomainIncludes.iterator().next());
+
+
+ assertEquals("Unexpected number of <exclude/>s", 4, excludesSet.size());
+ // Sets are annoying to iterate over b/c order isn't enforced - convert to an array and
+ // sort lexicographically.
+ List<String> arrayedSet = new ArrayList<String>(excludesSet);
+ Collections.sort(arrayedSet);
+
+ assertEquals("Invalid path parsed for <exclude/>",
+ new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt")
+ .getCanonicalPath(),
+ arrayedSet.get(0));
+ assertEquals("Invalid path parsed for <exclude/>",
+ new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt-journal")
+ .getCanonicalPath(),
+ arrayedSet.get(1));
+ assertEquals("Invalid path parsed for <exclude/>",
+ new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
+ arrayedSet.get(2));
+ assertEquals("Invalid path parsed for <exclude/>",
+ new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3.txt")
+ .getCanonicalPath(),
+ arrayedSet.get(3));
+ }
+
+ public void testParseBackupSchemeFromXml_invalidXmlFails() throws Exception {
+ // Invalid root tag.
+ mXpp.setInput(new StringReader(
+ "<full-weird-tag>" +
+ "<exclude path=\"invalidRootTag.txt\" domain=\"file\"/>" +
+ "</ffull-weird-tag>" ));
+
+ try {
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+ fail("Invalid root xml tag should throw an XmlPullParserException");
+ } catch (XmlPullParserException expected) {}
+
+ // Invalid exclude tag.
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<excluded path=\"invalidExcludeTag.txt\" domain=\"file\"/>" +
+ "</full-backup-conten>t" ));
+ try {
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+ fail("Misspelled xml exclude tag should throw an XmlPullParserException");
+ } catch (XmlPullParserException expected) {}
+
+ // Just for good measure - invalid include tag.
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<yinclude path=\"invalidIncludeTag.txt\" domain=\"file\"/>" +
+ "</full-backup-conten>t" ));
+ try {
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+ fail("Misspelled xml exclude tag should throw an XmlPullParserException");
+ } catch (XmlPullParserException expected) {}
+
+ }
+
+ public void testInvalidPath_doesNotBackup() throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<exclude path=\"..\" domain=\"file\"/>" + // Invalid use of ".." dir.
+ "</full-backup-content>" ));
+
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+
+ assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
+
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
+ }
+ public void testDoubleDotInPath_isIgnored() throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<include path=\"..\" domain=\"file\"/>" + // Invalid use of ".." dir.
+ "</full-backup-content>" ));
+
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+
+ assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
+
+ Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+ assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
+ }
+
+ public void testDoubleSlashInPath_isIgnored() throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<exclude path=\"//hello.txt\" domain=\"file\"/>" + // Invalid use of "//"
+ "</full-backup-content>" ));
+
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+
+ assertEquals("Didn't throw away invalid path containing \"//\".", 0, excludesSet.size());
+ }
+}
diff --git a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java
index 89f84fc..e453cf5 100644
--- a/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java
+++ b/packages/SharedStorageBackup/src/com/android/sharedstoragebackup/SharedStorageAgent.java
@@ -8,11 +8,11 @@ import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
+import android.util.ArraySet;
import android.util.Slog;
import java.io.File;
import java.io.IOException;
-import java.util.HashSet;
public class SharedStorageAgent extends FullBackupAgent {
static final String TAG = "SharedStorageAgent";
@@ -42,7 +42,7 @@ public class SharedStorageAgent extends FullBackupAgent {
if (DEBUG) Slog.i(TAG, "Backing up " + mVolumes.length + " shared volumes");
// Ignore all apps' getExternalFilesDir() content; it is backed up as part of
// each app-specific payload.
- HashSet<String> externalFilesDirFilter = new HashSet<String>();
+ ArraySet<String> externalFilesDirFilter = new ArraySet();
final File externalAndroidRoot = new File(Environment.getExternalStorageDirectory(),
Environment.DIRECTORY_ANDROID);
externalFilesDirFilter.add(externalAndroidRoot.getCanonicalPath());
@@ -53,7 +53,9 @@ 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;
- fullBackupFileTree(null, domain, v.getPath(), externalFilesDirFilter, output);
+ fullBackupFileTree(null, domain, v.getPath(),
+ null /* manifestExcludes */,
+ externalFilesDirFilter /* systemExcludes */, output);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 1bed4f3..bfe8b5c 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -160,7 +160,7 @@ import libcore.io.IoUtils;
public class BackupManagerService {
private static final String TAG = "BackupManagerService";
- static final boolean DEBUG = true;
+ static final boolean DEBUG = false;
static final boolean MORE_DEBUG = false;
static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;