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

Commit 69f53dfa authored by Raphael Kim's avatar Raphael Kim
Browse files

[CDM] Introduce data sync metadata for AssociationInfo

This metadata is persisted on disk.

Bug: 435265892
Test: atest FrameworksServicesTests:AssociationDiskStoreTest
Test: atest FrameworksServicesTests:DataSyncStoreTest
Flag: android.companion.enable_data_sync
Change-Id: I0ad707b0af8ce22eb56500656ad3d5def1c6d74f
parent 4c4c0881
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -954,6 +954,7 @@ package android.companion {
    method @NonNull public android.companion.AssociationInfo.Builder setDeviceProfile(@Nullable String);
    method @NonNull public android.companion.AssociationInfo.Builder setDisplayName(@Nullable CharSequence);
    method @NonNull public android.companion.AssociationInfo.Builder setLastTimeConnected(long);
    method @FlaggedApi("android.companion.enable_data_sync") @NonNull public android.companion.AssociationInfo.Builder setMetadata(@NonNull android.os.PersistableBundle);
    method @NonNull public android.companion.AssociationInfo.Builder setNotifyOnDeviceNearby(boolean);
    method @FlaggedApi("android.companion.association_verification") @NonNull public android.companion.AssociationInfo.Builder setPackagesToNotify(@Nullable java.util.List<java.lang.String>);
    method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean);
+80 −4
Original line number Diff line number Diff line
@@ -22,10 +22,13 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.companion.CompanionDeviceManager.FeatureName;
import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.BaseBundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;

import java.util.Date;
import java.util.List;
@@ -44,6 +47,11 @@ public final class AssociationInfo implements Parcelable {
     * A String indicates the selfManaged device is not connected.
     */
    private static final String LAST_TIME_CONNECTED_NONE = "None";
    /**
     * Key for the reception timestamp of the metadata.
     */
    static final String METADATA_TIMESTAMP = "_timestamp_";

    /**
     * A unique ID of this Association record.
     * Disclosed to the clients (i.e. companion applications) for referring to this record (e.g. in
@@ -89,6 +97,11 @@ public final class AssociationInfo implements Parcelable {
    private final DeviceId mDeviceId;
    @Nullable
    private final List<String> mPackagesToNotify;
    /**
     * A map of metadata describing the device's data sync policies for each feature client.
     */
    @NonNull
    private final PersistableBundle mMetadata;

    /**
     * A device icon displayed on a selfManaged association dialog.
@@ -106,7 +119,7 @@ public final class AssociationInfo implements Parcelable {
            boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked, boolean pending,
            long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags,
            int transportFlags, @Nullable Icon deviceIcon, @Nullable DeviceId deviceId,
            @Nullable List<String> packagesToNotify) {
            @Nullable List<String> packagesToNotify, @Nullable PersistableBundle metadata) {
        if (id <= 0) {
            throw new IllegalArgumentException("Association ID should be greater than 0");
        }
@@ -133,6 +146,7 @@ public final class AssociationInfo implements Parcelable {
        mDeviceIcon = deviceIcon;
        mDeviceId = deviceId;
        mPackagesToNotify = packagesToNotify;
        mMetadata = metadata;
    }

    /**
@@ -322,6 +336,47 @@ public final class AssociationInfo implements Parcelable {
        return mPackagesToNotify;
    }

    /**
     * @return the metadata of the association.
     * @hide
     */
    @FlaggedApi(Flags.FLAG_ENABLE_DATA_SYNC)
    @NonNull
    public PersistableBundle getMetadata() {
        return mMetadata;
    }

    /**
     * @return the metadata of the association for a given feature name.
     * If the metadata is not available, it returns a new empty bundle.
     *
     * @hide
     */
    @FlaggedApi(Flags.FLAG_ENABLE_DATA_SYNC)
    @NonNull
    public PersistableBundle getMetadata(@NonNull @FeatureName String feature) {
        if (METADATA_TIMESTAMP.equals(feature)) {
            throw new IllegalArgumentException("Cannot get metadata for timestamp. "
                    + "Use getMetadataTimestamp() instead to get the timestamp.");
        }

        PersistableBundle bundle = mMetadata.getPersistableBundle(feature);
        if (bundle == null) {
            return new PersistableBundle();
        }
        return bundle;
    }

    /**
     * @return the timestamp at which the metadata was last received from the remote device.
     * If the metadata was never set, then it returns 0.
     * @hide
     */
    @FlaggedApi(Flags.FLAG_ENABLE_DATA_SYNC)
    public long getMetadataTimestamp() {
        return mMetadata.getLong(METADATA_TIMESTAMP, 0L);
    }

    /**
     * Utility method for checking if the association represents a device with the given MAC
     * address.
@@ -395,6 +450,7 @@ public final class AssociationInfo implements Parcelable {
                + ", mTransportFlags=" + mTransportFlags
                + ", mDeviceId=" + mDeviceId
                + ", mPackagesToNotify=" + mPackagesToNotify
                + ", mMetadata=" + mMetadata
                + '}';
    }

@@ -421,7 +477,8 @@ public final class AssociationInfo implements Parcelable {
                && mTransportFlags == that.mTransportFlags
                && isSameIcon(mDeviceIcon, that.mDeviceIcon)
                && Objects.equals(mDeviceId, that.mDeviceId)
                && Objects.equals(mPackagesToNotify, that.mPackagesToNotify);
                && Objects.equals(mPackagesToNotify, that.mPackagesToNotify)
                && BaseBundle.kindofEquals(mMetadata, that.mMetadata);
    }

    private boolean isSameIcon(Icon iconA, Icon iconB) {
@@ -436,7 +493,7 @@ public final class AssociationInfo implements Parcelable {
        return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
                mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
                mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags,
                mTransportFlags, mDeviceIcon, mDeviceId, mPackagesToNotify);
                mTransportFlags, mDeviceIcon, mDeviceId, mPackagesToNotify, mMetadata);
    }

    @Override
@@ -476,6 +533,7 @@ public final class AssociationInfo implements Parcelable {
        }

        dest.writeStringList(mPackagesToNotify);
        dest.writePersistableBundle(mMetadata);
    }

    private AssociationInfo(@NonNull Parcel in) {
@@ -507,6 +565,7 @@ public final class AssociationInfo implements Parcelable {
            mDeviceId = null;
        }
        mPackagesToNotify = in.createStringArrayList();
        mMetadata = in.readPersistableBundle();
    }

    @NonNull
@@ -548,6 +607,7 @@ public final class AssociationInfo implements Parcelable {
        private Icon mDeviceIcon;
        private DeviceId mDeviceId;
        private List<String> mPackagesToNotify;
        private PersistableBundle mMetadata = new PersistableBundle(); // Empty bundle by default.

        /** @hide */
        @TestApi
@@ -578,6 +638,7 @@ public final class AssociationInfo implements Parcelable {
            mDeviceIcon = info.mDeviceIcon;
            mDeviceId = info.mDeviceId;
            mPackagesToNotify = info.mPackagesToNotify;
            mMetadata = info.mMetadata;
        }

        /**
@@ -605,6 +666,7 @@ public final class AssociationInfo implements Parcelable {
            mDeviceIcon = info.mDeviceIcon;
            mDeviceId = info.mDeviceId;
            mPackagesToNotify = info.mPackagesToNotify;
            mMetadata = info.mMetadata;
        }

        /** @hide */
@@ -746,6 +808,16 @@ public final class AssociationInfo implements Parcelable {
            return this;
        }

        /** @hide */
        @TestApi
        @NonNull
        @SuppressLint("MissingGetterMatchingBuilder")
        @FlaggedApi(Flags.FLAG_ENABLE_DATA_SYNC)
        public Builder setMetadata(@NonNull PersistableBundle metadata) {
            mMetadata = metadata;
            return this;
        }

        /** @hide */
        @TestApi
        @NonNull
@@ -757,6 +829,9 @@ public final class AssociationInfo implements Parcelable {
                throw new IllegalArgumentException("MAC address and the display name must NOT be "
                        + "null at the same time");
            }
            if (mMetadata == null) {
                throw new IllegalArgumentException("Association metadata cannot be null");
            }
            return new AssociationInfo(
                    mId,
                    mUserId,
@@ -775,7 +850,8 @@ public final class AssociationInfo implements Parcelable {
                    mTransportFlags,
                    mDeviceIcon,
                    mDeviceId,
                    mPackagesToNotify
                    mPackagesToNotify,
                    mMetadata
            );
        }
    }
+65 −2
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMIN
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
import static android.companion.AssociationInfo.METADATA_TIMESTAMP;
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;

@@ -31,6 +32,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -61,6 +63,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
@@ -219,6 +222,36 @@ public final class CompanionDeviceManager {
    @FlaggedApi(Flags.FLAG_ENABLE_TASK_CONTINUITY)
    public static final int FLAG_TASK_CONTINUITY = 2;

    /**
     * The feature name for task continuity manager.
     * @hide
     */
    @FlaggedApi(Flags.FLAG_ENABLE_DATA_SYNC)
    public static final String FEATURE_TASK_CONTINUITY = "task_continuity_manager";

    /**
     * The feature name for the mode Sync.
     * @hide
     */
    @FlaggedApi(Flags.FLAG_ENABLE_DATA_SYNC)
    public static final String FEATURE_MODE_SYNC = "mode_sync";

    /**
     * The feature name for airplane mode sync.
     * @hide
     */
    @FlaggedApi(Flags.FLAG_ENABLE_DATA_SYNC)
    public static final String FEATURE_AIRPLANE_MODE_SYNC = "airplane_mode_sync";

    /** @hide */
    @StringDef(prefix = { "FEATURE_" }, value = {
            FEATURE_TASK_CONTINUITY,
            FEATURE_MODE_SYNC,
            FEATURE_AIRPLANE_MODE_SYNC,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface FeatureName {}

    /**
     * A device, returned in the activity result of the {@link IntentSender} received in
     * {@link Callback#onDeviceFound}
@@ -612,7 +645,7 @@ public final class CompanionDeviceManager {
    }

    /**
     * <p>Enable system data sync.
     * <p>Enable system data sync (it only supports call metadata sync for now).
     * By default all supported system data types are enabled.</p>
     *
     * <p>Calling this API requires a uses-feature
@@ -635,7 +668,7 @@ public final class CompanionDeviceManager {
    }

    /**
     * <p>Disable system data sync.
     * <p>Disable system data sync (it only supports call metadata sync for now).
     * By default all supported system data types are enabled.</p>
     *
     * <p>Calling this API requires a uses-feature
@@ -1975,6 +2008,36 @@ public final class CompanionDeviceManager {
        }
    }

    /**
     * Sets the metadata for this device. If the metadata for the feature is already set, then
     * it will be overwritten. The client feature service can use this metadata to store
     * feature-relevant data to be shared with associated devices.
     *
     * @param userId The user id of the user for which this metadata is set.
     * @param feature The feature name for this metadata.
     * @param value The bundle containing feature-relevant metadata.
     *
     * @hide
     */
    @FlaggedApi(Flags.FLAG_ENABLE_DATA_SYNC)
    public void setLocalMetadata(@UserIdInt int userId, @NonNull @FeatureName String feature,
            @Nullable PersistableBundle value) {
        if (mService == null) {
            Log.w(TAG, "CompanionDeviceManager service is not available.");
            return;
        }

        if (METADATA_TIMESTAMP.equals(feature)) {
            throw new IllegalArgumentException("Cannot set metadata timestamp");
        }

        try {
            mService.setLocalMetadata(userId, feature, value);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    private static class AssociationRequestCallbackProxy extends IAssociationRequestCallback.Stub {
        private final Handler mHandler;
        private final Callback mCallback;
+3 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.companion.ObservingDevicePresenceRequest;
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.companion.DeviceId;


@@ -153,4 +154,6 @@ interface ICompanionDeviceManager {
    AssociationInfo getAssociationByDeviceId(int userId, in DeviceId deviceId);

    DeviceId setDeviceId(int associationId, in DeviceId deviceId);

    void setLocalMetadata(int userId, String key, in PersistableBundle value);
}
+31 −15
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ import android.net.MacAddress;
import android.os.Binder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.PowerExemptionManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
@@ -98,6 +99,8 @@ import com.android.server.companion.association.AssociationRequestsProcessor;
import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.association.DisassociationProcessor;
import com.android.server.companion.association.InactiveAssociationsRemovalService;
import com.android.server.companion.datasync.DataSyncProcessor;
import com.android.server.companion.datasync.LocalMetadataStore;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
@@ -137,6 +140,8 @@ public class CompanionDeviceManagerService extends SystemService {
    private final CompanionTransportManager mTransportManager;
    private final DisassociationProcessor mDisassociationProcessor;
    private final CrossDeviceSyncController mCrossDeviceSyncController;
    private final LocalMetadataStore mLocalMetadataStore;
    private final DataSyncProcessor mDataSyncProcessor;

    private final Object mPackageLock = new Object();

@@ -163,6 +168,7 @@ public class CompanionDeviceManagerService extends SystemService {
        mAssociationStore = new AssociationStore(context, userManager, associationDiskStore);
        mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
        mObservableUuidStore = new ObservableUuidStore();
        mLocalMetadataStore = new LocalMetadataStore();

        // Init processors
        mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
@@ -192,6 +198,8 @@ public class CompanionDeviceManagerService extends SystemService {
                packageManagerInternal, mAssociationStore,
                mSystemDataTransferRequestStore, mTransportManager);

        mDataSyncProcessor = new DataSyncProcessor(mAssociationStore, mLocalMetadataStore);

        // TODO(b/279663946): move context sync to a dedicated system service
        mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
    }
@@ -647,25 +655,22 @@ public class CompanionDeviceManagerService extends SystemService {

        @Override
        public void enablePermissionsSync(int associationId) {
            if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
                throw new SecurityException("Caller must be system UID");
            }
            enforceCallerIsSystem();

            mSystemDataTransferProcessor.enablePermissionsSync(associationId);
        }

        @Override
        public void disablePermissionsSync(int associationId) {
            if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
                throw new SecurityException("Caller must be system UID");
            }
            enforceCallerIsSystem();

            mSystemDataTransferProcessor.disablePermissionsSync(associationId);
        }

        @Override
        public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
            if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
                throw new SecurityException("Caller must be system UID");
            }
            enforceCallerIsSystem();

            return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
        }

@@ -734,6 +739,12 @@ public class CompanionDeviceManagerService extends SystemService {
            }
        }

        private void enforceCallerIsSystem() {
            if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
                throw new SecurityException("Caller must be system UID");
            }
        }

        @Override
        public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
            final AssociationInfo association =
@@ -760,18 +771,23 @@ public class CompanionDeviceManagerService extends SystemService {
        }

        @Override
        public byte[] getBackupPayload(int userId) {
            if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
                throw new SecurityException("Caller must be system");
        public void setLocalMetadata(int userId, String key, PersistableBundle value) {
            enforceCallerIsSystem();

            mDataSyncProcessor.setLocalMetadata(userId, key, value);
        }

        @Override
        public byte[] getBackupPayload(int userId) {
            enforceCallerIsSystem();

            return mBackupRestoreProcessor.getBackupPayload(userId);
        }

        @Override
        public void applyRestoredPayload(byte[] payload, int userId) {
            if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
                throw new SecurityException("Caller must be system");
            }
            enforceCallerIsSystem();

            mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
        }

Loading