Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6751d290 authored by Ruslan Tkhakokhov's avatar Ruslan Tkhakokhov
Browse files

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
parent 2409543c
Loading
Loading
Loading
Loading
+100 −18
Original line number Diff line number Diff line
@@ -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,32 +484,96 @@ 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
@@ -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);
+7 −0
Original line number Diff line number Diff line
@@ -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);
    }
}