Merge "Extend XML parser to allow optional requiredFlags attribute for <include /> rules in <full-backup-content> specification"
This commit is contained in:
committed by
Android (Google) Code Review
commit
df0d0f1a84
@@ -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()) {
|
||||
|
||||
@@ -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() + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user