Loading services/companion/java/com/android/server/companion/BackupRestoreProcessor.java +39 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 + "]."); Loading @@ -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) Loading @@ -101,6 +113,8 @@ class BackupRestoreProcessor { .put(associationsPayload) .putInt(requestsPayloadLength) .put(requestsPayload) .putInt(metadataPayloadLength) .put(metadataPayload) .array(); } Loading Loading @@ -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()]; Loading @@ -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); Loading services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +27 −1 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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); } Loading Loading @@ -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."); } } services/companion/java/com/android/server/companion/datasync/LocalMetadataStore.java +33 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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; Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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(); }); } Loading @@ -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(); Loading @@ -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, Loading Loading
services/companion/java/com/android/server/companion/BackupRestoreProcessor.java +39 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 + "]."); Loading @@ -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) Loading @@ -101,6 +113,8 @@ class BackupRestoreProcessor { .put(associationsPayload) .putInt(requestsPayloadLength) .put(requestsPayload) .putInt(metadataPayloadLength) .put(metadataPayload) .array(); } Loading Loading @@ -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()]; Loading @@ -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); Loading
services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading
services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +27 −1 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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); } Loading Loading @@ -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."); } }
services/companion/java/com/android/server/companion/datasync/LocalMetadataStore.java +33 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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; Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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(); }); } Loading @@ -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(); Loading @@ -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, Loading