am 303650c9: Add full backup criteria to android manifest

* commit '303650c9cdb7cec88e7ec20747b161d9fff10719':
  Add full backup criteria to android manifest
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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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.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 @@
      * 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 @@
      * 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;
+        }
 
-        // 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
+        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();
+
         String rootDir = new File(appInfo.dataDir).getCanonicalPath();
         String filesDir = getFilesDir().getCanonicalPath();
         String nobackupDir = getNoBackupFilesDir().getCanonicalPath();
@@ -292,34 +319,49 @@
                 ? 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);
+        traversalExcludeSet.add(databaseDir);
+        traversalExcludeSet.add(sharedPrefsDir);
+        traversalExcludeSet.add(filesDir);
 
-        filterSet.add(filesDir);
-        filterSet.remove(databaseDir);
-        fullBackupFileTree(packageName, FullBackup.DATABASE_TREE_TOKEN, databaseDir, filterSet, data);
+        // Root dir first.
+        applyXmlFiltersAndDoFullBackupForDomain(
+                packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
+                manifestExcludeSet, traversalExcludeSet, data);
+        traversalExcludeSet.add(rootDir);
 
-        filterSet.add(databaseDir);
-        filterSet.remove(sharedPrefsDir);
-        fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data);
+        // 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 @@
         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 @@
         // 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 @@
                     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 @@
                     }
                 } 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 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 @@
                 + " 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 @@
  */
 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 @@
     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 @@
                     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 @@
             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 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 @@
             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 @@
         uiOptions = orig.uiOptions;
         backupAgentName = orig.backupAgentName;
         hardwareAccelerated = orig.hardwareAccelerated;
+        fullBackupContent = orig.fullBackupContent;
     }
 
 
@@ -816,6 +837,7 @@
         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 @@
         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 @@
         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 @@
                     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.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 @@
             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 @@
                 //     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 @@
 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;