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

Commit 49504063 authored by Raphael Kim's avatar Raphael Kim
Browse files

[CDM] Add local metadata to backup/restoration payload.

Bug: 435265892
Test: atest CtsCompanionDeviceManagerCoreTestCases:BackupAndRestoreTest
Flag: android.companion.enable_data_sync
Change-Id: I8d547b1d56a0ebf646ce154dc65be3b9e158eb8c
parent cfd01f61
Loading
Loading
Loading
Loading
+39 −4
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.companion.datatransfer.SystemDataTransferRequest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
import android.os.PersistableBundle;
import android.util.Slog;

import com.android.internal.util.CollectionUtils;
@@ -37,6 +38,7 @@ import com.android.server.companion.association.AssociationDiskStore;
import com.android.server.companion.association.AssociationRequestsProcessor;
import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.association.Associations;
import com.android.server.companion.datasync.LocalMetadataStore;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;

import java.nio.ByteBuffer;
@@ -60,26 +62,31 @@ class BackupRestoreProcessor {
    private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
    @NonNull
    private final AssociationRequestsProcessor mAssociationRequestsProcessor;
    @NonNull
    private final LocalMetadataStore mLocalMetadataStore;

    BackupRestoreProcessor(@NonNull Context context,
                           @NonNull PackageManagerInternal packageManagerInternal,
                           @NonNull AssociationStore associationStore,
                           @NonNull AssociationDiskStore associationDiskStore,
                           @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
                           @NonNull AssociationRequestsProcessor associationRequestsProcessor) {
                           @NonNull AssociationRequestsProcessor associationRequestsProcessor,
                           @NonNull LocalMetadataStore localMetadataStore) {
        mContext = context;
        mPackageManagerInternal = packageManagerInternal;
        mAssociationStore = associationStore;
        mAssociationDiskStore = associationDiskStore;
        mSystemDataTransferRequestStore = systemDataTransferRequestStore;
        mAssociationRequestsProcessor = associationRequestsProcessor;
        mLocalMetadataStore = localMetadataStore;
    }

    /**
     * Generate CDM state payload to be backed up.
     * Backup payload is formatted as following:
     * | (4) payload version | (4) AssociationInfo length | AssociationInfo XML
     * | (4) SystemDataTransferRequest length | SystemDataTransferRequest XML (without userId)|
     * | (4) SystemDataTransferRequest length | SystemDataTransferRequest XML (without userId)
     * | (4) LocalMetadata length | LocalMetadata XML |
     */
    byte[] getBackupPayload(int userId) {
        Slog.i(TAG, "getBackupPayload() userId=[" + userId + "].");
@@ -91,9 +98,14 @@ class BackupRestoreProcessor {
        byte[] requestsPayload = mSystemDataTransferRequestStore.getBackupPayload(userId);
        int requestsPayloadLength = requestsPayload.length;

        int payloadSize = /* 3 integers */ 12
        // Local metadata is persisted up-to-date already
        byte[] metadataPayload = mLocalMetadataStore.getBackupPayload(userId);
        int metadataPayloadLength = metadataPayload.length;

        int payloadSize = /* 4 integers */ 16
                + associationsPayloadLength
                + requestsPayloadLength;
                + requestsPayloadLength
                + metadataPayloadLength;

        return ByteBuffer.allocate(payloadSize)
                .putInt(BACKUP_AND_RESTORE_VERSION)
@@ -101,6 +113,8 @@ class BackupRestoreProcessor {
                .put(associationsPayload)
                .putInt(requestsPayloadLength)
                .put(requestsPayload)
                .putInt(metadataPayloadLength)
                .put(metadataPayload)
                .array();
    }

@@ -129,6 +143,7 @@ class BackupRestoreProcessor {
        // error is caught early on.
        final byte[] associationsPayload;
        final byte[] requestsPayload;
        final byte[] metadataPayload;
        try {
            // Read the bytes containing backed-up associations
            associationsPayload = new byte[buffer.getInt()];
@@ -142,6 +157,26 @@ class BackupRestoreProcessor {
            return;
        }

        // Metadata was introduced in 26Q2. Ignore if the payload is older and missing the metadata
        // payload section.
        // Restoring local metadata can be done independently of associations and requests, so it
        // will not return early even if the payload is mal-formatted.
        try {
            // Read the bytes containing backed-up local metadata
            metadataPayload = new byte[buffer.getInt()];
            buffer.get(metadataPayload);

            // Handle conflict by prioritizing current metadata over restored metadata.
            PersistableBundle currentMetadata = mLocalMetadataStore.getMetadataForUser(userId);
            PersistableBundle restoredMetadata =
                    mLocalMetadataStore.readMetadataFromPayload(metadataPayload);
            restoredMetadata.putAll(currentMetadata);
            mLocalMetadataStore.setMetadataForUser(userId, restoredMetadata);
        } catch (Exception bufferException) {
            Slog.w(TAG, "Metadata payload was malformatted or missing.", bufferException);
        }

        // Restore associations and system data transfer requests next.
        final Associations restoredAssociations = readAssociationsFromPayload(
                associationsPayload, userId);

+1 −1
Original line number Diff line number Diff line
@@ -181,7 +181,7 @@ public class CompanionDeviceManagerService extends SystemService {
                packageManagerInternal, mAssociationStore);
        mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal,
                mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
                mAssociationRequestsProcessor);
                mAssociationRequestsProcessor, mLocalMetadataStore);

        mCompanionAppBinder = new CompanionAppBinder(context);

+27 −1
Original line number Diff line number Diff line
@@ -477,7 +477,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
                    if (bundle == null) {
                        bundle = new PersistableBundle();
                    }
                    if (value == null) {
                    if (value == "null") {
                        bundle.remove(key);
                    } else {
                        try {
@@ -492,6 +492,26 @@ class CompanionDeviceShellCommand extends ShellCommand {
                    break;
                }

                case "get-local-metadata": {
                    int userId = getNextIntArgRequired();
                    String feature = getNextArgRequired();
                    String key = getNextArgRequired();
                    PersistableBundle bundle = mDataSyncProcessor.getLocalMetadata(userId)
                            .getPersistableBundle(feature);
                    out.println(bundle.getString(key, "null"));
                    break;
                }

                case "clear-local-metadata": {
                    int userId = getNextIntArgRequired();
                    PersistableBundle bundle = mDataSyncProcessor.getLocalMetadata(userId);
                    bundle.keySet().stream().forEach(key -> {
                        mDataSyncProcessor.setLocalMetadata(userId, key, null);
                    });
                    out.println("Cleared local metadata for user " + userId);
                    break;
                }

                default:
                    return handleDefaultCommands(cmd);
            }
@@ -651,5 +671,11 @@ class CompanionDeviceShellCommand extends ShellCommand {
        pw.println("  set-local-metadata <USER_ID> <FEATURE_KEY> <KEY> <VALUE>");
        pw.println("      Set local metadata for the user.");
        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
        pw.println("  get-local-metadata <USER_ID> <FEATURE_KEY> <KEY>");
        pw.println("      Fetch local metadata for the user at a given key.");
        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
        pw.println("  clear-local-metadata <USER_ID>");
        pw.println("      Clear local metadata for the user.");
        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
    }
}
+33 −6
Original line number Diff line number Diff line
@@ -29,10 +29,13 @@ import android.util.SparseArray;
import android.util.Xml;

import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
@@ -41,6 +44,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.xmlpull.v1.XmlPullParserException;

/**
 * This store manages the cache and disk data for data sync.
@@ -53,6 +57,7 @@ public class LocalMetadataStore {

    private static final String TAG = "CDM_LocalMetadataStore";
    private static final String FILE_NAME = "cdm_local_metadata.xml";
    private static final String ROOT_TAG = "bundle";
    private static final int READ_FROM_DISK_TIMEOUT = 5; // in seconds

    private final ExecutorService mExecutor;
@@ -71,8 +76,8 @@ public class LocalMetadataStore {
    /**
     * Set the metadata for a given user.
     */
    void setMetadataForUser(@UserIdInt int userId, @NonNull PersistableBundle metadata) {
        Slog.i(TAG, "Setting metadata for user=[" + userId + "] value=[" + metadata + "]...");
    public void setMetadataForUser(@UserIdInt int userId, @NonNull PersistableBundle metadata) {
        Slog.i(TAG, "Setting metadata for user=[" + userId + "] value=" + metadata + "...");

        synchronized (mLock) {
            mCachedPerUser.put(userId, metadata);
@@ -85,7 +90,7 @@ public class LocalMetadataStore {
     * Read the metadata for a given user.
     */
    @NonNull
    PersistableBundle getMetadataForUser(@UserIdInt int userId) {
    public PersistableBundle getMetadataForUser(@UserIdInt int userId) {
        synchronized (mLock) {
            return readMetadataFromCache(userId).deepCopy();
        }
@@ -123,10 +128,10 @@ public class LocalMetadataStore {
        synchronized (file) {
            writeToFileSafely(file, out -> {
                final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
                serializer.setFeature(
                        "http://xmlpull.org/v1/doc/features.html#indent-output", true);
                serializer.startDocument(null, true);
                serializer.startTag(null, ROOT_TAG);
                metadata.saveToXml(serializer);
                serializer.endTag(null, ROOT_TAG);
                serializer.endDocument();
            });
        }
@@ -144,7 +149,7 @@ public class LocalMetadataStore {
                return new PersistableBundle();
            }
            try (FileInputStream in = file.openRead()) {
                return PersistableBundle.readFromStream(in);
                return readMetadataFromInputStream(in);
            } catch (IOException e) {
                Slog.e(TAG, "Error while reading metadata file", e);
                return new PersistableBundle();
@@ -164,6 +169,28 @@ public class LocalMetadataStore {
        }
    }

    @NonNull
    public PersistableBundle readMetadataFromPayload(@NonNull byte[] payload) {
        try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
            return readMetadataFromInputStream(in);
        } catch (IOException e) {
            Slog.w(TAG, "Error while reading metadata from payload.", e);
            return new PersistableBundle();
        }
    }

    @NonNull
    private PersistableBundle readMetadataFromInputStream(@NonNull InputStream in)
            throws IOException {
        try {
            final TypedXmlPullParser parser = Xml.resolvePullParser(in);
            parser.next();
            return PersistableBundle.restoreFromXml(parser);
        } catch (XmlPullParserException e) {
            throw new IOException(e);
        }
    }

    @NonNull
    private AtomicFile getStorageFileForUser(@UserIdInt int userId) {
        return mUserIdToStorageFile.computeIfAbsent(userId,