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

Commit c540bea6 authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov
Browse files

Implement extensible device ID in CDM

Introduce android.companion.DeviceId class.
Refactor Association class to store the list of DeviceId objects instead
of a single MAC address String.
Fix CDMService to work with the updated Association class.

Bug: 197933995
Test: make
Change-Id: I43ce12178d6ccdcfd9832bf2a78d00acae431f94
parent b1d40851
Loading
Loading
Loading
Loading
+154 −152
Original line number Original line Diff line number Diff line
@@ -15,219 +15,219 @@
 */
 */
package android.companion;
package android.companion;


import static android.companion.DeviceId.TYPE_MAC_ADDRESS;

import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.UserIdInt;
import android.os.Parcel;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable;


import com.android.internal.util.DataClass;
import java.util.ArrayList;

import java.util.Collections;
import java.util.Date;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Objects;
import java.util.Set;


/**
/**
 * A record indicating that a device with a given address was confirmed by the user to be
 * A record indicating that a device with a given address was confirmed by the user to be
 * associated to a given companion app
 * associated to a given companion app
 *
 *
 * @hide
 * @hide
 * TODO(b/1979395): un-hide and rename to AssociationInfo when implementing public APIs that use
 *                  this class.
 */
 */
@DataClass(genEqualsHashCode = true, genToString = true, genHiddenConstructor = true)
public final class Association implements Parcelable {
public final class Association implements Parcelable {
    /**
     * A unique ID of this Association record.
     * Disclosed to the clients (ie. companion applications) for referring to this record (eg. in
     * {@code disassociate()} API call).
     */
    private final int mAssociationId;


    private final @UserIdInt int mUserId;
    private final @UserIdInt int mUserId;
    private final @NonNull String mDeviceMacAddress;
    private final @NonNull String mPackageName;
    private final @NonNull String mPackageName;

    private final @NonNull List<DeviceId> mDeviceIds;
    private final @Nullable String mDeviceProfile;
    private final @Nullable String mDeviceProfile;
    private final boolean mNotifyOnDeviceNearby;
    private final long mTimeApprovedMs;


    /** @hide */
    private final boolean mManagedByCompanionApp;
    public int getUserId() {
    private boolean mNotifyOnDeviceNearby;
        return mUserId;
    private final long mTimeApprovedMs;
    }


    private String timeApprovedMsToString() {
    /**
        return new Date(mTimeApprovedMs).toString();
     * Creates a new Association.
     * Only to be used by the CompanionDeviceManagerService.
     *
     * @hide
     */
    public Association(int associationId, @UserIdInt int userId, @NonNull String packageName,
            @NonNull List<DeviceId> deviceIds, @Nullable String deviceProfile,
            boolean managedByCompanionApp, boolean notifyOnDeviceNearby, long timeApprovedMs) {
        if (associationId <= 0) {
            // TODO: uncomment when implementing real IDs
            // throw new IllegalArgumentException("Association ID should be greater than 0");
        }
        }
        validateDeviceIds(deviceIds);


        mAssociationId = associationId;


        mUserId = userId;
        mPackageName = packageName;


    // Code below generated by codegen v1.0.22.
        mDeviceProfile = deviceProfile;
    //
        mDeviceIds = new ArrayList<>(deviceIds);
    // DO NOT MODIFY!
    // CHECKSTYLE:OFF Generated code
    //
    // To regenerate run:
    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/companion/Association.java
    //
    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
    //   Settings > Editor > Code Style > Formatter Control
    //@formatter:off


        mManagedByCompanionApp = managedByCompanionApp;
        mNotifyOnDeviceNearby = notifyOnDeviceNearby;
        mTimeApprovedMs = timeApprovedMs;
    }


    /**
    /**
     * Creates a new Association.
     * @return the unique ID of this association record.
     *
     * @hide
     */
     */
    @DataClass.Generated.Member
    public int getAssociationId() {
    public Association(
        return mAssociationId;
            @UserIdInt int userId,
            @NonNull String deviceMacAddress,
            @NonNull String packageName,
            @Nullable String deviceProfile,
            boolean notifyOnDeviceNearby,
            long timeApprovedMs) {
        this.mUserId = userId;
        com.android.internal.util.AnnotationValidations.validate(
                UserIdInt.class, null, mUserId);
        this.mDeviceMacAddress = deviceMacAddress;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mDeviceMacAddress);
        this.mPackageName = packageName;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mPackageName);
        this.mDeviceProfile = deviceProfile;
        this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
        this.mTimeApprovedMs = timeApprovedMs;

        // onConstructed(); // You can define this method to get a callback
    }

    @DataClass.Generated.Member
    public @NonNull String getDeviceMacAddress() {
        return mDeviceMacAddress;
    }
    }


    @DataClass.Generated.Member
    /** @hide */
    public int getUserId() {
        return mUserId;
    }

    /** @hide */
    public @NonNull String getPackageName() {
    public @NonNull String getPackageName() {
        return mPackageName;
        return mPackageName;
    }
    }


    @DataClass.Generated.Member
    /**
     * @return list of the device's IDs. At any time a device has at least 1 ID.
     */
    public @NonNull List<DeviceId> getDeviceIds() {
        return Collections.unmodifiableList(mDeviceIds);
    }

    /**
     * @param type type of the ID.
     * @return ID of the type if the device has such ID, {@code null} otherwise.
     */
    public @Nullable String getIdOfType(@NonNull String type) {
        for (int i = mDeviceIds.size() - 1; i >= 0; i--) {
            final DeviceId id = mDeviceIds.get(i);
            if (Objects.equals(mDeviceIds.get(i).getType(), type)) return id.getValue();
        }
        return null;
    }

    /** @hide */
    public @NonNull String getDeviceMacAddress() {
        return Objects.requireNonNull(getIdOfType(TYPE_MAC_ADDRESS),
                "MAC address of this device is not specified.");
    }

    /**
     * @return the profile of the device.
     */
    public @Nullable String getDeviceProfile() {
    public @Nullable String getDeviceProfile() {
        return mDeviceProfile;
        return mDeviceProfile;
    }
    }


    @DataClass.Generated.Member
    /** @hide */
    public boolean isManagedByCompanionApp() {
        return mManagedByCompanionApp;
    }

    /**
     * Should only be used by the CdmService.
     * @hide
     */
    public void setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) {
        mNotifyOnDeviceNearby = notifyOnDeviceNearby;
    }

    /** @hide */
    public boolean isNotifyOnDeviceNearby() {
    public boolean isNotifyOnDeviceNearby() {
        return mNotifyOnDeviceNearby;
        return mNotifyOnDeviceNearby;
    }
    }


    @DataClass.Generated.Member
    /** @hide */
    public long getTimeApprovedMs() {
    public long getTimeApprovedMs() {
        return mTimeApprovedMs;
        return mTimeApprovedMs;
    }
    }


    @Override
    @Override
    @DataClass.Generated.Member
    public String toString() {
    public String toString() {
        // You can override field toString logic by defining methods like:
        return "Association{"
        // String fieldNameToString() { ... }
                + "mAssociationId=" + mAssociationId

                + ", mUserId=" + mUserId
        return "Association { " +
                + ", mPackageName='" + mPackageName + '\''
                "userId = " + mUserId + ", " +
                + ", mDeviceIds=" + mDeviceIds
                "deviceMacAddress = " + mDeviceMacAddress + ", " +
                + ", mDeviceProfile='" + mDeviceProfile + '\''
                "packageName = " + mPackageName + ", " +
                + ", mManagedByCompanionApp=" + mManagedByCompanionApp
                "deviceProfile = " + mDeviceProfile + ", " +
                + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
                "notifyOnDeviceNearby = " + mNotifyOnDeviceNearby + ", " +
                + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
                "timeApprovedMs = " + timeApprovedMsToString() +
                + '}';
        " }";
    }
    }


    @Override
    @Override
    @DataClass.Generated.Member
    public boolean equals(Object o) {
    public boolean equals(@Nullable Object o) {
        // You can override field equality logic by defining either of the methods like:
        // boolean fieldNameEquals(Association other) { ... }
        // boolean fieldNameEquals(FieldType otherValue) { ... }

        if (this == o) return true;
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!(o instanceof Association)) return false;
        @SuppressWarnings("unchecked")
        final Association that = (Association) o;
        Association that = (Association) o;
        return mAssociationId == that.mAssociationId
        //noinspection PointlessBooleanExpression
        return true
                && mUserId == that.mUserId
                && mUserId == that.mUserId
                && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
                && mManagedByCompanionApp == that.mManagedByCompanionApp
                && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
                && mTimeApprovedMs == that.mTimeApprovedMs
                && Objects.equals(mPackageName, that.mPackageName)
                && Objects.equals(mPackageName, that.mPackageName)
                && Objects.equals(mDeviceProfile, that.mDeviceProfile)
                && Objects.equals(mDeviceProfile, that.mDeviceProfile)
                && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
                && Objects.equals(mDeviceIds, that.mDeviceIds);
                && mTimeApprovedMs == that.mTimeApprovedMs;
    }
    }


    @Override
    @Override
    @DataClass.Generated.Member
    public int hashCode() {
    public int hashCode() {
        // You can override field hashCode logic by defining methods like:
        return Objects.hash(mAssociationId, mUserId, mPackageName, mDeviceIds, mDeviceProfile,
        // int fieldNameHashCode() { ... }
                mManagedByCompanionApp, mNotifyOnDeviceNearby, mTimeApprovedMs);
    }


        int _hash = 1;
    @Override
        _hash = 31 * _hash + mUserId;
    public int describeContents() {
        _hash = 31 * _hash + Objects.hashCode(mDeviceMacAddress);
        return 0;
        _hash = 31 * _hash + Objects.hashCode(mPackageName);
        _hash = 31 * _hash + Objects.hashCode(mDeviceProfile);
        _hash = 31 * _hash + Boolean.hashCode(mNotifyOnDeviceNearby);
        _hash = 31 * _hash + Long.hashCode(mTimeApprovedMs);
        return _hash;
    }
    }


    @Override
    @Override
    @DataClass.Generated.Member
    public void writeToParcel(@NonNull Parcel dest, int flags) {
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        // You can override field parcelling by defining methods like:
        dest.writeInt(mAssociationId);
        // void parcelFieldName(Parcel dest, int flags) { ... }


        byte flg = 0;
        if (mNotifyOnDeviceNearby) flg |= 0x10;
        if (mDeviceProfile != null) flg |= 0x8;
        dest.writeByte(flg);
        dest.writeInt(mUserId);
        dest.writeInt(mUserId);
        dest.writeString(mDeviceMacAddress);
        dest.writeString(mPackageName);
        dest.writeString(mPackageName);
        if (mDeviceProfile != null) dest.writeString(mDeviceProfile);

        dest.writeParcelableList(mDeviceIds, 0);
        dest.writeString(mDeviceProfile);

        dest.writeBoolean(mManagedByCompanionApp);
        dest.writeBoolean(mNotifyOnDeviceNearby);
        dest.writeLong(mTimeApprovedMs);
        dest.writeLong(mTimeApprovedMs);
    }
    }


    @Override
    private Association(@NonNull Parcel in) {
    @DataClass.Generated.Member
        mAssociationId = in.readInt();
    public int describeContents() { return 0; }


    /** @hide */
        mUserId = in.readInt();
    @SuppressWarnings({"unchecked", "RedundantCast"})
        mPackageName = in.readString();
    @DataClass.Generated.Member

    /* package-private */ Association(@NonNull Parcel in) {
        mDeviceIds = in.readParcelableList(new ArrayList<>(), DeviceId.class.getClassLoader());
        // You can override field unparcelling by defining methods like:
        mDeviceProfile = in.readString();
        // static FieldType unparcelFieldName(Parcel in) { ... }


        mManagedByCompanionApp = in.readBoolean();
        byte flg = in.readByte();
        mNotifyOnDeviceNearby = in.readBoolean();
        boolean notifyOnDeviceNearby = (flg & 0x10) != 0;
        mTimeApprovedMs = in.readLong();
        int userId = in.readInt();
    }
        String deviceMacAddress = in.readString();

        String packageName = in.readString();
    public static final Parcelable.Creator<Association> CREATOR =
        String deviceProfile = (flg & 0x8) == 0 ? null : in.readString();
            new Parcelable.Creator<Association>() {
        long timeApprovedMs = in.readLong();

        this.mUserId = userId;
        com.android.internal.util.AnnotationValidations.validate(
                UserIdInt.class, null, mUserId);
        this.mDeviceMacAddress = deviceMacAddress;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mDeviceMacAddress);
        this.mPackageName = packageName;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mPackageName);
        this.mDeviceProfile = deviceProfile;
        this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
        this.mTimeApprovedMs = timeApprovedMs;

        // onConstructed(); // You can define this method to get a callback
    }

    @DataClass.Generated.Member
    public static final @NonNull Parcelable.Creator<Association> CREATOR
            = new Parcelable.Creator<Association>() {
        @Override
        @Override
        public Association[] newArray(int size) {
        public Association[] newArray(int size) {
            return new Association[size];
            return new Association[size];
@@ -239,16 +239,18 @@ public final class Association implements Parcelable {
        }
        }
    };
    };


    @DataClass.Generated(
    private static void validateDeviceIds(@NonNull List<DeviceId> ids) {
            time = 1611795283642L,
        if (ids.isEmpty()) throw new IllegalArgumentException("Device must have at least 1 id.");
            codegenVersion = "1.0.22",

            sourceFile = "frameworks/base/core/java/android/companion/Association.java",
        // Make sure none of the IDs are null, and they all have different types.
            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final  boolean mNotifyOnDeviceNearby\nprivate final  long mTimeApprovedMs\npublic  int getUserId()\nprivate  java.lang.String timeApprovedMsToString()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
        final Set<String> types = new HashSet<>(ids.size());
    @Deprecated
        for (int i = ids.size() - 1; i >= 0; i--) {
    private void __metadata() {}
            final DeviceId deviceId = ids.get(i);

            if (deviceId == null) throw new IllegalArgumentException("DeviceId must not be null");

            if (!types.add(deviceId.getType())) {
    //@formatter:on
                throw new IllegalArgumentException(
    // End of generated code
                        "DeviceId cannot have multiple IDs of the same type");

            }
        }
    }
}
}
+127 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.companion;

import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.Objects;

/**
 * The class represents free-form ID of a companion device.
 *
 * Since companion devices may have multiple IDs of different type at the same time
 * (eg. a MAC address and a Serial Number), this class not only stores the ID itself, it also stores
 * the type of the ID.
 * Both the type of the ID and its actual value are represented as {@link String}-s.
 *
 * Examples of device IDs:
 *  - "mac_address: f0:18:98:b3:fd:2e"
 *  - "ip_address: 128.121.35.200"
 *  - "imei: 352932100034923 / 44"
 *  - "serial_number: 96141FFAZ000B7"
 *  - "meid_hex: 35293210003492"
 *  - "meid_dic: 08918 92240 0001 3548"
 *
 * @hide
 * TODO(b/1979395): un-hide when implementing public APIs that use this class.
 */
public final class DeviceId implements Parcelable {
    public static final String TYPE_MAC_ADDRESS = "mac_address";

    private final @NonNull String mType;
    private final @NonNull String mValue;

    /**
     * @param type type of the ID. Non-empty. Max length - 16 characters.
     * @param value the ID. Non-empty. Max length - 48 characters.
     * @throws IllegalArgumentException if either {@param type} or {@param value} is empty or
     *         exceeds its max allowed length.
     */
    public DeviceId(@NonNull String type, @NonNull String value) {
        if (type.isEmpty() || value.isEmpty()) {
            throw new IllegalArgumentException("'type' and 'value' should not be empty");
        }
        this.mType = type;
        this.mValue = value;
    }

    /**
     * @return the type of the ID.
     */
    public @NonNull String getType() {
        return mType;
    }

    /**
     * @return the ID.
     */
    public @NonNull String getValue() {
        return mValue;
    }

    @Override
    public String toString() {
        return "DeviceId{"
                + "type='" + mType + '\''
                + ", value='" + mValue + '\''
                + '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof DeviceId)) return false;
        DeviceId deviceId = (DeviceId) o;
        return Objects.equals(mType, deviceId.mType) && Objects.equals(mValue,
                deviceId.mValue);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mType, mValue);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString(mType);
        dest.writeString(mValue);
    }

    private DeviceId(@NonNull Parcel in) {
        mType = in.readString();
        mValue = in.readString();
    }

    public static final @NonNull Creator<DeviceId> CREATOR = new Creator<DeviceId>() {
        @Override
        public DeviceId createFromParcel(@NonNull Parcel in) {
            return new DeviceId(in);
        }

        @Override
        public DeviceId[] newArray(int size) {
            return new DeviceId[size];
        }
    };
}
+36 −23
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.companion.DeviceId.TYPE_MAC_ADDRESS;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;


@@ -60,6 +61,7 @@ import android.bluetooth.le.ScanSettings;
import android.companion.Association;
import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.companion.CompanionDeviceManager;
import android.companion.DeviceId;
import android.companion.DeviceNotAssociatedException;
import android.companion.DeviceNotAssociatedException;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceManager;
import android.companion.ICompanionDeviceManager;
@@ -447,9 +449,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind


            }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> {
            }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> {
                if (err == null) {
                if (err == null) {
                    Association association = new Association(userId, deviceAddress, callingPackage,
                    createAssociationInternal(
                            deviceProfile, false, System.currentTimeMillis());
                            userId, deviceAddress, callingPackage, deviceProfile);
                    addAssociation(association, userId);
                } else {
                } else {
                    Slog.e(LOG_TAG, "Failed to discover device(s)", err);
                    Slog.e(LOG_TAG, "Failed to discover device(s)", err);
                    callback.onFailure("No devices found: " + err.getMessage());
                    callback.onFailure("No devices found: " + err.getMessage());
@@ -646,16 +647,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
            updateAssociations(associations -> map(associations, association -> {
            updateAssociations(associations -> map(associations, association -> {
                if (Objects.equals(association.getPackageName(), packageName)
                if (Objects.equals(association.getPackageName(), packageName)
                        && Objects.equals(association.getDeviceMacAddress(), deviceAddress)) {
                        && Objects.equals(association.getDeviceMacAddress(), deviceAddress)) {
                    return new Association(
                    association.setNotifyOnDeviceNearby(active);
                            association.getUserId(),
                            association.getDeviceMacAddress(),
                            association.getPackageName(),
                            association.getDeviceProfile(),
                            active /* notifyOnDeviceNearby */,
                            association.getTimeApprovedMs());
                } else {
                    return association;
                }
                }
                return association;
            }), userId);
            }), userId);


            restartBleScan();
            restartBleScan();
@@ -673,9 +667,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
            getContext().enforceCallingOrSelfPermission(
            getContext().enforceCallingOrSelfPermission(
                    android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation");
                    android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation");


            addAssociation(new Association(
            createAssociationInternal(userId, macAddress, packageName, null);
                    userId, macAddress, packageName, null, false,
                    System.currentTimeMillis()), userId);
        }
        }


        private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
        private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
@@ -779,7 +771,20 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        return Binder.getCallingUid() == Process.SYSTEM_UID;
        return Binder.getCallingUid() == Process.SYSTEM_UID;
    }
    }


    void addAssociation(Association association, int userId) {
    private void createAssociationInternal(
            int userId, String deviceMacAddress, String packageName, String deviceProfile) {
        // TODO: general a valid unique ID.
        final int associationId = 0;
        final Association association = new Association(
                associationId,
                userId,
                packageName,
                Arrays.asList(new DeviceId(TYPE_MAC_ADDRESS, deviceMacAddress)),
                deviceProfile,
                /* managedByCompanionApp */false,
                /* notifyOnDeviceNearby */ false ,
                System.currentTimeMillis());

        updateSpecialAccessPermissionForAssociatedPackage(association);
        updateSpecialAccessPermissionForAssociatedPackage(association);
        recordAssociation(association, userId);
        recordAssociation(association, userId);
    }
    }
@@ -1144,9 +1149,19 @@ public class CompanionDeviceManagerService extends SystemService implements Bind


                    if (appPackage == null || deviceAddress == null) continue;
                    if (appPackage == null || deviceAddress == null) continue;


                    result = ArrayUtils.add(result,
                    // TODO: either read or generate a valid ID
                            new Association(userId, deviceAddress, appPackage,
                    final int associationId = 0;
                                    profile, persistentGrants, timeApproved));
                    final Association association = new Association(
                            associationId,
                            userId,
                            appPackage,
                            Arrays.asList(new DeviceId(TYPE_MAC_ADDRESS, deviceAddress)),
                            profile,
                            /* managedByCompanionApp */false,
                            /* notifyOnDeviceNearby */ persistentGrants ,
                            System.currentTimeMillis());

                    result = ArrayUtils.add(result, association);
                }
                }
                return result;
                return result;
            } catch (XmlPullParserException | IOException e) {
            } catch (XmlPullParserException | IOException e) {
@@ -1504,10 +1519,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind


                    case "associate": {
                    case "associate": {
                        int userId = getNextArgInt();
                        int userId = getNextArgInt();
                        String pkg = getNextArgRequired();
                        String packageName = getNextArgRequired();
                        String address = getNextArgRequired();
                        String address = getNextArgRequired();
                        addAssociation(new Association(userId, address, pkg, null, false,
                        createAssociationInternal(userId, address, packageName, null);
                                System.currentTimeMillis()), userId);
                    }
                    }
                    break;
                    break;


@@ -1548,7 +1562,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        }
        }
    }
    }



    private class BluetoothDeviceConnectedListener
    private class BluetoothDeviceConnectedListener
            extends BluetoothAdapter.BluetoothConnectionCallback {
            extends BluetoothAdapter.BluetoothConnectionCallback {
        @Override
        @Override