summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Michal Karpinski <mkarpinski@google.com> 2018-02-19 13:55:23 +0000
committer Bernardo Rufino <brufino@google.com> 2018-02-22 13:18:02 +0000
commitb5e0931dcd0745dba4633dce9853114b1ee15a46 (patch)
treefa49d31710dd1906f5c6ca4c35c69da40d6fa548
parent72b7966b06b6aea1bc0f558f655e7510e6d01fc5 (diff)
Extend XML parser to allow optional requiredFlags attribute
for <include /> rules in <full-backup-content> specification Give an app developer the option to include files based on the transport flags exposed by the transport. This allows conditionally including files as long as the transport identifies itself as for instance encrypted or device-to-device. Extend the parsing mechanism to read optional requiredFlags attributes, and extend existing structures to encompass that data for BackupAgent to retrieve and act on it based on FullBackupDataOutput#getTransportFlags(). -- Changes in robotests/ The old version of this CL (that already got reverted) broke our Robolectric suite because it added an inner class to FullBackup and a dependency on it from BackupAgent. FullBackup wasn't being built from Android tree (instead it was in a prebuilt Robolectric snapshot jar of the framework) but BackupAgent was, which resulted in not finding the inner class. So, also in this CL. Changing our tests to include everything under platform/base/core/java/android/app/backup from Android tree. `m -j RunFrameworksServicesRoboTests` is green now Bug: 72484288 Test: m -j RunFrameworksServicesRoboTests Test: runtest frameworks-core -c android.app.backup.FullBackupTest Test: make cts -j40 && cts-tradefed run cts -m CtsBackupHostTestCases -t android.cts.backup.FullbackupRulesHostSideTest Change-Id: Ideaed59f8337257aa6a882ff0ce80c170b17d55e
-rw-r--r--core/java/android/app/backup/BackupAgent.java60
-rw-r--r--core/java/android/app/backup/FullBackup.java189
-rw-r--r--core/tests/coretests/src/android/app/backup/FullBackupTest.java183
-rw-r--r--services/robotests/Android.mk6
-rw-r--r--services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java5
5 files changed, 340 insertions, 103 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);
}
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index d825533e92f5..cd8163dd4852 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -58,14 +58,14 @@ INTERNAL_BACKUP := ../../core/java/com/android/internal/backup
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+ $(call all-java-files-under, ../../core/java/android/app/backup) \
+ $(call all-Iaidl-files-under, ../../core/java/android/app/backup) \
../../core/java/android/content/pm/PackageInfo.java \
- ../../core/java/android/app/backup/BackupAgent.java \
- ../../core/java/android/app/backup/BackupDataOutput.java \
- ../../core/java/android/app/backup/FullBackupDataOutput.java \
../../core/java/android/app/IBackupAgent.aidl
LOCAL_AIDL_INCLUDES := \
$(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+ $(call all-Iaidl-files-under, ../../core/java/android/app/backup) \
../../core/java/android/app/IBackupAgent.aidl
LOCAL_STATIC_JAVA_LIBRARIES := \
diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
index d0e665890b5e..60704e74f177 100644
--- a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
+++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
@@ -112,11 +112,8 @@ import java.util.stream.Stream;
ShadowQueuedWork.class
}
)
-@SystemLoaderPackages({"com.android.server.backup"})
+@SystemLoaderPackages({"com.android.server.backup", "android.app.backup"})
@SystemLoaderClasses({
- BackupDataOutput.class,
- FullBackupDataOutput.class,
- BackupAgent.class,
IBackupTransport.class,
IBackupAgent.class,
PackageInfo.class