Merge "Extend XML parser to allow optional requiredFlags attribute for <include /> rules in <full-backup-content> specification"

This commit is contained in:
TreeHugger Robot
2018-02-20 19:20:00 +00:00
committed by Android (Google) Code Review
3 changed files with 335 additions and 95 deletions

View File

@@ -18,6 +18,7 @@ package android.app.backup;
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 @@ public abstract class BackupAgent extends ContextWrapper {
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 @@ public abstract class BackupAgent extends ContextWrapper {
/**
* 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 @@ public abstract class BackupAgent extends ContextWrapper {
} 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 @@ public abstract class BackupAgent extends ContextWrapper {
* @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 @@ public abstract class BackupAgent extends ContextWrapper {
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 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
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 @@ public abstract class BackupAgent extends ContextWrapper {
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 @@ public abstract class BackupAgent extends ContextWrapper {
// 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 @@ public abstract class BackupAgent extends ContextWrapper {
* @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()) {

View File

@@ -82,6 +82,9 @@ public class FullBackup {
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 @@ public class FullBackup {
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 @@ public class FullBackup {
}
/**
* 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 @@ public class FullBackup {
}
/**
* @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 @@ public class FullBackup {
}
/**
* @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 @@ public class FullBackup {
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 @@ public class FullBackup {
@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 @@ public class FullBackup {
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 @@ public class FullBackup {
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 @@ public class FullBackup {
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 @@ public class FullBackup {
!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 @@ public class FullBackup {
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 @@ public class FullBackup {
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 @@ public class FullBackup {
}
}
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 @@ public class FullBackup {
/**
*
* @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 @@ public class FullBackup {
* 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() + "\"");
}
}
}

View File

@@ -19,6 +19,7 @@ package android.app.backup;
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 @@ public class FullBackupTest extends AndroidTestCase {
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 @@ public class FullBackupTest extends AndroidTestCase {
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 @@ public class FullBackupTest extends AndroidTestCase {
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 @@ public class FullBackupTest extends AndroidTestCase {
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 @@ public class FullBackupTest extends AndroidTestCase {
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 @@ public class FullBackupTest extends AndroidTestCase {
"<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 @@ public class FullBackupTest extends AndroidTestCase {
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 @@ public class FullBackupTest extends AndroidTestCase {
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);
}