Loading core/java/android/app/backup/BackupAnnotations.java +4 −1 Original line number Diff line number Diff line Loading @@ -50,7 +50,8 @@ public class BackupAnnotations { @IntDef({ BackupDestination.CLOUD, BackupDestination.DEVICE_TRANSFER, BackupDestination.ADB_BACKUP BackupDestination.ADB_BACKUP, BackupDestination.CROSS_PLATFORM_TRANSFER, }) public @interface BackupDestination { // A cloud backup. Loading @@ -59,5 +60,7 @@ public class BackupAnnotations { int DEVICE_TRANSFER = 1; // An adb backup. int ADB_BACKUP = 2; // A device migration to or from another platform. int CROSS_PLATFORM_TRANSFER = 3; } } core/java/android/app/backup/FullBackup.java +152 −31 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.backup.Flags; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; Loading Loading @@ -98,11 +99,16 @@ public class FullBackup { public static final String FLAG_REQUIRED_CLIENT_SIDE_ENCRYPTION = "clientSideEncryption"; public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer"; public static final String FLAG_REQUIRED_PLATFORM = "platform"; public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION = "fakeClientSideEncryption"; private static final String FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES = "disableIfNoEncryptionCapabilities"; private static final String FLAG_PLATFORM_SPECIFIC_PARAMS_BUNDLE_ID = "bundleId"; private static final String FLAG_PLATFORM_SPECIFIC_PARAMS_TEAM_ID = "teamId"; private static final String FLAG_PLATFORM_SPECIFIC_PARAMS_CONTENT_VERSION = "contentVersion"; /** * When this change is enabled, include / exclude rules specified via {@code * android:fullBackupContent} are ignored during D2D transfers. Loading @@ -112,10 +118,15 @@ public class FullBackup { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) private static final long IGNORE_FULL_BACKUP_CONTENT_IN_D2D = 180523564L; @StringDef({ConfigSection.CLOUD_BACKUP, ConfigSection.DEVICE_TRANSFER}) @StringDef({ ConfigSection.CLOUD_BACKUP, ConfigSection.DEVICE_TRANSFER, ConfigSection.CROSS_PLATFORM_TRANSFER, }) @interface ConfigSection { String CLOUD_BACKUP = "cloud-backup"; String DEVICE_TRANSFER = "device-transfer"; String CROSS_PLATFORM_TRANSFER = "cross-platform-transfer"; } /** Loading Loading @@ -176,9 +187,16 @@ public class FullBackup { } public static BackupScheme getBackupSchemeForTest(Context context) { BackupScheme testing = new BackupScheme(context, BackupDestination.CLOUD); return getBackupSchemeForTest(context, BackupDestination.CLOUD); } /** Returns a BackupScheme for testing only. */ public static BackupScheme getBackupSchemeForTest( Context context, @BackupDestination int backupDestination) { BackupScheme testing = new BackupScheme(context, backupDestination); testing.mExcludes = new ArraySet(); testing.mIncludes = new ArrayMap(); testing.mPlatformSpecificParams = new ArrayMap(); return testing; } Loading Loading @@ -300,6 +318,7 @@ public class FullBackup { private static final String TAG_INCLUDE = "include"; private static final String TAG_EXCLUDE = "exclude"; private static final String TAG_PLATFORM_SPECIFIC_PARAMS = "platform-specific-params"; final int mDataExtractionRules; final int mFullBackupContent; Loading Loading @@ -409,6 +428,31 @@ public class FullBackup { } } /** Represents platform specific parameters for cross-platform transfers. */ public static class PlatformSpecificParams { private final String mBundleId; private final String mTeamId; private final String mContentVersion; public PlatformSpecificParams(String bundleId, String teamId, String contentVersion) { this.mBundleId = bundleId; this.mTeamId = teamId; this.mContentVersion = contentVersion; } public String getBundleId() { return mBundleId; } public String getTeamId() { return mTeamId; } public String getContentVersion() { return mContentVersion; } } /** * 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 Loading @@ -423,6 +467,13 @@ public class FullBackup { */ ArraySet<PathWithRequiredFlags> mExcludes; /** * A map of platform -> platform specific params. The presence of an entry indicates that * export to and import from that platform is supported. The platform specific params are * used to identify the corresponding app on the other platform. */ Map<String, PlatformSpecificParams> mPlatformSpecificParams; BackupScheme(Context context, @BackupDestination int backupDestination) { ApplicationInfo applicationInfo = context.getApplicationInfo(); Loading Loading @@ -594,7 +645,11 @@ public class FullBackup { try (XmlResourceParser parser = getParserForResource(mDataExtractionRules)) { isSectionPresent = parseNewBackupSchemeFromXmlLocked( parser, configSection, mExcludes, mIncludes); parser, configSection, mExcludes, mIncludes, mPlatformSpecificParams); } if (isSectionPresent) { // Found the relevant section in the new config, we will use it. Loading Loading @@ -640,11 +695,13 @@ public class FullBackup { XmlPullParser parser, @ConfigSection String configSection, Set<PathWithRequiredFlags> excludes, Map<String, Set<PathWithRequiredFlags>> includes) Map<String, Set<PathWithRequiredFlags>> includes, Map<String, PlatformSpecificParams> platformSpecificParams) throws IOException, XmlPullParserException { verifyTopLevelTag(parser, "data-extraction-rules"); boolean isSectionPresent = false; String platform = null; int event; while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { Loading @@ -654,8 +711,21 @@ public class FullBackup { isSectionPresent = true; if (Flags.enableCrossPlatformTransfer() && ConfigSection.CROSS_PLATFORM_TRANSFER.equals(configSection)) { platform = parser.getAttributeValue(/* namespace= */ null, FLAG_REQUIRED_PLATFORM); } parseRequiredTransportFlags(parser, configSection); parseRules(parser, excludes, includes, Optional.of(0), configSection); parseRules( parser, excludes, includes, platformSpecificParams, Optional.of(0), configSection, platform); } logParsingResults(excludes, includes); Loading Loading @@ -683,7 +753,14 @@ public class FullBackup { throws IOException, XmlPullParserException { verifyTopLevelTag(parser, "full-backup-content"); parseRules(parser, excludes, includes, Optional.empty(), "full-backup-content"); parseRules( parser, excludes, includes, /* platformSpecificParamsMap= */ new ArrayMap<>(), /* maybeRequiredFlags= */ Optional.empty(), "full-backup-content", /* platform= */ null); logParsingResults(excludes, includes); } Loading Loading @@ -718,19 +795,46 @@ public class FullBackup { XmlPullParser parser, Set<PathWithRequiredFlags> excludes, Map<String, Set<PathWithRequiredFlags>> includes, Map<String, PlatformSpecificParams> platformSpecificParamsMap, Optional<Integer> maybeRequiredFlags, String endingTag) String configSection, String platform) throws IOException, XmlPullParserException { int event; while ((event = parser.next()) != XmlPullParser.END_DOCUMENT && !parser.getName().equals(endingTag)) { && !parser.getName().equals(configSection)) { if (event != XmlPullParser.START_TAG) { continue; } validateInnerTagContents(parser); validateInnerTagContents(parser, configSection); if (Flags.enableCrossPlatformTransfer() && ConfigSection.CROSS_PLATFORM_TRANSFER.equals(configSection) && parser.getName().equals(TAG_PLATFORM_SPECIFIC_PARAMS)) { parsePlatformSpecificParamsTag(parser, platform, platformSpecificParamsMap); } else { parseIncludeExcludeTag(parser, excludes, includes, maybeRequiredFlags); } } } private void parsePlatformSpecificParamsTag( XmlPullParser parser, String platform, Map<String, PlatformSpecificParams> platformSpecificParamsMap) { if (TextUtils.isEmpty(platform)) { // Ignore the platform specific parameters if platform wasn't specified. return; } String bundleId = parser.getAttributeValue(null, FLAG_PLATFORM_SPECIFIC_PARAMS_BUNDLE_ID); String teamId = parser.getAttributeValue(null, FLAG_PLATFORM_SPECIFIC_PARAMS_TEAM_ID); String contentVersion = parser.getAttributeValue(null, FLAG_PLATFORM_SPECIFIC_PARAMS_CONTENT_VERSION); platformSpecificParamsMap.put( platform, new PlatformSpecificParams(bundleId, teamId, contentVersion)); } private void parseIncludeExcludeTag( XmlPullParser parser, Loading Loading @@ -1041,32 +1145,49 @@ 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 == null) { private void validateInnerTagContents(XmlPullParser parser, String configSection) throws XmlPullParserException { if (parser == null || parser.getName() == null) { return; } switch (parser.getName()) { case TAG_INCLUDE: if (parser.getName().equals(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: } else if (parser.getName().equals(TAG_EXCLUDE)) { if (parser.getAttributeCount() > 2) { throw new XmlPullParserException( "At most 2 tag attributes allowed for " + "\"exclude\" tag (\"domain\" & \"path\"."); } break; default: } else if (Flags.enableCrossPlatformTransfer() && configSection.equals(ConfigSection.CROSS_PLATFORM_TRANSFER) && parser.getName().equals(TAG_PLATFORM_SPECIFIC_PARAMS)) { if (parser.getAttributeCount() > 3) { throw new XmlPullParserException( "At most 3 tag attributes allowed for\"platform-specific-params\"" + " tag (\"bundleId\" & \"teamId\" &" + " \"contentVersion\"."); } } else { if (Flags.enableCrossPlatformTransfer() && configSection.equals(ConfigSection.CROSS_PLATFORM_TRANSFER)) { throw new XmlPullParserException( "A valid tag is one of \"<include/>\" or" + " \"<exclude/>. You provided \"" "A valid tag is one of \"<include/>\" or \"<exclude/>\" or" + " \"<platform-specific-params/>\". You provided \"" + parser.getName() + "\""); } else { throw new XmlPullParserException( "A valid tag is one of \"<include/>\" or \"<exclude/>\". You provided" + " \"" + parser.getName() + "\""); } } } } Loading core/tests/coretests/src/android/app/backup/FullBackupTest.java +153 −2 Original line number Diff line number Diff line Loading @@ -17,14 +17,23 @@ package android.app.backup; import static android.app.backup.FullBackup.ConfigSection.CLOUD_BACKUP; import static android.app.backup.FullBackup.ConfigSection.CROSS_PLATFORM_TRANSFER; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags; import android.app.backup.FullBackup.BackupScheme.PlatformSpecificParams; import android.content.Context; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.ArraySet; Loading @@ -32,7 +41,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.server.backup.Flags; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParser; Loading @@ -50,12 +62,16 @@ import java.util.Set; @LargeTest @RunWith(AndroidJUnit4.class) public class FullBackupTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private XmlPullParserFactory mFactory; private XmlPullParser mXpp; private Context mContext; Map<String, Set<PathWithRequiredFlags>> includeMap; Set<PathWithRequiredFlags> excludesSet; Map<String, PlatformSpecificParams> mPlatformSpecificParamsMap; @Before public void setUp() throws Exception { Loading @@ -65,6 +81,7 @@ public class FullBackupTest { includeMap = new ArrayMap<>(); excludesSet = new ArraySet<>(); mPlatformSpecificParamsMap = new ArrayMap<>(); } @Test Loading Loading @@ -498,7 +515,7 @@ public class FullBackupTest { FullBackup.BackupScheme backupScheme = FullBackup.getBackupSchemeForTest(mContext); boolean result = backupScheme.parseNewBackupSchemeFromXmlLocked( mXpp, CLOUD_BACKUP, excludesSet, includeMap); mXpp, CLOUD_BACKUP, excludesSet, includeMap, mPlatformSpecificParamsMap); assertTrue(result); } Loading @@ -516,7 +533,7 @@ public class FullBackupTest { FullBackup.BackupScheme backupScheme = FullBackup.getBackupSchemeForTest(mContext); boolean result = backupScheme.parseNewBackupSchemeFromXmlLocked( mXpp, CLOUD_BACKUP, excludesSet, includeMap); mXpp, CLOUD_BACKUP, excludesSet, includeMap, mPlatformSpecificParamsMap); assertTrue(result); assertEquals( Loading Loading @@ -556,4 +573,138 @@ public class FullBackupTest { assertEquals("Didn't throw away invalid path containing \"//\".", 0, excludesSet.size()); } @Test @EnableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseOldBackupSchemeFromXml_flagOn_crossPlatformTransferSection_throws() throws Exception { mXpp.setInput( new StringReader( "<full-backup-content><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0\" contentVersion=\"1.0\" />" + "</full-backup-content>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext); assertThrows( "Unexpected platform-specific-params in old scheme should throw an exception", XmlPullParserException.class, () -> bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap)); } @Test @DisableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseNewBackupSchemeFromXml_flagOff_crossPlatformTransferSection_throws() throws Exception { mXpp.setInput( new StringReader( "<data-extraction-rules><cross-platform-transfer platform=\"ios\"><include" + " path=\"file.txt\" domain=\"file\" /><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0123abcd\"" + " contentVersion=\"1.0\" /></cross-platform-transfer>" + "</data-extraction-rules>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest( mContext, BackupAnnotations.BackupDestination.CROSS_PLATFORM_TRANSFER); assertThrows( "cross-platform-transfer in backup rules when feature is disabled should throw an" + " exception", XmlPullParserException.class, () -> bs.parseNewBackupSchemeFromXmlLocked( mXpp, CROSS_PLATFORM_TRANSFER, excludesSet, includeMap, mPlatformSpecificParamsMap)); } @Test @EnableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseNewBackupSchemeFromXml_flagOn_crossPlatformTransferSection_isParsed() throws Exception { assumeTrue(Flags.enableCrossPlatformTransfer()); mXpp.setInput( new StringReader( "<data-extraction-rules><cross-platform-transfer platform=\"ios\"><include" + " path=\"file.txt\" domain=\"file\" /><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0123abcd\"" + " contentVersion=\"1.0\" /></cross-platform-transfer>" + "</data-extraction-rules>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest( mContext, BackupAnnotations.BackupDestination.CROSS_PLATFORM_TRANSFER); boolean result = bs.parseNewBackupSchemeFromXmlLocked( mXpp, CROSS_PLATFORM_TRANSFER, excludesSet, includeMap, mPlatformSpecificParamsMap); assertTrue(result); assertThat(mPlatformSpecificParamsMap).containsKey("ios"); PlatformSpecificParams actual = mPlatformSpecificParamsMap.get("ios"); assertThat(actual.getBundleId()).isEqualTo("com.example.app"); assertThat(actual.getTeamId()).isEqualTo("0123abcd"); assertThat(actual.getContentVersion()).isEqualTo("1.0"); } @Test @EnableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseNewBackupSchemeFromXml_flagOn_missingPlatform_isParsedButIgnored() throws Exception { assumeTrue(Flags.enableCrossPlatformTransfer()); mXpp.setInput( new StringReader( "<data-extraction-rules><cross-platform-transfer><include" + " path=\"file.txt\" domain=\"file\" /><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0123abcd\"" + " contentVersion=\"1.0\" /></cross-platform-transfer>" + "</data-extraction-rules>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest( mContext, BackupAnnotations.BackupDestination.CROSS_PLATFORM_TRANSFER); boolean result = bs.parseNewBackupSchemeFromXmlLocked( mXpp, CROSS_PLATFORM_TRANSFER, excludesSet, includeMap, mPlatformSpecificParamsMap); assertTrue(result); assertThat(mPlatformSpecificParamsMap).isEmpty(); } @Test @EnableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseNewBackupSchemeFromXml_flagOn_invalidAttributeCount_throws() throws Exception { assumeTrue(Flags.enableCrossPlatformTransfer()); mXpp.setInput( new StringReader( "<data-extraction-rules><cross-platform-transfer platform=\"ios\"><include" + " path=\"file.txt\" domain=\"file\" /><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0123abcd\"" + " contentVersion=\"1.0\" platform=\"ios\" />" + "</cross-platform-transfer>" + "</data-extraction-rules>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest( mContext, BackupAnnotations.BackupDestination.CROSS_PLATFORM_TRANSFER); assertThrows( "invalid number of attributes in platform-specific-params should throw", XmlPullParserException.class, () -> bs.parseNewBackupSchemeFromXmlLocked( mXpp, CROSS_PLATFORM_TRANSFER, excludesSet, includeMap, mPlatformSpecificParamsMap)); } } Loading
core/java/android/app/backup/BackupAnnotations.java +4 −1 Original line number Diff line number Diff line Loading @@ -50,7 +50,8 @@ public class BackupAnnotations { @IntDef({ BackupDestination.CLOUD, BackupDestination.DEVICE_TRANSFER, BackupDestination.ADB_BACKUP BackupDestination.ADB_BACKUP, BackupDestination.CROSS_PLATFORM_TRANSFER, }) public @interface BackupDestination { // A cloud backup. Loading @@ -59,5 +60,7 @@ public class BackupAnnotations { int DEVICE_TRANSFER = 1; // An adb backup. int ADB_BACKUP = 2; // A device migration to or from another platform. int CROSS_PLATFORM_TRANSFER = 3; } }
core/java/android/app/backup/FullBackup.java +152 −31 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.backup.Flags; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; Loading Loading @@ -98,11 +99,16 @@ public class FullBackup { public static final String FLAG_REQUIRED_CLIENT_SIDE_ENCRYPTION = "clientSideEncryption"; public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer"; public static final String FLAG_REQUIRED_PLATFORM = "platform"; public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION = "fakeClientSideEncryption"; private static final String FLAG_DISABLE_IF_NO_ENCRYPTION_CAPABILITIES = "disableIfNoEncryptionCapabilities"; private static final String FLAG_PLATFORM_SPECIFIC_PARAMS_BUNDLE_ID = "bundleId"; private static final String FLAG_PLATFORM_SPECIFIC_PARAMS_TEAM_ID = "teamId"; private static final String FLAG_PLATFORM_SPECIFIC_PARAMS_CONTENT_VERSION = "contentVersion"; /** * When this change is enabled, include / exclude rules specified via {@code * android:fullBackupContent} are ignored during D2D transfers. Loading @@ -112,10 +118,15 @@ public class FullBackup { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) private static final long IGNORE_FULL_BACKUP_CONTENT_IN_D2D = 180523564L; @StringDef({ConfigSection.CLOUD_BACKUP, ConfigSection.DEVICE_TRANSFER}) @StringDef({ ConfigSection.CLOUD_BACKUP, ConfigSection.DEVICE_TRANSFER, ConfigSection.CROSS_PLATFORM_TRANSFER, }) @interface ConfigSection { String CLOUD_BACKUP = "cloud-backup"; String DEVICE_TRANSFER = "device-transfer"; String CROSS_PLATFORM_TRANSFER = "cross-platform-transfer"; } /** Loading Loading @@ -176,9 +187,16 @@ public class FullBackup { } public static BackupScheme getBackupSchemeForTest(Context context) { BackupScheme testing = new BackupScheme(context, BackupDestination.CLOUD); return getBackupSchemeForTest(context, BackupDestination.CLOUD); } /** Returns a BackupScheme for testing only. */ public static BackupScheme getBackupSchemeForTest( Context context, @BackupDestination int backupDestination) { BackupScheme testing = new BackupScheme(context, backupDestination); testing.mExcludes = new ArraySet(); testing.mIncludes = new ArrayMap(); testing.mPlatformSpecificParams = new ArrayMap(); return testing; } Loading Loading @@ -300,6 +318,7 @@ public class FullBackup { private static final String TAG_INCLUDE = "include"; private static final String TAG_EXCLUDE = "exclude"; private static final String TAG_PLATFORM_SPECIFIC_PARAMS = "platform-specific-params"; final int mDataExtractionRules; final int mFullBackupContent; Loading Loading @@ -409,6 +428,31 @@ public class FullBackup { } } /** Represents platform specific parameters for cross-platform transfers. */ public static class PlatformSpecificParams { private final String mBundleId; private final String mTeamId; private final String mContentVersion; public PlatformSpecificParams(String bundleId, String teamId, String contentVersion) { this.mBundleId = bundleId; this.mTeamId = teamId; this.mContentVersion = contentVersion; } public String getBundleId() { return mBundleId; } public String getTeamId() { return mTeamId; } public String getContentVersion() { return mContentVersion; } } /** * 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 Loading @@ -423,6 +467,13 @@ public class FullBackup { */ ArraySet<PathWithRequiredFlags> mExcludes; /** * A map of platform -> platform specific params. The presence of an entry indicates that * export to and import from that platform is supported. The platform specific params are * used to identify the corresponding app on the other platform. */ Map<String, PlatformSpecificParams> mPlatformSpecificParams; BackupScheme(Context context, @BackupDestination int backupDestination) { ApplicationInfo applicationInfo = context.getApplicationInfo(); Loading Loading @@ -594,7 +645,11 @@ public class FullBackup { try (XmlResourceParser parser = getParserForResource(mDataExtractionRules)) { isSectionPresent = parseNewBackupSchemeFromXmlLocked( parser, configSection, mExcludes, mIncludes); parser, configSection, mExcludes, mIncludes, mPlatformSpecificParams); } if (isSectionPresent) { // Found the relevant section in the new config, we will use it. Loading Loading @@ -640,11 +695,13 @@ public class FullBackup { XmlPullParser parser, @ConfigSection String configSection, Set<PathWithRequiredFlags> excludes, Map<String, Set<PathWithRequiredFlags>> includes) Map<String, Set<PathWithRequiredFlags>> includes, Map<String, PlatformSpecificParams> platformSpecificParams) throws IOException, XmlPullParserException { verifyTopLevelTag(parser, "data-extraction-rules"); boolean isSectionPresent = false; String platform = null; int event; while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { Loading @@ -654,8 +711,21 @@ public class FullBackup { isSectionPresent = true; if (Flags.enableCrossPlatformTransfer() && ConfigSection.CROSS_PLATFORM_TRANSFER.equals(configSection)) { platform = parser.getAttributeValue(/* namespace= */ null, FLAG_REQUIRED_PLATFORM); } parseRequiredTransportFlags(parser, configSection); parseRules(parser, excludes, includes, Optional.of(0), configSection); parseRules( parser, excludes, includes, platformSpecificParams, Optional.of(0), configSection, platform); } logParsingResults(excludes, includes); Loading Loading @@ -683,7 +753,14 @@ public class FullBackup { throws IOException, XmlPullParserException { verifyTopLevelTag(parser, "full-backup-content"); parseRules(parser, excludes, includes, Optional.empty(), "full-backup-content"); parseRules( parser, excludes, includes, /* platformSpecificParamsMap= */ new ArrayMap<>(), /* maybeRequiredFlags= */ Optional.empty(), "full-backup-content", /* platform= */ null); logParsingResults(excludes, includes); } Loading Loading @@ -718,19 +795,46 @@ public class FullBackup { XmlPullParser parser, Set<PathWithRequiredFlags> excludes, Map<String, Set<PathWithRequiredFlags>> includes, Map<String, PlatformSpecificParams> platformSpecificParamsMap, Optional<Integer> maybeRequiredFlags, String endingTag) String configSection, String platform) throws IOException, XmlPullParserException { int event; while ((event = parser.next()) != XmlPullParser.END_DOCUMENT && !parser.getName().equals(endingTag)) { && !parser.getName().equals(configSection)) { if (event != XmlPullParser.START_TAG) { continue; } validateInnerTagContents(parser); validateInnerTagContents(parser, configSection); if (Flags.enableCrossPlatformTransfer() && ConfigSection.CROSS_PLATFORM_TRANSFER.equals(configSection) && parser.getName().equals(TAG_PLATFORM_SPECIFIC_PARAMS)) { parsePlatformSpecificParamsTag(parser, platform, platformSpecificParamsMap); } else { parseIncludeExcludeTag(parser, excludes, includes, maybeRequiredFlags); } } } private void parsePlatformSpecificParamsTag( XmlPullParser parser, String platform, Map<String, PlatformSpecificParams> platformSpecificParamsMap) { if (TextUtils.isEmpty(platform)) { // Ignore the platform specific parameters if platform wasn't specified. return; } String bundleId = parser.getAttributeValue(null, FLAG_PLATFORM_SPECIFIC_PARAMS_BUNDLE_ID); String teamId = parser.getAttributeValue(null, FLAG_PLATFORM_SPECIFIC_PARAMS_TEAM_ID); String contentVersion = parser.getAttributeValue(null, FLAG_PLATFORM_SPECIFIC_PARAMS_CONTENT_VERSION); platformSpecificParamsMap.put( platform, new PlatformSpecificParams(bundleId, teamId, contentVersion)); } private void parseIncludeExcludeTag( XmlPullParser parser, Loading Loading @@ -1041,32 +1145,49 @@ 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 == null) { private void validateInnerTagContents(XmlPullParser parser, String configSection) throws XmlPullParserException { if (parser == null || parser.getName() == null) { return; } switch (parser.getName()) { case TAG_INCLUDE: if (parser.getName().equals(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: } else if (parser.getName().equals(TAG_EXCLUDE)) { if (parser.getAttributeCount() > 2) { throw new XmlPullParserException( "At most 2 tag attributes allowed for " + "\"exclude\" tag (\"domain\" & \"path\"."); } break; default: } else if (Flags.enableCrossPlatformTransfer() && configSection.equals(ConfigSection.CROSS_PLATFORM_TRANSFER) && parser.getName().equals(TAG_PLATFORM_SPECIFIC_PARAMS)) { if (parser.getAttributeCount() > 3) { throw new XmlPullParserException( "At most 3 tag attributes allowed for\"platform-specific-params\"" + " tag (\"bundleId\" & \"teamId\" &" + " \"contentVersion\"."); } } else { if (Flags.enableCrossPlatformTransfer() && configSection.equals(ConfigSection.CROSS_PLATFORM_TRANSFER)) { throw new XmlPullParserException( "A valid tag is one of \"<include/>\" or" + " \"<exclude/>. You provided \"" "A valid tag is one of \"<include/>\" or \"<exclude/>\" or" + " \"<platform-specific-params/>\". You provided \"" + parser.getName() + "\""); } else { throw new XmlPullParserException( "A valid tag is one of \"<include/>\" or \"<exclude/>\". You provided" + " \"" + parser.getName() + "\""); } } } } Loading
core/tests/coretests/src/android/app/backup/FullBackupTest.java +153 −2 Original line number Diff line number Diff line Loading @@ -17,14 +17,23 @@ package android.app.backup; import static android.app.backup.FullBackup.ConfigSection.CLOUD_BACKUP; import static android.app.backup.FullBackup.ConfigSection.CROSS_PLATFORM_TRANSFER; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags; import android.app.backup.FullBackup.BackupScheme.PlatformSpecificParams; import android.content.Context; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.ArraySet; Loading @@ -32,7 +41,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.server.backup.Flags; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParser; Loading @@ -50,12 +62,16 @@ import java.util.Set; @LargeTest @RunWith(AndroidJUnit4.class) public class FullBackupTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private XmlPullParserFactory mFactory; private XmlPullParser mXpp; private Context mContext; Map<String, Set<PathWithRequiredFlags>> includeMap; Set<PathWithRequiredFlags> excludesSet; Map<String, PlatformSpecificParams> mPlatformSpecificParamsMap; @Before public void setUp() throws Exception { Loading @@ -65,6 +81,7 @@ public class FullBackupTest { includeMap = new ArrayMap<>(); excludesSet = new ArraySet<>(); mPlatformSpecificParamsMap = new ArrayMap<>(); } @Test Loading Loading @@ -498,7 +515,7 @@ public class FullBackupTest { FullBackup.BackupScheme backupScheme = FullBackup.getBackupSchemeForTest(mContext); boolean result = backupScheme.parseNewBackupSchemeFromXmlLocked( mXpp, CLOUD_BACKUP, excludesSet, includeMap); mXpp, CLOUD_BACKUP, excludesSet, includeMap, mPlatformSpecificParamsMap); assertTrue(result); } Loading @@ -516,7 +533,7 @@ public class FullBackupTest { FullBackup.BackupScheme backupScheme = FullBackup.getBackupSchemeForTest(mContext); boolean result = backupScheme.parseNewBackupSchemeFromXmlLocked( mXpp, CLOUD_BACKUP, excludesSet, includeMap); mXpp, CLOUD_BACKUP, excludesSet, includeMap, mPlatformSpecificParamsMap); assertTrue(result); assertEquals( Loading Loading @@ -556,4 +573,138 @@ public class FullBackupTest { assertEquals("Didn't throw away invalid path containing \"//\".", 0, excludesSet.size()); } @Test @EnableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseOldBackupSchemeFromXml_flagOn_crossPlatformTransferSection_throws() throws Exception { mXpp.setInput( new StringReader( "<full-backup-content><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0\" contentVersion=\"1.0\" />" + "</full-backup-content>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext); assertThrows( "Unexpected platform-specific-params in old scheme should throw an exception", XmlPullParserException.class, () -> bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap)); } @Test @DisableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseNewBackupSchemeFromXml_flagOff_crossPlatformTransferSection_throws() throws Exception { mXpp.setInput( new StringReader( "<data-extraction-rules><cross-platform-transfer platform=\"ios\"><include" + " path=\"file.txt\" domain=\"file\" /><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0123abcd\"" + " contentVersion=\"1.0\" /></cross-platform-transfer>" + "</data-extraction-rules>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest( mContext, BackupAnnotations.BackupDestination.CROSS_PLATFORM_TRANSFER); assertThrows( "cross-platform-transfer in backup rules when feature is disabled should throw an" + " exception", XmlPullParserException.class, () -> bs.parseNewBackupSchemeFromXmlLocked( mXpp, CROSS_PLATFORM_TRANSFER, excludesSet, includeMap, mPlatformSpecificParamsMap)); } @Test @EnableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseNewBackupSchemeFromXml_flagOn_crossPlatformTransferSection_isParsed() throws Exception { assumeTrue(Flags.enableCrossPlatformTransfer()); mXpp.setInput( new StringReader( "<data-extraction-rules><cross-platform-transfer platform=\"ios\"><include" + " path=\"file.txt\" domain=\"file\" /><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0123abcd\"" + " contentVersion=\"1.0\" /></cross-platform-transfer>" + "</data-extraction-rules>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest( mContext, BackupAnnotations.BackupDestination.CROSS_PLATFORM_TRANSFER); boolean result = bs.parseNewBackupSchemeFromXmlLocked( mXpp, CROSS_PLATFORM_TRANSFER, excludesSet, includeMap, mPlatformSpecificParamsMap); assertTrue(result); assertThat(mPlatformSpecificParamsMap).containsKey("ios"); PlatformSpecificParams actual = mPlatformSpecificParamsMap.get("ios"); assertThat(actual.getBundleId()).isEqualTo("com.example.app"); assertThat(actual.getTeamId()).isEqualTo("0123abcd"); assertThat(actual.getContentVersion()).isEqualTo("1.0"); } @Test @EnableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseNewBackupSchemeFromXml_flagOn_missingPlatform_isParsedButIgnored() throws Exception { assumeTrue(Flags.enableCrossPlatformTransfer()); mXpp.setInput( new StringReader( "<data-extraction-rules><cross-platform-transfer><include" + " path=\"file.txt\" domain=\"file\" /><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0123abcd\"" + " contentVersion=\"1.0\" /></cross-platform-transfer>" + "</data-extraction-rules>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest( mContext, BackupAnnotations.BackupDestination.CROSS_PLATFORM_TRANSFER); boolean result = bs.parseNewBackupSchemeFromXmlLocked( mXpp, CROSS_PLATFORM_TRANSFER, excludesSet, includeMap, mPlatformSpecificParamsMap); assertTrue(result); assertThat(mPlatformSpecificParamsMap).isEmpty(); } @Test @EnableFlags({Flags.FLAG_ENABLE_CROSS_PLATFORM_TRANSFER}) public void testParseNewBackupSchemeFromXml_flagOn_invalidAttributeCount_throws() throws Exception { assumeTrue(Flags.enableCrossPlatformTransfer()); mXpp.setInput( new StringReader( "<data-extraction-rules><cross-platform-transfer platform=\"ios\"><include" + " path=\"file.txt\" domain=\"file\" /><platform-specific-params" + " bundleId=\"com.example.app\" teamId=\"0123abcd\"" + " contentVersion=\"1.0\" platform=\"ios\" />" + "</cross-platform-transfer>" + "</data-extraction-rules>")); FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest( mContext, BackupAnnotations.BackupDestination.CROSS_PLATFORM_TRANSFER); assertThrows( "invalid number of attributes in platform-specific-params should throw", XmlPullParserException.class, () -> bs.parseNewBackupSchemeFromXmlLocked( mXpp, CROSS_PLATFORM_TRANSFER, excludesSet, includeMap, mPlatformSpecificParamsMap)); } }