Parse include / exclude config specified by dataExtractionRules

1. Parse the new config and prefer it over the old config specified by
android:fullBackupContent.

2. Add a new parameter to LocalTransportParameters to allow testing this
functionality in CTS.

Bug: 180522596
Test: atest FullBackupRulesHostSideTest
Change-Id: If18edfcdb2b9637d380251f48fdbc56cca6af3ec
This commit is contained in:
Ruslan Tkhakokhov
2021-02-17 20:22:12 +00:00
parent 2409543ce8
commit 6751d29041
2 changed files with 107 additions and 18 deletions

View File

@@ -19,8 +19,10 @@ package android.app.backup;
import static android.app.backup.BackupManager.OperationType;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.os.ParcelFileDescriptor;
@@ -33,6 +35,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -93,6 +96,15 @@ public class FullBackup {
public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION =
"fakeClientSideEncryption";
@StringDef({
ConfigSection.CLOUD_BACKUP,
ConfigSection.DEVICE_TRANSFER
})
private @interface ConfigSection {
String CLOUD_BACKUP = "cloud-backup";
String DEVICE_TRANSFER = "device-transfer";
}
/**
* Identify {@link BackupScheme} object by package and operation type
* (see {@link OperationType}) it corresponds to.
@@ -273,6 +285,7 @@ public class FullBackup {
private final static String TAG_INCLUDE = "include";
private final static String TAG_EXCLUDE = "exclude";
final int mDataExtractionRules;
final int mFullBackupContent;
@OperationType final int mOperationType;
final PackageManager mPackageManager;
@@ -394,7 +407,10 @@ public class FullBackup {
ArraySet<PathWithRequiredFlags> mExcludes;
BackupScheme(Context context, @OperationType int operationType) {
mFullBackupContent = context.getApplicationInfo().fullBackupContent;
ApplicationInfo applicationInfo = context.getApplicationInfo();
mDataExtractionRules = applicationInfo.dataExtractionRulesRes;
mFullBackupContent = applicationInfo.fullBackupContent;
mOperationType = operationType;
mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
mPackageManager = context.getPackageManager();
@@ -468,34 +484,98 @@ public class FullBackup {
mIncludes = new ArrayMap<String, Set<PathWithRequiredFlags>>();
mExcludes = new ArraySet<PathWithRequiredFlags>();
if (mFullBackupContent == 0) {
// android:fullBackupContent="true" which means that we'll do everything.
if (mFullBackupContent == 0 && mDataExtractionRules == 0) {
// No scheme specified via either new or legacy config, will copy everything.
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER, "android:fullBackupContent - \"true\"");
}
} else {
// android:fullBackupContent="@xml/some_resource".
// Scheme is present.
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
"android:fullBackupContent - found xml resource");
Log.v(FullBackup.TAG_XML_PARSER, "Found xml scheme: "
+ "android:fullBackupContent=" + mFullBackupContent
+ "; android:dataExtractionRules=" + mDataExtractionRules);
}
XmlResourceParser parser = null;
try {
parser = mPackageManager
.getResourcesForApplication(mPackageName)
.getXml(mFullBackupContent);
parseBackupSchemeFromXmlLocked(parser, mExcludes, mIncludes);
parseSchemeForOperationType(mOperationType);
} catch (PackageManager.NameNotFoundException e) {
// Throw it as an IOException
throw new IOException(e);
} finally {
if (parser != null) {
parser.close();
}
}
}
}
private void parseSchemeForOperationType(@OperationType int operationType)
throws PackageManager.NameNotFoundException, IOException, XmlPullParserException {
String configSection = getConfigSectionForOperationType(operationType);
if (configSection == null) {
Slog.w(TAG, "Given operation type isn't supported by backup scheme: "
+ operationType);
return;
}
if (mDataExtractionRules != 0) {
// New config is present. Use it if it has configuration for this operation
// type.
try (XmlResourceParser parser = getParserForResource(mDataExtractionRules)) {
parseNewBackupSchemeFromXmlLocked(parser, configSection, mExcludes, mIncludes);
}
if (!mExcludes.isEmpty() || !mIncludes.isEmpty()) {
// Found configuration in the new config, we will use it.
return;
}
}
// TODO(b/180523564): Ignore the old config for apps targeting Android S+ during D2D.
if (mFullBackupContent != 0) {
// Fall back to the old config.
try (XmlResourceParser parser = getParserForResource(mFullBackupContent)) {
parseBackupSchemeFromXmlLocked(parser, mExcludes, mIncludes);
}
}
}
@Nullable
private String getConfigSectionForOperationType(@OperationType int operationType) {
switch (operationType) {
case OperationType.BACKUP:
return ConfigSection.CLOUD_BACKUP;
case OperationType.MIGRATION:
return ConfigSection.DEVICE_TRANSFER;
default:
return null;
}
}
private XmlResourceParser getParserForResource(int resourceId)
throws PackageManager.NameNotFoundException {
return mPackageManager
.getResourcesForApplication(mPackageName)
.getXml(resourceId);
}
private void parseNewBackupSchemeFromXmlLocked(XmlPullParser parser,
@ConfigSection String configSection,
Set<PathWithRequiredFlags> excludes,
Map<String, Set<PathWithRequiredFlags>> includes)
throws IOException, XmlPullParserException {
verifyTopLevelTag(parser, "data-extraction-rules");
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event != XmlPullParser.START_TAG || !configSection.equals(parser.getName())) {
continue;
}
// TODO(b/180523028): Parse required attributes for rules (e.g. encryption).
parseRules(parser, excludes, includes, Optional.of(0), configSection);
}
logParsingResults(excludes, includes);
}
@VisibleForTesting
public void parseBackupSchemeFromXmlLocked(XmlPullParser parser,
Set<PathWithRequiredFlags> excludes,
@@ -503,7 +583,7 @@ public class FullBackup {
throws IOException, XmlPullParserException {
verifyTopLevelTag(parser, "full-backup-content");
parseRules(parser, excludes, includes, Optional.empty());
parseRules(parser, excludes, includes, Optional.empty(), "full-backup-content");
logParsingResults(excludes, includes);
}
@@ -532,10 +612,12 @@ public class FullBackup {
private void parseRules(XmlPullParser parser,
Set<PathWithRequiredFlags> excludes,
Map<String, Set<PathWithRequiredFlags>> includes,
Optional<Integer> maybeRequiredFlags)
Optional<Integer> maybeRequiredFlags,
String endingTag)
throws IOException, XmlPullParserException {
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
&& !parser.getName().equals(endingTag)) {
switch (event) {
case XmlPullParser.START_TAG:
validateInnerTagContents(parser);

View File

@@ -27,9 +27,11 @@ public class LocalTransportParameters extends KeyValueSettingObserver {
private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS;
private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only";
private static final String KEY_IS_DEVICE_TRANSFER = "is_device_transfer";
private boolean mFakeEncryptionFlag;
private boolean mIsNonIncrementalOnly;
private boolean mIsDeviceTransfer;
public LocalTransportParameters(Handler handler, ContentResolver resolver) {
super(handler, resolver, Settings.Secure.getUriFor(SETTING));
@@ -43,6 +45,10 @@ public class LocalTransportParameters extends KeyValueSettingObserver {
return mIsNonIncrementalOnly;
}
boolean isDeviceTransfer() {
return mIsDeviceTransfer;
}
public String getSettingValue(ContentResolver resolver) {
return Settings.Secure.getString(resolver, SETTING);
}
@@ -50,5 +56,6 @@ public class LocalTransportParameters extends KeyValueSettingObserver {
public void update(KeyValueListParser parser) {
mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false);
mIsNonIncrementalOnly = parser.getBoolean(KEY_NON_INCREMENTAL_ONLY, false);
mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false);
}
}