1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.companion; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.annotation.SystemApi; 21 import android.annotation.UserIdInt; 22 import android.net.MacAddress; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 import java.util.Date; 27 import java.util.Objects; 28 29 /** 30 * Details for a specific "association" that has been established between an app and companion 31 * device. 32 * <p> 33 * An association gives an app the ability to interact with a companion device without needing to 34 * acquire broader runtime permissions. An association only exists after the user has confirmed that 35 * an app should have access to a companion device. 36 */ 37 public final class AssociationInfo implements Parcelable { 38 /** 39 * A String indicates the selfManaged device is not connected. 40 */ 41 private static final String LAST_TIME_CONNECTED_NONE = "None"; 42 /** 43 * A unique ID of this Association record. 44 * Disclosed to the clients (ie. companion applications) for referring to this record (eg. in 45 * {@code disassociate()} API call). 46 */ 47 private final int mId; 48 49 private final @UserIdInt int mUserId; 50 private final @NonNull String mPackageName; 51 52 private final @Nullable MacAddress mDeviceMacAddress; 53 private final @Nullable CharSequence mDisplayName; 54 private final @Nullable String mDeviceProfile; 55 private final @Nullable AssociatedDevice mAssociatedDevice; 56 57 private final boolean mSelfManaged; 58 private final boolean mNotifyOnDeviceNearby; 59 private final int mSystemDataSyncFlags; 60 61 /** 62 * Indicates that the association has been revoked (removed), but we keep the association 63 * record for final clean up (e.g. removing the app from the list of the role holders). 64 * 65 * @see CompanionDeviceManager#disassociate(int) 66 */ 67 private final boolean mRevoked; 68 private final long mTimeApprovedMs; 69 /** 70 * A long value indicates the last time connected reported by selfManaged devices 71 * Default value is Long.MAX_VALUE. 72 */ 73 private final long mLastTimeConnectedMs; 74 75 /** 76 * Creates a new Association. 77 * 78 * @hide 79 */ AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, @Nullable MacAddress macAddress, @Nullable CharSequence displayName, @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice, boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked, long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags)80 public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, 81 @Nullable MacAddress macAddress, @Nullable CharSequence displayName, 82 @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice, 83 boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked, 84 long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags) { 85 if (id <= 0) { 86 throw new IllegalArgumentException("Association ID should be greater than 0"); 87 } 88 if (macAddress == null && displayName == null) { 89 throw new IllegalArgumentException("MAC address and the Display Name must NOT be null " 90 + "at the same time"); 91 } 92 93 mId = id; 94 95 mUserId = userId; 96 mPackageName = packageName; 97 98 mDeviceMacAddress = macAddress; 99 mDisplayName = displayName; 100 mDeviceProfile = deviceProfile; 101 mAssociatedDevice = associatedDevice; 102 103 mSelfManaged = selfManaged; 104 mNotifyOnDeviceNearby = notifyOnDeviceNearby; 105 mRevoked = revoked; 106 mTimeApprovedMs = timeApprovedMs; 107 mLastTimeConnectedMs = lastTimeConnectedMs; 108 mSystemDataSyncFlags = systemDataSyncFlags; 109 } 110 111 /** 112 * @return the unique ID of this association record. 113 */ getId()114 public int getId() { 115 return mId; 116 } 117 118 /** 119 * @return the ID of the user who "owns" this association. 120 * @hide 121 */ getUserId()122 public @UserIdInt int getUserId() { 123 return mUserId; 124 } 125 126 /** 127 * @return the package name of the app which this association refers to. 128 * @hide 129 */ 130 @SystemApi getPackageName()131 public @NonNull String getPackageName() { 132 return mPackageName; 133 } 134 135 /** 136 * @return the MAC address of the device. 137 */ getDeviceMacAddress()138 public @Nullable MacAddress getDeviceMacAddress() { 139 return mDeviceMacAddress; 140 } 141 142 /** @hide */ getDeviceMacAddressAsString()143 public @Nullable String getDeviceMacAddressAsString() { 144 return mDeviceMacAddress != null ? mDeviceMacAddress.toString().toUpperCase() : null; 145 } 146 147 /** 148 * @return the display name of the companion device (optionally) provided by the companion 149 * application. 150 * 151 * @see AssociationRequest.Builder#setDisplayName(CharSequence) 152 */ getDisplayName()153 public @Nullable CharSequence getDisplayName() { 154 return mDisplayName; 155 } 156 157 /** 158 * @return the companion device profile used when establishing this 159 * association, or {@code null} if no specific profile was used. 160 * @see AssociationRequest.Builder#setDeviceProfile(String) 161 */ getDeviceProfile()162 public @Nullable String getDeviceProfile() { 163 return mDeviceProfile; 164 } 165 166 /** 167 * Companion device that was associated. Note that this field is not persisted across sessions. 168 * Device can be one of the following types: 169 * 170 * <ul> 171 * <li>for classic Bluetooth - {@link AssociatedDevice#getBluetoothDevice()}</li> 172 * <li>for Bluetooth LE - {@link AssociatedDevice#getBleDevice()}</li> 173 * <li>for WiFi - {@link AssociatedDevice#getWifiDevice()}</li> 174 * </ul> 175 * 176 * @return the companion device that was associated, or {@code null} if the device is 177 * self-managed or this association info was retrieved from persistent storage. 178 */ getAssociatedDevice()179 public @Nullable AssociatedDevice getAssociatedDevice() { 180 return mAssociatedDevice; 181 } 182 183 /** 184 * @return whether the association is managed by the companion application it belongs to. 185 * @see AssociationRequest.Builder#setSelfManaged(boolean) 186 * @hide 187 */ 188 @SystemApi isSelfManaged()189 public boolean isSelfManaged() { 190 return mSelfManaged; 191 } 192 193 /** @hide */ isNotifyOnDeviceNearby()194 public boolean isNotifyOnDeviceNearby() { 195 return mNotifyOnDeviceNearby; 196 } 197 198 /** @hide */ getTimeApprovedMs()199 public long getTimeApprovedMs() { 200 return mTimeApprovedMs; 201 } 202 203 /** @hide */ belongsToPackage(@serIdInt int userId, String packageName)204 public boolean belongsToPackage(@UserIdInt int userId, String packageName) { 205 return mUserId == userId && Objects.equals(mPackageName, packageName); 206 } 207 208 /** 209 * @return if the association has been revoked (removed). 210 * @hide 211 */ isRevoked()212 public boolean isRevoked() { 213 return mRevoked; 214 } 215 216 /** 217 * @return the last time self reported disconnected for selfManaged only. 218 * @hide 219 */ getLastTimeConnectedMs()220 public Long getLastTimeConnectedMs() { 221 return mLastTimeConnectedMs; 222 } 223 224 /** 225 * @return Enabled system data sync flags set via 226 * {@link CompanionDeviceManager#enableSystemDataSync(int, int)} and 227 * {@link CompanionDeviceManager#disableSystemDataSync(int, int)}. 228 * Or by default all flags are 1 (enabled). 229 */ getSystemDataSyncFlags()230 public int getSystemDataSyncFlags() { 231 return mSystemDataSyncFlags; 232 } 233 234 /** 235 * Utility method for checking if the association represents a device with the given MAC 236 * address. 237 * 238 * @return {@code false} if the association is "self-managed". 239 * {@code false} if the {@code addr} is {@code null} or is not a valid MAC address. 240 * Otherwise - the result of {@link MacAddress#equals(Object)} 241 * 242 * @hide 243 */ isLinkedTo(@ullable String addr)244 public boolean isLinkedTo(@Nullable String addr) { 245 if (mSelfManaged) return false; 246 247 if (addr == null) return false; 248 249 final MacAddress macAddress; 250 try { 251 macAddress = MacAddress.fromString(addr); 252 } catch (IllegalArgumentException e) { 253 return false; 254 } 255 return macAddress.equals(mDeviceMacAddress); 256 } 257 258 /** 259 * Utility method to be used by CdmService only. 260 * 261 * @return whether CdmService should bind the companion application that "owns" this association 262 * when the device is present. 263 * 264 * @hide 265 */ shouldBindWhenPresent()266 public boolean shouldBindWhenPresent() { 267 return mNotifyOnDeviceNearby || mSelfManaged; 268 } 269 270 /** @hide */ toShortString()271 public @NonNull String toShortString() { 272 final StringBuilder sb = new StringBuilder(); 273 sb.append("id=").append(mId); 274 if (mDeviceMacAddress != null) { 275 sb.append(", addr=").append(getDeviceMacAddressAsString()); 276 } 277 if (mSelfManaged) { 278 sb.append(", self-managed"); 279 } 280 sb.append(", pkg=u").append(mUserId).append('/').append(mPackageName); 281 return sb.toString(); 282 } 283 284 @Override toString()285 public String toString() { 286 return "Association{" 287 + "mId=" + mId 288 + ", mUserId=" + mUserId 289 + ", mPackageName='" + mPackageName + '\'' 290 + ", mDeviceMacAddress=" + mDeviceMacAddress 291 + ", mDisplayName='" + mDisplayName + '\'' 292 + ", mDeviceProfile='" + mDeviceProfile + '\'' 293 + ", mSelfManaged=" + mSelfManaged 294 + ", mAssociatedDevice=" + mAssociatedDevice 295 + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby 296 + ", mRevoked=" + mRevoked 297 + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs) 298 + ", mLastTimeConnectedMs=" + ( 299 mLastTimeConnectedMs == Long.MAX_VALUE 300 ? LAST_TIME_CONNECTED_NONE : new Date(mLastTimeConnectedMs)) 301 + ", mSystemDataSyncFlags=" + mSystemDataSyncFlags 302 + '}'; 303 } 304 305 @Override equals(Object o)306 public boolean equals(Object o) { 307 if (this == o) return true; 308 if (!(o instanceof AssociationInfo)) return false; 309 final AssociationInfo that = (AssociationInfo) o; 310 return mId == that.mId 311 && mUserId == that.mUserId 312 && mSelfManaged == that.mSelfManaged 313 && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby 314 && mRevoked == that.mRevoked 315 && mTimeApprovedMs == that.mTimeApprovedMs 316 && mLastTimeConnectedMs == that.mLastTimeConnectedMs 317 && Objects.equals(mPackageName, that.mPackageName) 318 && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress) 319 && Objects.equals(mDisplayName, that.mDisplayName) 320 && Objects.equals(mDeviceProfile, that.mDeviceProfile) 321 && Objects.equals(mAssociatedDevice, that.mAssociatedDevice) 322 && mSystemDataSyncFlags == that.mSystemDataSyncFlags; 323 } 324 325 @Override hashCode()326 public int hashCode() { 327 return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName, 328 mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, 329 mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags); 330 } 331 332 @Override describeContents()333 public int describeContents() { 334 return 0; 335 } 336 337 @Override writeToParcel(@onNull Parcel dest, int flags)338 public void writeToParcel(@NonNull Parcel dest, int flags) { 339 dest.writeInt(mId); 340 341 dest.writeInt(mUserId); 342 dest.writeString(mPackageName); 343 344 dest.writeTypedObject(mDeviceMacAddress, 0); 345 dest.writeCharSequence(mDisplayName); 346 dest.writeString(mDeviceProfile); 347 dest.writeTypedObject(mAssociatedDevice, 0); 348 349 dest.writeBoolean(mSelfManaged); 350 dest.writeBoolean(mNotifyOnDeviceNearby); 351 dest.writeBoolean(mRevoked); 352 dest.writeLong(mTimeApprovedMs); 353 dest.writeLong(mLastTimeConnectedMs); 354 dest.writeInt(mSystemDataSyncFlags); 355 } 356 AssociationInfo(@onNull Parcel in)357 private AssociationInfo(@NonNull Parcel in) { 358 mId = in.readInt(); 359 360 mUserId = in.readInt(); 361 mPackageName = in.readString(); 362 363 mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR); 364 mDisplayName = in.readCharSequence(); 365 mDeviceProfile = in.readString(); 366 mAssociatedDevice = in.readTypedObject(AssociatedDevice.CREATOR); 367 368 mSelfManaged = in.readBoolean(); 369 mNotifyOnDeviceNearby = in.readBoolean(); 370 mRevoked = in.readBoolean(); 371 mTimeApprovedMs = in.readLong(); 372 mLastTimeConnectedMs = in.readLong(); 373 mSystemDataSyncFlags = in.readInt(); 374 } 375 376 @NonNull 377 public static final Parcelable.Creator<AssociationInfo> CREATOR = 378 new Parcelable.Creator<AssociationInfo>() { 379 @Override 380 public AssociationInfo[] newArray(int size) { 381 return new AssociationInfo[size]; 382 } 383 384 @Override 385 public AssociationInfo createFromParcel(@NonNull Parcel in) { 386 return new AssociationInfo(in); 387 } 388 }; 389 390 /** 391 * Use this method to obtain a builder that you can use to create a copy of the 392 * given {@link AssociationInfo} with modified values of {@code mLastTimeConnected} 393 * or {@code mNotifyOnDeviceNearby}. 394 * <p> 395 * Note that you <b>must</b> call either {@link Builder#setLastTimeConnected(long) 396 * setLastTimeConnected} or {@link Builder#setNotifyOnDeviceNearby(boolean) 397 * setNotifyOnDeviceNearby} before you will be able to call {@link Builder#build() build}. 398 * 399 * This is ensured statically at compile time. 400 * 401 * @hide 402 */ 403 @NonNull builder(@onNull AssociationInfo info)404 public static NonActionableBuilder builder(@NonNull AssociationInfo info) { 405 return new Builder(info); 406 } 407 408 /** @hide */ 409 public static final class Builder implements NonActionableBuilder { 410 @NonNull 411 private final AssociationInfo mOriginalInfo; 412 private boolean mNotifyOnDeviceNearby; 413 private boolean mRevoked; 414 private long mLastTimeConnectedMs; 415 private int mSystemDataSyncFlags; 416 Builder(@onNull AssociationInfo info)417 private Builder(@NonNull AssociationInfo info) { 418 mOriginalInfo = info; 419 mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; 420 mRevoked = info.mRevoked; 421 mLastTimeConnectedMs = info.mLastTimeConnectedMs; 422 mSystemDataSyncFlags = info.mSystemDataSyncFlags; 423 } 424 425 /** @hide */ 426 @Override 427 @NonNull setLastTimeConnected(long lastTimeConnectedMs)428 public Builder setLastTimeConnected(long lastTimeConnectedMs) { 429 if (lastTimeConnectedMs < 0) { 430 throw new IllegalArgumentException( 431 "lastTimeConnectedMs must not be negative! (Given " + lastTimeConnectedMs 432 + " )"); 433 } 434 mLastTimeConnectedMs = lastTimeConnectedMs; 435 return this; 436 } 437 438 /** @hide */ 439 @Override 440 @NonNull setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby)441 public Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) { 442 mNotifyOnDeviceNearby = notifyOnDeviceNearby; 443 return this; 444 } 445 446 /** @hide */ 447 @Override 448 @NonNull setRevoked(boolean revoked)449 public Builder setRevoked(boolean revoked) { 450 mRevoked = revoked; 451 return this; 452 } 453 454 /** @hide */ 455 @Override 456 @NonNull setSystemDataSyncFlags(int flags)457 public Builder setSystemDataSyncFlags(int flags) { 458 mSystemDataSyncFlags = flags; 459 return this; 460 } 461 462 /** @hide */ 463 @NonNull build()464 public AssociationInfo build() { 465 return new AssociationInfo( 466 mOriginalInfo.mId, 467 mOriginalInfo.mUserId, 468 mOriginalInfo.mPackageName, 469 mOriginalInfo.mDeviceMacAddress, 470 mOriginalInfo.mDisplayName, 471 mOriginalInfo.mDeviceProfile, 472 mOriginalInfo.mAssociatedDevice, 473 mOriginalInfo.mSelfManaged, 474 mNotifyOnDeviceNearby, 475 mRevoked, 476 mOriginalInfo.mTimeApprovedMs, 477 mLastTimeConnectedMs, 478 mSystemDataSyncFlags 479 ); 480 } 481 } 482 483 /** 484 * This interface is returned from the 485 * {@link AssociationInfo#builder(android.companion.AssociationInfo) builder} entry point 486 * to indicate that this builder is not yet in a state that can produce a meaningful 487 * {@link AssociationInfo} object that is different from the one originally passed in. 488 * 489 * <p> 490 * Only by calling one of the setter methods is this builder turned into one where calling 491 * {@link Builder#build() build()} makes sense. 492 * 493 * @hide 494 */ 495 public interface NonActionableBuilder { 496 /** @hide */ 497 @NonNull setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby)498 Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby); 499 500 /** @hide */ 501 @NonNull setLastTimeConnected(long lastTimeConnectedMs)502 Builder setLastTimeConnected(long lastTimeConnectedMs); 503 504 /** @hide */ 505 @NonNull setRevoked(boolean revoked)506 Builder setRevoked(boolean revoked); 507 508 /** @hide */ 509 @NonNull setSystemDataSyncFlags(int flags)510 Builder setSystemDataSyncFlags(int flags); 511 } 512 } 513