| /* |
| * Copyright (C) 2019 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.FlaggedApi; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SuppressLint; |
| import android.annotation.SystemApi; |
| import android.annotation.TestApi; |
| import android.annotation.UserIdInt; |
| import android.net.MacAddress; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| |
| import java.util.Date; |
| import java.util.Objects; |
| |
| /** |
| * Details for a specific "association" that has been established between an app and companion |
| * device. |
| * <p> |
| * An association gives an app the ability to interact with a companion device without needing to |
| * acquire broader runtime permissions. An association only exists after the user has confirmed that |
| * an app should have access to a companion device. |
| */ |
| public final class AssociationInfo implements Parcelable { |
| /** |
| * A String indicates the selfManaged device is not connected. |
| */ |
| private static final String LAST_TIME_CONNECTED_NONE = "None"; |
| /** |
| * A unique ID of this Association record. |
| * Disclosed to the clients (i.e. companion applications) for referring to this record (e.g. in |
| * {@code disassociate()} API call). |
| */ |
| private final int mId; |
| @UserIdInt |
| private final int mUserId; |
| @NonNull |
| private final String mPackageName; |
| @Nullable |
| private final String mTag; |
| @Nullable |
| private final MacAddress mDeviceMacAddress; |
| @Nullable |
| private final CharSequence mDisplayName; |
| @Nullable |
| private final String mDeviceProfile; |
| @Nullable |
| private final AssociatedDevice mAssociatedDevice; |
| private final boolean mSelfManaged; |
| private final boolean mNotifyOnDeviceNearby; |
| /** |
| * Indicates that the association has been revoked (removed), but we keep the association |
| * record for final clean up (e.g. removing the app from the list of the role holders). |
| * |
| * @see CompanionDeviceManager#disassociate(int) |
| */ |
| private final boolean mRevoked; |
| /** |
| * Indicates that the association is waiting for its corresponding companion app to be installed |
| * before it can be added to CDM. This is likely because it was restored onto the device from a |
| * backup. |
| */ |
| private final boolean mPending; |
| private final long mTimeApprovedMs; |
| /** |
| * A long value indicates the last time connected reported by selfManaged devices |
| * Default value is Long.MAX_VALUE. |
| */ |
| private final long mLastTimeConnectedMs; |
| private final int mSystemDataSyncFlags; |
| |
| /** |
| * Creates a new Association. |
| * |
| * @hide |
| */ |
| public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, |
| @Nullable String tag, @Nullable MacAddress macAddress, |
| @Nullable CharSequence displayName, @Nullable String deviceProfile, |
| @Nullable AssociatedDevice associatedDevice, boolean selfManaged, |
| boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs, |
| long lastTimeConnectedMs, int systemDataSyncFlags) { |
| if (id <= 0) { |
| throw new IllegalArgumentException("Association ID should be greater than 0"); |
| } |
| if (macAddress == null && displayName == null) { |
| throw new IllegalArgumentException("MAC address and the Display Name must NOT be null " |
| + "at the same time"); |
| } |
| |
| mId = id; |
| mUserId = userId; |
| mPackageName = packageName; |
| mDeviceMacAddress = macAddress; |
| mDisplayName = displayName; |
| mTag = tag; |
| mDeviceProfile = deviceProfile; |
| mAssociatedDevice = associatedDevice; |
| mSelfManaged = selfManaged; |
| mNotifyOnDeviceNearby = notifyOnDeviceNearby; |
| mRevoked = revoked; |
| mPending = pending; |
| mTimeApprovedMs = timeApprovedMs; |
| mLastTimeConnectedMs = lastTimeConnectedMs; |
| mSystemDataSyncFlags = systemDataSyncFlags; |
| } |
| |
| /** |
| * @return the unique ID of this association record. |
| */ |
| public int getId() { |
| return mId; |
| } |
| |
| /** |
| * @return the ID of the user who "owns" this association. |
| * @hide |
| */ |
| @UserIdInt |
| public int getUserId() { |
| return mUserId; |
| } |
| |
| /** |
| * @return the package name of the app which this association refers to. |
| * @hide |
| */ |
| @SystemApi |
| @NonNull |
| public String getPackageName() { |
| return mPackageName; |
| } |
| |
| /** |
| * @return the tag of this association. |
| * @see CompanionDeviceManager#setAssociationTag(int, String) |
| */ |
| @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG) |
| @Nullable |
| public String getTag() { |
| return mTag; |
| } |
| |
| /** |
| * @return the MAC address of the device. |
| */ |
| @Nullable |
| public MacAddress getDeviceMacAddress() { |
| return mDeviceMacAddress; |
| } |
| |
| /** @hide */ |
| @Nullable |
| public String getDeviceMacAddressAsString() { |
| return mDeviceMacAddress != null ? mDeviceMacAddress.toString().toUpperCase() : null; |
| } |
| |
| /** |
| * @return the display name of the companion device (optionally) provided by the companion |
| * application. |
| * |
| * @see AssociationRequest.Builder#setDisplayName(CharSequence) |
| */ |
| @Nullable |
| public CharSequence getDisplayName() { |
| return mDisplayName; |
| } |
| |
| /** |
| * @return the companion device profile used when establishing this |
| * association, or {@code null} if no specific profile was used. |
| * @see AssociationRequest.Builder#setDeviceProfile(String) |
| */ |
| @Nullable |
| public String getDeviceProfile() { |
| return mDeviceProfile; |
| } |
| |
| /** |
| * Companion device that was associated. Note that this field is not persisted across sessions. |
| * Device can be one of the following types: |
| * |
| * <ul> |
| * <li>for classic Bluetooth - {@link AssociatedDevice#getBluetoothDevice()}</li> |
| * <li>for Bluetooth LE - {@link AssociatedDevice#getBleDevice()}</li> |
| * <li>for WiFi - {@link AssociatedDevice#getWifiDevice()}</li> |
| * </ul> |
| * |
| * @return the companion device that was associated, or {@code null} if the device is |
| * self-managed or this association info was retrieved from persistent storage. |
| */ |
| @Nullable |
| public AssociatedDevice getAssociatedDevice() { |
| return mAssociatedDevice; |
| } |
| |
| /** |
| * @return whether the association is managed by the companion application it belongs to. |
| * @see AssociationRequest.Builder#setSelfManaged(boolean) |
| */ |
| @SuppressLint("UnflaggedApi") // promoting from @SystemApi |
| public boolean isSelfManaged() { |
| return mSelfManaged; |
| } |
| |
| /** @hide */ |
| public boolean isNotifyOnDeviceNearby() { |
| return mNotifyOnDeviceNearby; |
| } |
| |
| /** @hide */ |
| public long getTimeApprovedMs() { |
| return mTimeApprovedMs; |
| } |
| |
| /** @hide */ |
| public boolean belongsToPackage(@UserIdInt int userId, String packageName) { |
| return mUserId == userId && Objects.equals(mPackageName, packageName); |
| } |
| |
| /** |
| * @return if the association has been revoked (removed). |
| * @hide |
| */ |
| public boolean isRevoked() { |
| return mRevoked; |
| } |
| |
| /** |
| * @return true if the association is waiting for its corresponding app to be installed |
| * before it can be added to CDM. |
| * @hide |
| */ |
| public boolean isPending() { |
| return mPending; |
| } |
| |
| /** |
| * @return true if the association is not revoked nor pending |
| * @hide |
| */ |
| public boolean isActive() { |
| return !mRevoked && !mPending; |
| } |
| |
| /** |
| * @return the last time self reported disconnected for selfManaged only. |
| * @hide |
| */ |
| public long getLastTimeConnectedMs() { |
| return mLastTimeConnectedMs; |
| } |
| |
| /** |
| * @return Enabled system data sync flags set via |
| * {@link CompanionDeviceManager#enableSystemDataSyncForTypes(int, int)} (int, int)} and |
| * {@link CompanionDeviceManager#disableSystemDataSyncForTypes(int, int)} (int, int)}. |
| * Or by default all flags are 1 (enabled). |
| */ |
| public int getSystemDataSyncFlags() { |
| return mSystemDataSyncFlags; |
| } |
| |
| /** |
| * Utility method for checking if the association represents a device with the given MAC |
| * address. |
| * |
| * @return {@code false} if the association is "self-managed". |
| * {@code false} if the {@code addr} is {@code null} or is not a valid MAC address. |
| * Otherwise - the result of {@link MacAddress#equals(Object)} |
| * |
| * @hide |
| */ |
| public boolean isLinkedTo(@Nullable String addr) { |
| if (mSelfManaged) return false; |
| |
| if (addr == null) return false; |
| |
| final MacAddress macAddress; |
| try { |
| macAddress = MacAddress.fromString(addr); |
| } catch (IllegalArgumentException e) { |
| return false; |
| } |
| return macAddress.equals(mDeviceMacAddress); |
| } |
| |
| /** |
| * Utility method to be used by CdmService only. |
| * |
| * @return whether CdmService should bind the companion application that "owns" this association |
| * when the device is present. |
| * |
| * @hide |
| */ |
| public boolean shouldBindWhenPresent() { |
| return mNotifyOnDeviceNearby || mSelfManaged; |
| } |
| |
| /** @hide */ |
| @NonNull |
| public String toShortString() { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("id=").append(mId); |
| if (mDeviceMacAddress != null) { |
| sb.append(", addr=").append(getDeviceMacAddressAsString()); |
| } |
| if (mSelfManaged) { |
| sb.append(", self-managed"); |
| } |
| sb.append(", pkg=u").append(mUserId).append('/').append(mPackageName); |
| return sb.toString(); |
| } |
| |
| @Override |
| public String toString() { |
| return "Association{" |
| + "mId=" + mId |
| + ", mUserId=" + mUserId |
| + ", mPackageName='" + mPackageName + '\'' |
| + ", mTag='" + mTag + '\'' |
| + ", mDeviceMacAddress=" + mDeviceMacAddress |
| + ", mDisplayName='" + mDisplayName + '\'' |
| + ", mDeviceProfile='" + mDeviceProfile + '\'' |
| + ", mSelfManaged=" + mSelfManaged |
| + ", mAssociatedDevice=" + mAssociatedDevice |
| + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby |
| + ", mRevoked=" + mRevoked |
| + ", mPending=" + mPending |
| + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs) |
| + ", mLastTimeConnectedMs=" + ( |
| mLastTimeConnectedMs == Long.MAX_VALUE |
| ? LAST_TIME_CONNECTED_NONE : new Date(mLastTimeConnectedMs)) |
| + ", mSystemDataSyncFlags=" + mSystemDataSyncFlags |
| + '}'; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof AssociationInfo)) return false; |
| final AssociationInfo that = (AssociationInfo) o; |
| return mId == that.mId |
| && mUserId == that.mUserId |
| && mSelfManaged == that.mSelfManaged |
| && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby |
| && mRevoked == that.mRevoked |
| && mPending == that.mPending |
| && mTimeApprovedMs == that.mTimeApprovedMs |
| && mLastTimeConnectedMs == that.mLastTimeConnectedMs |
| && Objects.equals(mPackageName, that.mPackageName) |
| && Objects.equals(mTag, that.mTag) |
| && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress) |
| && Objects.equals(mDisplayName, that.mDisplayName) |
| && Objects.equals(mDeviceProfile, that.mDeviceProfile) |
| && Objects.equals(mAssociatedDevice, that.mAssociatedDevice) |
| && mSystemDataSyncFlags == that.mSystemDataSyncFlags; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName, |
| mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, |
| mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(@NonNull Parcel dest, int flags) { |
| dest.writeInt(mId); |
| dest.writeInt(mUserId); |
| dest.writeString(mPackageName); |
| dest.writeString(mTag); |
| dest.writeTypedObject(mDeviceMacAddress, 0); |
| dest.writeCharSequence(mDisplayName); |
| dest.writeString(mDeviceProfile); |
| dest.writeTypedObject(mAssociatedDevice, 0); |
| dest.writeBoolean(mSelfManaged); |
| dest.writeBoolean(mNotifyOnDeviceNearby); |
| dest.writeBoolean(mRevoked); |
| dest.writeBoolean(mPending); |
| dest.writeLong(mTimeApprovedMs); |
| dest.writeLong(mLastTimeConnectedMs); |
| dest.writeInt(mSystemDataSyncFlags); |
| } |
| |
| private AssociationInfo(@NonNull Parcel in) { |
| mId = in.readInt(); |
| mUserId = in.readInt(); |
| mPackageName = in.readString(); |
| mTag = in.readString(); |
| mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR); |
| mDisplayName = in.readCharSequence(); |
| mDeviceProfile = in.readString(); |
| mAssociatedDevice = in.readTypedObject(AssociatedDevice.CREATOR); |
| mSelfManaged = in.readBoolean(); |
| mNotifyOnDeviceNearby = in.readBoolean(); |
| mRevoked = in.readBoolean(); |
| mPending = in.readBoolean(); |
| mTimeApprovedMs = in.readLong(); |
| mLastTimeConnectedMs = in.readLong(); |
| mSystemDataSyncFlags = in.readInt(); |
| } |
| |
| @NonNull |
| public static final Parcelable.Creator<AssociationInfo> CREATOR = |
| new Parcelable.Creator<AssociationInfo>() { |
| @Override |
| public AssociationInfo[] newArray(int size) { |
| return new AssociationInfo[size]; |
| } |
| |
| @Override |
| public AssociationInfo createFromParcel(@NonNull Parcel in) { |
| return new AssociationInfo(in); |
| } |
| }; |
| |
| /** |
| * Builder for {@link AssociationInfo} |
| * |
| * @hide |
| */ |
| @FlaggedApi(Flags.FLAG_NEW_ASSOCIATION_BUILDER) |
| @TestApi |
| public static final class Builder { |
| private final int mId; |
| private final int mUserId; |
| private final String mPackageName; |
| private String mTag; |
| private MacAddress mDeviceMacAddress; |
| private CharSequence mDisplayName; |
| private String mDeviceProfile; |
| private AssociatedDevice mAssociatedDevice; |
| private boolean mSelfManaged; |
| private boolean mNotifyOnDeviceNearby; |
| private boolean mRevoked; |
| private boolean mPending; |
| private long mTimeApprovedMs; |
| private long mLastTimeConnectedMs; |
| private int mSystemDataSyncFlags; |
| |
| /** @hide */ |
| @TestApi |
| public Builder(int id, int userId, @NonNull String packageName) { |
| mId = id; |
| mUserId = userId; |
| mPackageName = packageName; |
| } |
| |
| /** @hide */ |
| @TestApi |
| public Builder(@NonNull AssociationInfo info) { |
| mId = info.mId; |
| mUserId = info.mUserId; |
| mPackageName = info.mPackageName; |
| mTag = info.mTag; |
| mDeviceMacAddress = info.mDeviceMacAddress; |
| mDisplayName = info.mDisplayName; |
| mDeviceProfile = info.mDeviceProfile; |
| mAssociatedDevice = info.mAssociatedDevice; |
| mSelfManaged = info.mSelfManaged; |
| mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; |
| mRevoked = info.mRevoked; |
| mPending = info.mPending; |
| mTimeApprovedMs = info.mTimeApprovedMs; |
| mLastTimeConnectedMs = info.mLastTimeConnectedMs; |
| mSystemDataSyncFlags = info.mSystemDataSyncFlags; |
| } |
| |
| /** |
| * This builder is used specifically to create a new association to be restored to a device |
| * that is potentially using a different user ID from the backed-up device. |
| * |
| * @hide |
| */ |
| public Builder(int id, int userId, @NonNull String packageName, AssociationInfo info) { |
| mId = id; |
| mUserId = userId; |
| mPackageName = packageName; |
| mTag = info.mTag; |
| mDeviceMacAddress = info.mDeviceMacAddress; |
| mDisplayName = info.mDisplayName; |
| mDeviceProfile = info.mDeviceProfile; |
| mAssociatedDevice = info.mAssociatedDevice; |
| mSelfManaged = info.mSelfManaged; |
| mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; |
| mRevoked = info.mRevoked; |
| mPending = info.mPending; |
| mTimeApprovedMs = info.mTimeApprovedMs; |
| mLastTimeConnectedMs = info.mLastTimeConnectedMs; |
| mSystemDataSyncFlags = info.mSystemDataSyncFlags; |
| } |
| |
| /** @hide */ |
| @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG) |
| @TestApi |
| @NonNull |
| public Builder setTag(@Nullable String tag) { |
| mTag = tag; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| public Builder setDeviceMacAddress(@Nullable MacAddress deviceMacAddress) { |
| mDeviceMacAddress = deviceMacAddress; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| public Builder setDisplayName(@Nullable CharSequence displayName) { |
| mDisplayName = displayName; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| public Builder setDeviceProfile(@Nullable String deviceProfile) { |
| mDeviceProfile = deviceProfile; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| public Builder setAssociatedDevice(@Nullable AssociatedDevice associatedDevice) { |
| mAssociatedDevice = associatedDevice; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| public Builder setSelfManaged(boolean selfManaged) { |
| mSelfManaged = selfManaged; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| @SuppressLint("MissingGetterMatchingBuilder") |
| public Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) { |
| mNotifyOnDeviceNearby = notifyOnDeviceNearby; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| @SuppressLint("MissingGetterMatchingBuilder") |
| public Builder setRevoked(boolean revoked) { |
| mRevoked = revoked; |
| return this; |
| } |
| |
| /** @hide */ |
| @NonNull |
| @SuppressLint("MissingGetterMatchingBuilder") |
| public Builder setPending(boolean pending) { |
| mPending = pending; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| @SuppressLint("MissingGetterMatchingBuilder") |
| public Builder setTimeApproved(long timeApprovedMs) { |
| if (timeApprovedMs < 0) { |
| throw new IllegalArgumentException("timeApprovedMs must be positive. Was given (" |
| + timeApprovedMs + ")"); |
| } |
| mTimeApprovedMs = timeApprovedMs; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| @SuppressLint("MissingGetterMatchingBuilder") |
| public Builder setLastTimeConnected(long lastTimeConnectedMs) { |
| if (lastTimeConnectedMs < 0) { |
| throw new IllegalArgumentException( |
| "lastTimeConnectedMs must not be negative! (Given " + lastTimeConnectedMs |
| + " )"); |
| } |
| mLastTimeConnectedMs = lastTimeConnectedMs; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| public Builder setSystemDataSyncFlags(int flags) { |
| mSystemDataSyncFlags = flags; |
| return this; |
| } |
| |
| /** @hide */ |
| @TestApi |
| @NonNull |
| public AssociationInfo build() { |
| if (mId <= 0) { |
| throw new IllegalArgumentException("Association ID should be greater than 0"); |
| } |
| if (mDeviceMacAddress == null && mDisplayName == null) { |
| throw new IllegalArgumentException("MAC address and the display name must NOT be " |
| + "null at the same time"); |
| } |
| return new AssociationInfo( |
| mId, |
| mUserId, |
| mPackageName, |
| mTag, |
| mDeviceMacAddress, |
| mDisplayName, |
| mDeviceProfile, |
| mAssociatedDevice, |
| mSelfManaged, |
| mNotifyOnDeviceNearby, |
| mRevoked, |
| mPending, |
| mTimeApprovedMs, |
| mLastTimeConnectedMs, |
| mSystemDataSyncFlags |
| ); |
| } |
| } |
| } |