diff options
| -rw-r--r-- | core/java/android/app/backup/BackupAgent.java | 60 | ||||
| -rw-r--r-- | core/java/android/app/backup/FullBackup.java | 189 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/backup/FullBackupTest.java | 183 |
3 files changed, 336 insertions, 96 deletions
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index d36a794ac046..d1c957b8fedc 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -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()) { diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index a5dd5bd30d63..fb1c2d085df6 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -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. + */ + 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<String> mExcludes; + 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() + "\""); } } } diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java index bc6fc15db163..58ee7a783589 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 @@ 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()); - - Set<String> databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN); + fileDomainIncludes.iterator().next().getPath()); + + 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); } |