Merge "Extend XML parser to allow optional requiredFlags attribute for <include /> rules in <full-backup-content> specification"
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index d36a794..d1c957b 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -18,6 +18,7 @@
import android.app.IBackupAgent;
import android.app.QueuedWork;
+import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
@@ -343,8 +344,8 @@
return;
}
- Map<String, Set<String>> manifestIncludeMap;
- ArraySet<String> manifestExcludeSet;
+ Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap;
+ ArraySet<PathWithRequiredFlags> manifestExcludeSet;
try {
manifestIncludeMap =
backupScheme.maybeParseAndGetCanonicalIncludePaths();
@@ -514,14 +515,13 @@
/**
* 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.
+ * is a directory, but only if all the required flags of the include rule are satisfied by
+ * the transport.
*/
private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
- Map<String, Set<String>> includeMap,
- ArraySet<String> filterSet,
- ArraySet<String> traversalExcludeSet,
- FullBackupDataOutput data)
- throws IOException {
+ Map<String, Set<PathWithRequiredFlags>> includeMap,
+ ArraySet<PathWithRequiredFlags> 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,
@@ -530,13 +530,22 @@
} 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);
+ for (PathWithRequiredFlags includeFile : includeMap.get(domainToken)) {
+ if (areIncludeRequiredTransportFlagsSatisfied(includeFile.getRequiredFlags(),
+ data.getTransportFlags())) {
+ fullBackupFileTree(packageName, domainToken, includeFile.getPath(), filterSet,
+ traversalExcludeSet, data);
+ }
}
}
}
+ private boolean areIncludeRequiredTransportFlagsSatisfied(int includeFlags,
+ int transportFlags) {
+ // all bits that are set in includeFlags must also be set in transportFlags
+ return (transportFlags & includeFlags) == includeFlags;
+ }
+
/**
* Write an entire file as part of a full-backup operation. The file's contents
* will be delivered to the backup destination along with the metadata necessary
@@ -683,7 +692,7 @@
* @hide
*/
protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
- ArraySet<String> manifestExcludes,
+ ArraySet<PathWithRequiredFlags> manifestExcludes,
ArraySet<String> systemExcludes,
FullBackupDataOutput output) {
// Pull out the domain and set it aside to use when making the tarball.
@@ -714,7 +723,8 @@
filePath = file.getCanonicalPath();
// prune this subtree?
- if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
+ if (manifestExcludes != null
+ && manifestExcludesContainFilePath(manifestExcludes, filePath)) {
continue;
}
if (systemExcludes != null && systemExcludes.contains(filePath)) {
@@ -750,6 +760,17 @@
}
}
+ private boolean manifestExcludesContainFilePath(
+ ArraySet<PathWithRequiredFlags> manifestExcludes, String filePath) {
+ for (PathWithRequiredFlags exclude : manifestExcludes) {
+ String excludePath = exclude.getPath();
+ if (excludePath != null && excludePath.equals(filePath)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Handle the data delivered via the given file descriptor during a full restore
* operation. The agent is given the path to the file's original location as well
@@ -796,8 +817,8 @@
return false;
}
- Map<String, Set<String>> includes = null;
- ArraySet<String> excludes = null;
+ Map<String, Set<PathWithRequiredFlags>> includes = null;
+ ArraySet<PathWithRequiredFlags> excludes = null;
final String destinationCanonicalPath = destination.getCanonicalPath();
try {
includes = bs.maybeParseAndGetCanonicalIncludePaths();
@@ -826,7 +847,7 @@
// 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()) {
+ for (Set<PathWithRequiredFlags> domainIncludes : includes.values()) {
explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
if (explicitlyIncluded) {
break;
@@ -849,9 +870,10 @@
* @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) {
+ private boolean isFileSpecifiedInPathList(File file,
+ Collection<PathWithRequiredFlags> canonicalPathList) throws IOException {
+ for (PathWithRequiredFlags canonical : canonicalPathList) {
+ String canonicalPath = canonical.getPath();
File fileFromList = new File(canonicalPath);
if (fileFromList.isDirectory()) {
if (file.isDirectory()) {
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index a5dd5bd..fb1c2d0 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -82,6 +82,9 @@
public static final String FULL_RESTORE_INTENT_ACTION = "fullrest";
public static final String CONF_TOKEN_INTENT_EXTRA = "conftoken";
+ public static final String FLAG_REQUIRED_CLIENT_SIDE_ENCRYPTION = "clientSideEncryption";
+ public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer";
+
/**
* @hide
*/
@@ -224,6 +227,9 @@
private final File EXTERNAL_DIR;
+ private final static String TAG_INCLUDE = "include";
+ private final static String TAG_EXCLUDE = "exclude";
+
final int mFullBackupContent;
final PackageManager mPackageManager;
final StorageManager mStorageManager;
@@ -303,15 +309,45 @@
}
/**
- * 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.
+ * Represents a path attribute specified in an <include /> rule along with optional
+ * transport flags required from the transport to include file(s) under that path as
+ * specified by requiredFlags attribute. If optional requiredFlags attribute is not
+ * provided, default requiredFlags to 0.
+ * Note: since our parsing codepaths were the same for <include /> and <exclude /> tags,
+ * this structure is also used for <exclude /> tags to preserve that, however you can expect
+ * the getRequiredFlags() to always return 0 for exclude rules.
*/
- ArraySet<String> mExcludes;
+ public static class PathWithRequiredFlags {
+ private final String mPath;
+ private final int mRequiredFlags;
+
+ public PathWithRequiredFlags(String path, int requiredFlags) {
+ mPath = path;
+ mRequiredFlags = requiredFlags;
+ }
+
+ public String getPath() {
+ return mPath;
+ }
+
+ public int getRequiredFlags() {
+ return mRequiredFlags;
+ }
+ }
+
+ /**
+ * A map of domain -> set of pairs (canonical file; required transport flags) in that
+ * domain that are to be included if the transport has decared the required flags.
+ * We keep track of the domain so that we can go through the file system in order later on.
+ */
+ Map<String, Set<PathWithRequiredFlags>> mIncludes;
+
+ /**
+ * Set that will be populated with pairs (canonical file; requiredFlags=0) for each file or
+ * directory that is to be excluded. Note that for excludes, the requiredFlags attribute is
+ * ignored and the value should be always set to 0.
+ */
+ ArraySet<PathWithRequiredFlags> mExcludes;
BackupScheme(Context context) {
mFullBackupContent = context.getApplicationInfo().fullBackupContent;
@@ -356,13 +392,14 @@
}
/**
- * @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).
+ * @return A mapping of domain -> set of pairs (canonical file; required transport flags)
+ * in that domain that are to be included if the transport has decared the required flags.
+ * 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 {
+ public synchronized Map<String, Set<PathWithRequiredFlags>>
+ maybeParseAndGetCanonicalIncludePaths() throws IOException, XmlPullParserException {
if (mIncludes == null) {
maybeParseBackupSchemeLocked();
}
@@ -370,9 +407,10 @@
}
/**
- * @return A set of canonical paths that are to be excluded from the backup/restore set.
+ * @return A set of (canonical paths; requiredFlags=0) that are to be excluded from the
+ * backup/restore set.
*/
- public synchronized ArraySet<String> maybeParseAndGetCanonicalExcludePaths()
+ public synchronized ArraySet<PathWithRequiredFlags> maybeParseAndGetCanonicalExcludePaths()
throws IOException, XmlPullParserException {
if (mExcludes == null) {
maybeParseBackupSchemeLocked();
@@ -382,8 +420,8 @@
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>();
+ mIncludes = new ArrayMap<String, Set<PathWithRequiredFlags>>();
+ mExcludes = new ArraySet<PathWithRequiredFlags>();
if (mFullBackupContent == 0) {
// android:fullBackupContent="true" which means that we'll do everything.
@@ -415,8 +453,8 @@
@VisibleForTesting
public void parseBackupSchemeFromXmlLocked(XmlPullParser parser,
- Set<String> excludes,
- Map<String, Set<String>> includes)
+ Set<PathWithRequiredFlags> excludes,
+ Map<String, Set<PathWithRequiredFlags>> includes)
throws IOException, XmlPullParserException {
int event = parser.getEventType(); // START_DOCUMENT
while (event != XmlPullParser.START_TAG) {
@@ -441,8 +479,7 @@
case XmlPullParser.START_TAG:
validateInnerTagContents(parser);
final String domainFromXml = parser.getAttributeValue(null, "domain");
- final File domainDirectory =
- getDirectoryForCriteriaDomain(domainFromXml);
+ final File domainDirectory = getDirectoryForCriteriaDomain(domainFromXml);
if (domainDirectory == null) {
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...parsing \"" + parser.getName() + "\": "
@@ -457,12 +494,23 @@
break;
}
- Set<String> activeSet = parseCurrentTagForDomain(
+ int requiredFlags = 0; // no transport flags are required by default
+ if (TAG_INCLUDE.equals(parser.getName())) {
+ // requiredFlags are only supported for <include /> tag, for <exclude />
+ // we should always leave them as the default = 0
+ requiredFlags = getRequiredFlagsFromString(
+ parser.getAttributeValue(null, "requireFlags"));
+ }
+
+ // retrieve the include/exclude set we'll be adding this rule to
+ Set<PathWithRequiredFlags> activeSet = parseCurrentTagForDomain(
parser, excludes, includes, domainFromXml);
- activeSet.add(canonicalFile.getCanonicalPath());
+ activeSet.add(new PathWithRequiredFlags(canonicalFile.getCanonicalPath(),
+ requiredFlags));
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...parsed " + canonicalFile.getCanonicalPath()
- + " for domain \"" + domainFromXml + "\"");
+ + " for domain \"" + domainFromXml + "\", requiredFlags + \""
+ + requiredFlags + "\"");
}
// Special case journal files (not dirs) for sqlite database. frowny-face.
@@ -472,14 +520,16 @@
if ("database".equals(domainFromXml) && !canonicalFile.isDirectory()) {
final String canonicalJournalPath =
canonicalFile.getCanonicalPath() + "-journal";
- activeSet.add(canonicalJournalPath);
+ activeSet.add(new PathWithRequiredFlags(canonicalJournalPath,
+ requiredFlags));
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...automatically generated "
+ canonicalJournalPath + ". Ignore if nonexistent.");
}
final String canonicalWalPath =
canonicalFile.getCanonicalPath() + "-wal";
- activeSet.add(canonicalWalPath);
+ activeSet.add(new PathWithRequiredFlags(canonicalWalPath,
+ requiredFlags));
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...automatically generated "
+ canonicalWalPath + ". Ignore if nonexistent.");
@@ -491,7 +541,8 @@
!canonicalFile.getCanonicalPath().endsWith(".xml")) {
final String canonicalXmlPath =
canonicalFile.getCanonicalPath() + ".xml";
- activeSet.add(canonicalXmlPath);
+ activeSet.add(new PathWithRequiredFlags(canonicalXmlPath,
+ requiredFlags));
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...automatically generated "
+ canonicalXmlPath + ". Ignore if nonexistent.");
@@ -508,10 +559,12 @@
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()) {
+ for (Map.Entry<String, Set<PathWithRequiredFlags>> entry
+ : includes.entrySet()) {
Log.v(TAG_XML_PARSER, " domain=" + entry.getKey());
- for (String includeData : entry.getValue()) {
- Log.v(TAG_XML_PARSER, " " + includeData);
+ for (PathWithRequiredFlags includeData : entry.getValue()) {
+ Log.v(TAG_XML_PARSER, " path: " + includeData.getPath()
+ + " requiredFlags: " + includeData.getRequiredFlags());
}
}
}
@@ -520,8 +573,9 @@
if (excludes.isEmpty()) {
Log.v(TAG_XML_PARSER, " ...nothing to exclude.");
} else {
- for (String excludeData : excludes) {
- Log.v(TAG_XML_PARSER, " " + excludeData);
+ for (PathWithRequiredFlags excludeData : excludes) {
+ Log.v(TAG_XML_PARSER, " path: " + excludeData.getPath()
+ + " requiredFlags: " + excludeData.getRequiredFlags());
}
}
@@ -531,20 +585,41 @@
}
}
- private Set<String> parseCurrentTagForDomain(XmlPullParser parser,
- Set<String> excludes,
- Map<String, Set<String>> includes,
- String domain)
+ private int getRequiredFlagsFromString(String requiredFlags) {
+ int flags = 0;
+ if (requiredFlags == null || requiredFlags.length() == 0) {
+ // requiredFlags attribute was missing or empty in <include /> tag
+ return flags;
+ }
+ String[] flagsStr = requiredFlags.split("\\|");
+ for (String f : flagsStr) {
+ switch (f) {
+ case FLAG_REQUIRED_CLIENT_SIDE_ENCRYPTION:
+ flags |= BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ break;
+ case FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER:
+ flags |= BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized requiredFlag provided, value: \"" + f + "\"");
+ }
+ }
+ return flags;
+ }
+
+ private Set<PathWithRequiredFlags> parseCurrentTagForDomain(XmlPullParser parser,
+ Set<PathWithRequiredFlags> excludes,
+ Map<String, Set<PathWithRequiredFlags>> includes, String domain)
throws XmlPullParserException {
- if ("include".equals(parser.getName())) {
+ if (TAG_INCLUDE.equals(parser.getName())) {
final String domainToken = getTokenForXmlDomain(domain);
- Set<String> includeSet = includes.get(domainToken);
+ Set<PathWithRequiredFlags> includeSet = includes.get(domainToken);
if (includeSet == null) {
- includeSet = new ArraySet<String>();
+ includeSet = new ArraySet<PathWithRequiredFlags>();
includes.put(domainToken, includeSet);
}
return includeSet;
- } else if ("exclude".equals(parser.getName())) {
+ } else if (TAG_EXCLUDE.equals(parser.getName())) {
return excludes;
} else {
// Unrecognised tag => hard failure.
@@ -589,8 +664,8 @@
/**
*
* @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.
+ * @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) {
@@ -650,15 +725,27 @@
* 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\".");
+ private void validateInnerTagContents(XmlPullParser parser) throws XmlPullParserException {
+ if (parser == null) {
+ return;
}
- 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() + "\"");
+ switch (parser.getName()) {
+ case TAG_INCLUDE:
+ if (parser.getAttributeCount() > 3) {
+ throw new XmlPullParserException("At most 3 tag attributes allowed for "
+ + "\"include\" tag (\"domain\" & \"path\""
+ + " & optional \"requiredFlags\").");
+ }
+ break;
+ case TAG_EXCLUDE:
+ if (parser.getAttributeCount() > 2) {
+ throw new XmlPullParserException("At most 2 tag attributes allowed for "
+ + "\"exclude\" tag (\"domain\" & \"path\".");
+ }
+ break;
+ default:
+ throw new XmlPullParserException("A valid tag is one of \"<include/>\" or" +
+ " \"<exclude/>. You provided \"" + parser.getName() + "\"");
}
}
}
diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
index bc6fc15..58ee7a7 100644
--- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java
+++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
@@ -19,6 +19,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
import android.content.Context;
import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
@@ -43,8 +44,8 @@
private XmlPullParser mXpp;
private Context mContext;
- Map<String, Set<String>> includeMap;
- Set<String> excludesSet;
+ Map<String, Set<PathWithRequiredFlags>> includeMap;
+ Set<PathWithRequiredFlags> excludesSet;
@Override
public void setUp() throws Exception {
@@ -52,8 +53,8 @@
mXpp = mFactory.newPullParser();
mContext = getContext();
- includeMap = new ArrayMap();
- excludesSet = new ArraySet();
+ includeMap = new ArrayMap<>();
+ excludesSet = new ArraySet<>();
}
public void testparseBackupSchemeFromXml_onlyInclude() throws Exception {
@@ -68,11 +69,127 @@
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.FILES_TREE_TOKEN);
+ Set<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
+ PathWithRequiredFlags include = fileDomainIncludes.iterator().next();
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
- fileDomainIncludes.iterator().next());
+ include.getPath());
+ assertEquals("Invalid requireFlags parsed for <include/>", 0, include.getRequiredFlags());
+ }
+
+ public void testparseBackupSchemeFromXml_onlyIncludeRequireEncryptionFlag() throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<include path=\"onlyInclude.txt\" domain=\"file\""
+ + " requireFlags=\"clientSideEncryption\"/>" +
+ "</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<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
+ assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
+ PathWithRequiredFlags include = fileDomainIncludes.iterator().next();
+ assertEquals("Invalid path parsed for <include/>",
+ new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
+ include.getPath());
+ assertEquals("Invalid requireFlags parsed for <include/>",
+ BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED,
+ include.getRequiredFlags());
+ }
+
+ public void testparseBackupSchemeFromXml_onlyIncludeRequireD2DFlag() throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<include path=\"onlyInclude.txt\" domain=\"file\""
+ + " requireFlags=\"deviceToDeviceTransfer\"/>" +
+ "</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<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
+ assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
+ PathWithRequiredFlags include = fileDomainIncludes.iterator().next();
+ assertEquals("Invalid path parsed for <include/>",
+ new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
+ include.getPath());
+ assertEquals("Invalid requireFlags parsed for <include/>",
+ BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER,
+ include.getRequiredFlags());
+ }
+
+ public void testparseBackupSchemeFromXml_onlyIncludeRequireEncryptionAndD2DFlags()
+ throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<include path=\"onlyInclude.txt\" domain=\"file\""
+ + " requireFlags=\"clientSideEncryption|deviceToDeviceTransfer\"/>" +
+ "</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<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
+ assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
+ PathWithRequiredFlags include = fileDomainIncludes.iterator().next();
+ assertEquals("Invalid path parsed for <include/>",
+ new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
+ include.getPath());
+ assertEquals("Invalid requireFlags parsed for <include/>",
+ BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED
+ | BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER,
+ include.getRequiredFlags());
+ }
+
+ public void testparseBackupSchemeFromXml_onlyIncludeRequireD2DFlagAndIngoreGarbage()
+ throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<include path=\"onlyInclude.txt\" domain=\"file\""
+ + " requireFlags=\"deviceToDeviceTransfer|garbageFlag\"/>" +
+ "</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<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
+ assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
+ PathWithRequiredFlags include = fileDomainIncludes.iterator().next();
+ assertEquals("Invalid path parsed for <include/>",
+ new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
+ include.getPath());
+ assertEquals("Invalid requireFlags parsed for <include/>",
+ BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER,
+ include.getRequiredFlags());
+ }
+
+ public void testparseBackupSchemeFromXml_onlyExcludeRequireFlagsNotSupported()
+ throws Exception {
+ mXpp.setInput(new StringReader(
+ "<full-backup-content>" +
+ "<exclude path=\"onlyExclude.txt\" domain=\"file\""
+ + " requireFlags=\"deviceToDeviceTransfer\"/>" +
+ "</full-backup-content>"));
+
+ try {
+ FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
+ bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
+ fail("Having more than 3 attributes in exclude should throw an XmlPullParserException");
+ } catch (XmlPullParserException expected) {}
}
public void testparseBackupSchemeFromXml_onlyExclude() throws Exception {
@@ -88,7 +205,7 @@
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());
+ excludesSet.iterator().next().getPath());
}
public void testparseBackupSchemeFromXml_includeAndExclude() throws Exception {
@@ -101,16 +218,16 @@
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
- Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
+ Set<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_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());
+ fileDomainIncludes.iterator().next().getPath());
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());
+ excludesSet.iterator().next().getPath());
}
public void testparseBackupSchemeFromXml_lotsOfIncludesAndExcludes() throws Exception {
@@ -126,61 +243,74 @@
"<include path=\"include4.xml\" domain=\"sharedpref\"/>" +
"</full-backup-content>"));
-
FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
- Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
+ Set<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_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());
+ fileDomainIncludes.iterator().next().getPath());
- Set<String> databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN);
+ Set<PathWithRequiredFlags> databaseDomainIncludes =
+ includeMap.get(FullBackup.DATABASE_TREE_TOKEN);
+ Set<String> databaseDomainIncludesPaths = new ArraySet<>();
+ for (PathWithRequiredFlags databaseInclude : databaseDomainIncludes) {
+ databaseDomainIncludesPaths.add(databaseInclude.getPath());
+ }
// Three expected here because of "-journal" and "-wal" files
assertEquals("Didn't find expected database domain include.",
3, databaseDomainIncludes.size());
assertTrue("Invalid path parsed for <include/>",
- databaseDomainIncludes.contains(
+ databaseDomainIncludesPaths.contains(
new File(mContext.getDatabasePath("foo").getParentFile(), "include2.txt")
.getCanonicalPath()));
assertTrue("Invalid path parsed for <include/>",
- databaseDomainIncludes.contains(
+ databaseDomainIncludesPaths.contains(
new File(
mContext.getDatabasePath("foo").getParentFile(),
"include2.txt-journal")
.getCanonicalPath()));
assertTrue("Invalid path parsed for <include/>",
- databaseDomainIncludes.contains(
+ databaseDomainIncludesPaths.contains(
new File(
mContext.getDatabasePath("foo").getParentFile(),
"include2.txt-wal")
.getCanonicalPath()));
- List<String> sharedPrefDomainIncludes = new ArrayList<String>(
+ List<PathWithRequiredFlags> sharedPrefDomainIncludes = new ArrayList<PathWithRequiredFlags>(
includeMap.get(FullBackup.SHAREDPREFS_TREE_TOKEN));
- Collections.sort(sharedPrefDomainIncludes);
+ ArrayList<String> sharedPrefDomainIncludesPaths = new ArrayList<>();
+ for (PathWithRequiredFlags sharedPrefInclude : sharedPrefDomainIncludes) {
+ sharedPrefDomainIncludesPaths.add(sharedPrefInclude.getPath());
+ }
+ // Sets are annoying to iterate over b/c order isn't enforced - convert to an array and
+ // sort lexicographically.
+ Collections.sort(sharedPrefDomainIncludesPaths);
assertEquals("Didn't find expected sharedpref domain include.",
3, sharedPrefDomainIncludes.size());
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getSharedPrefsFile("foo").getParentFile(), "include3")
.getCanonicalPath(),
- sharedPrefDomainIncludes.get(0));
+ sharedPrefDomainIncludesPaths.get(0));
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getSharedPrefsFile("foo").getParentFile(), "include3.xml")
.getCanonicalPath(),
- sharedPrefDomainIncludes.get(1));
+ sharedPrefDomainIncludesPaths.get(1));
assertEquals("Invalid path parsed for <include/>",
new File(mContext.getSharedPrefsFile("foo").getParentFile(), "include4.xml")
.getCanonicalPath(),
- sharedPrefDomainIncludes.get(2));
+ sharedPrefDomainIncludesPaths.get(2));
assertEquals("Unexpected number of <exclude/>s", 7, 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);
+ ArrayList<String> arrayedSet = new ArrayList<>();
+ for (PathWithRequiredFlags exclude : excludesSet) {
+ arrayedSet.add(exclude.getPath());
+ }
Collections.sort(arrayedSet);
assertEquals("Invalid path parsed for <exclude/>",
@@ -260,9 +390,10 @@
assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
- Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
+ Set<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
}
+
public void testDoubleDotInPath_isIgnored() throws Exception {
mXpp.setInput(new StringReader(
"<full-backup-content>" +
@@ -274,7 +405,7 @@
assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
- Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
+ Set<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
}
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index d825533..cd8163d 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -58,14 +58,14 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+ $(call all-java-files-under, ../../core/java/android/app/backup) \
+ $(call all-Iaidl-files-under, ../../core/java/android/app/backup) \
../../core/java/android/content/pm/PackageInfo.java \
- ../../core/java/android/app/backup/BackupAgent.java \
- ../../core/java/android/app/backup/BackupDataOutput.java \
- ../../core/java/android/app/backup/FullBackupDataOutput.java \
../../core/java/android/app/IBackupAgent.aidl
LOCAL_AIDL_INCLUDES := \
$(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+ $(call all-Iaidl-files-under, ../../core/java/android/app/backup) \
../../core/java/android/app/IBackupAgent.aidl
LOCAL_STATIC_JAVA_LIBRARIES := \
diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
index d0e6658..60704e7 100644
--- a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
+++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
@@ -112,11 +112,8 @@
ShadowQueuedWork.class
}
)
-@SystemLoaderPackages({"com.android.server.backup"})
+@SystemLoaderPackages({"com.android.server.backup", "android.app.backup"})
@SystemLoaderClasses({
- BackupDataOutput.class,
- FullBackupDataOutput.class,
- BackupAgent.class,
IBackupTransport.class,
IBackupAgent.class,
PackageInfo.class