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.FlaggedApi; 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SuppressLint; 22 import android.annotation.SystemApi; 23 import android.annotation.TestApi; 24 import android.annotation.UserIdInt; 25 import android.net.MacAddress; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 29 import java.util.Date; 30 import java.util.Objects; 31 32 /** 33 * Details for a specific "association" that has been established between an app and companion 34 * device. 35 * <p> 36 * An association gives an app the ability to interact with a companion device without needing to 37 * acquire broader runtime permissions. An association only exists after the user has confirmed that 38 * an app should have access to a companion device. 39 */ 40 public final class AssociationInfo implements Parcelable { 41 /** 42 * A String indicates the selfManaged device is not connected. 43 */ 44 private static final String LAST_TIME_CONNECTED_NONE = "None"; 45 /** 46 * A unique ID of this Association record. 47 * Disclosed to the clients (i.e. companion applications) for referring to this record (e.g. in 48 * {@code disassociate()} API call). 49 */ 50 private final int mId; 51 @UserIdInt 52 private final int mUserId; 53 @NonNull 54 private final String mPackageName; 55 @Nullable 56 private final String mTag; 57 @Nullable 58 private final MacAddress mDeviceMacAddress; 59 @Nullable 60 private final CharSequence mDisplayName; 61 @Nullable 62 private final String mDeviceProfile; 63 @Nullable 64 private final AssociatedDevice mAssociatedDevice; 65 private final boolean mSelfManaged; 66 private final boolean mNotifyOnDeviceNearby; 67 /** 68 * Indicates that the association has been revoked (removed), but we keep the association 69 * record for final clean up (e.g. removing the app from the list of the role holders). 70 * 71 * @see CompanionDeviceManager#disassociate(int) 72 */ 73 private final boolean mRevoked; 74 /** 75 * Indicates that the association is waiting for its corresponding companion app to be installed 76 * before it can be added to CDM. This is likely because it was restored onto the device from a 77 * backup. 78 */ 79 private final boolean mPending; 80 private final long mTimeApprovedMs; 81 /** 82 * A long value indicates the last time connected reported by selfManaged devices 83 * Default value is Long.MAX_VALUE. 84 */ 85 private final long mLastTimeConnectedMs; 86 private final int mSystemDataSyncFlags; 87 88 /** 89 * Creates a new Association. 90 * 91 * @hide 92 */ 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)93 public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, 94 @Nullable String tag, @Nullable MacAddress macAddress, 95 @Nullable CharSequence displayName, @Nullable String deviceProfile, 96 @Nullable AssociatedDevice associatedDevice, boolean selfManaged, 97 boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs, 98 long lastTimeConnectedMs, int systemDataSyncFlags) { 99 if (id <= 0) { 100 throw new IllegalArgumentException("Association ID should be greater than 0"); 101 } 102 if (macAddress == null && displayName == null) { 103 throw new IllegalArgumentException("MAC address and the Display Name must NOT be null " 104 + "at the same time"); 105 } 106 107 mId = id; 108 mUserId = userId; 109 mPackageName = packageName; 110 mDeviceMacAddress = macAddress; 111 mDisplayName = displayName; 112 mTag = tag; 113 mDeviceProfile = deviceProfile; 114 mAssociatedDevice = associatedDevice; 115 mSelfManaged = selfManaged; 116 mNotifyOnDeviceNearby = notifyOnDeviceNearby; 117 mRevoked = revoked; 118 mPending = pending; 119 mTimeApprovedMs = timeApprovedMs; 120 mLastTimeConnectedMs = lastTimeConnectedMs; 121 mSystemDataSyncFlags = systemDataSyncFlags; 122 } 123 124 /** 125 * @return the unique ID of this association record. 126 */ getId()127 public int getId() { 128 return mId; 129 } 130 131 /** 132 * @return the ID of the user who "owns" this association. 133 * @hide 134 */ 135 @UserIdInt getUserId()136 public int getUserId() { 137 return mUserId; 138 } 139 140 /** 141 * @return the package name of the app which this association refers to. 142 * @hide 143 */ 144 @SystemApi 145 @NonNull getPackageName()146 public String getPackageName() { 147 return mPackageName; 148 } 149 150 /** 151 * @return the tag of this association. 152 * @see CompanionDeviceManager#setAssociationTag(int, String) 153 */ 154 @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG) 155 @Nullable getTag()156 public String getTag() { 157 return mTag; 158 } 159 160 /** 161 * @return the MAC address of the device. 162 */ 163 @Nullable getDeviceMacAddress()164 public MacAddress getDeviceMacAddress() { 165 return mDeviceMacAddress; 166 } 167 168 /** @hide */ 169 @Nullable getDeviceMacAddressAsString()170 public String getDeviceMacAddressAsString() { 171 return mDeviceMacAddress != null ? mDeviceMacAddress.toString().toUpperCase() : null; 172 } 173 174 /** 175 * @return the display name of the companion device (optionally) provided by the companion 176 * application. 177 * 178 * @see AssociationRequest.Builder#setDisplayName(CharSequence) 179 */ 180 @Nullable getDisplayName()181 public CharSequence getDisplayName() { 182 return mDisplayName; 183 } 184 185 /** 186 * @return the companion device profile used when establishing this 187 * association, or {@code null} if no specific profile was used. 188 * @see AssociationRequest.Builder#setDeviceProfile(String) 189 */ 190 @Nullable getDeviceProfile()191 public String getDeviceProfile() { 192 return mDeviceProfile; 193 } 194 195 /** 196 * Companion device that was associated. Note that this field is not persisted across sessions. 197 * Device can be one of the following types: 198 * 199 * <ul> 200 * <li>for classic Bluetooth - {@link AssociatedDevice#getBluetoothDevice()}</li> 201 * <li>for Bluetooth LE - {@link AssociatedDevice#getBleDevice()}</li> 202 * <li>for WiFi - {@link AssociatedDevice#getWifiDevice()}</li> 203 * </ul> 204 * 205 * @return the companion device that was associated, or {@code null} if the device is 206 * self-managed or this association info was retrieved from persistent storage. 207 */ 208 @Nullable getAssociatedDevice()209 public AssociatedDevice getAssociatedDevice() { 210 return mAssociatedDevice; 211 } 212 213 /** 214 * @return whether the association is managed by the companion application it belongs to. 215 * @see AssociationRequest.Builder#setSelfManaged(boolean) 216 */ 217 @SuppressLint("UnflaggedApi") // promoting from @SystemApi isSelfManaged()218 public boolean isSelfManaged() { 219 return mSelfManaged; 220 } 221 222 /** @hide */ isNotifyOnDeviceNearby()223 public boolean isNotifyOnDeviceNearby() { 224 return mNotifyOnDeviceNearby; 225 } 226 227 /** @hide */ getTimeApprovedMs()228 public long getTimeApprovedMs() { 229 return mTimeApprovedMs; 230 } 231 232 /** @hide */ belongsToPackage(@serIdInt int userId, String packageName)233 public boolean belongsToPackage(@UserIdInt int userId, String packageName) { 234 return mUserId == userId && Objects.equals(mPackageName, packageName); 235 } 236 237 /** 238 * @return if the association has been revoked (removed). 239 * @hide 240 */ isRevoked()241 public boolean isRevoked() { 242 return mRevoked; 243 } 244 245 /** 246 * @return true if the association is waiting for its corresponding app to be installed 247 * before it can be added to CDM. 248 * @hide 249 */ isPending()250 public boolean isPending() { 251 return mPending; 252 } 253 254 /** 255 * @return true if the association is not revoked nor pending 256 * @hide 257 */ isActive()258 public boolean isActive() { 259 return !mRevoked && !mPending; 260 } 261 262 /** 263 * @return the last time self reported disconnected for selfManaged only. 264 * @hide 265 */ getLastTimeConnectedMs()266 public long getLastTimeConnectedMs() { 267 return mLastTimeConnectedMs; 268 } 269 270 /** 271 * @return Enabled system data sync flags set via 272 * {@link CompanionDeviceManager#enableSystemDataSyncForTypes(int, int)} (int, int)} and 273 * {@link CompanionDeviceManager#disableSystemDataSyncForTypes(int, int)} (int, int)}. 274 * Or by default all flags are 1 (enabled). 275 */ getSystemDataSyncFlags()276 public int getSystemDataSyncFlags() { 277 return mSystemDataSyncFlags; 278 } 279 280 /** 281 * Utility method for checking if the association represents a device with the given MAC 282 * address. 283 * 284 * @return {@code false} if the association is "self-managed". 285 * {@code false} if the {@code addr} is {@code null} or is not a valid MAC address. 286 * Otherwise - the result of {@link MacAddress#equals(Object)} 287 * 288 * @hide 289 */ isLinkedTo(@ullable String addr)290 public boolean isLinkedTo(@Nullable String addr) { 291 if (mSelfManaged) return false; 292 293 if (addr == null) return false; 294 295 final MacAddress macAddress; 296 try { 297 macAddress = MacAddress.fromString(addr); 298 } catch (IllegalArgumentException e) { 299 return false; 300 } 301 return macAddress.equals(mDeviceMacAddress); 302 } 303 304 /** 305 * Utility method to be used by CdmService only. 306 * 307 * @return whether CdmService should bind the companion application that "owns" this association 308 * when the device is present. 309 * 310 * @hide 311 */ shouldBindWhenPresent()312 public boolean shouldBindWhenPresent() { 313 return mNotifyOnDeviceNearby || mSelfManaged; 314 } 315 316 /** @hide */ 317 @NonNull toShortString()318 public String toShortString() { 319 final StringBuilder sb = new StringBuilder(); 320 sb.append("id=").append(mId); 321 if (mDeviceMacAddress != null) { 322 sb.append(", addr=").append(getDeviceMacAddressAsString()); 323 } 324 if (mSelfManaged) { 325 sb.append(", self-managed"); 326 } 327 sb.append(", pkg=u").append(mUserId).append('/').append(mPackageName); 328 return sb.toString(); 329 } 330 331 @Override toString()332 public String toString() { 333 return "Association{" 334 + "mId=" + mId 335 + ", mUserId=" + mUserId 336 + ", mPackageName='" + mPackageName + '\'' 337 + ", mTag='" + mTag + '\'' 338 + ", mDeviceMacAddress=" + mDeviceMacAddress 339 + ", mDisplayName='" + mDisplayName + '\'' 340 + ", mDeviceProfile='" + mDeviceProfile + '\'' 341 + ", mSelfManaged=" + mSelfManaged 342 + ", mAssociatedDevice=" + mAssociatedDevice 343 + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby 344 + ", mRevoked=" + mRevoked 345 + ", mPending=" + mPending 346 + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs) 347 + ", mLastTimeConnectedMs=" + ( 348 mLastTimeConnectedMs == Long.MAX_VALUE 349 ? LAST_TIME_CONNECTED_NONE : new Date(mLastTimeConnectedMs)) 350 + ", mSystemDataSyncFlags=" + mSystemDataSyncFlags 351 + '}'; 352 } 353 354 @Override equals(Object o)355 public boolean equals(Object o) { 356 if (this == o) return true; 357 if (!(o instanceof AssociationInfo)) return false; 358 final AssociationInfo that = (AssociationInfo) o; 359 return mId == that.mId 360 && mUserId == that.mUserId 361 && mSelfManaged == that.mSelfManaged 362 && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby 363 && mRevoked == that.mRevoked 364 && mPending == that.mPending 365 && mTimeApprovedMs == that.mTimeApprovedMs 366 && mLastTimeConnectedMs == that.mLastTimeConnectedMs 367 && Objects.equals(mPackageName, that.mPackageName) 368 && Objects.equals(mTag, that.mTag) 369 && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress) 370 && Objects.equals(mDisplayName, that.mDisplayName) 371 && Objects.equals(mDeviceProfile, that.mDeviceProfile) 372 && Objects.equals(mAssociatedDevice, that.mAssociatedDevice) 373 && mSystemDataSyncFlags == that.mSystemDataSyncFlags; 374 } 375 376 @Override hashCode()377 public int hashCode() { 378 return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName, 379 mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, 380 mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags); 381 } 382 383 @Override describeContents()384 public int describeContents() { 385 return 0; 386 } 387 388 @Override writeToParcel(@onNull Parcel dest, int flags)389 public void writeToParcel(@NonNull Parcel dest, int flags) { 390 dest.writeInt(mId); 391 dest.writeInt(mUserId); 392 dest.writeString(mPackageName); 393 dest.writeString(mTag); 394 dest.writeTypedObject(mDeviceMacAddress, 0); 395 dest.writeCharSequence(mDisplayName); 396 dest.writeString(mDeviceProfile); 397 dest.writeTypedObject(mAssociatedDevice, 0); 398 dest.writeBoolean(mSelfManaged); 399 dest.writeBoolean(mNotifyOnDeviceNearby); 400 dest.writeBoolean(mRevoked); 401 dest.writeBoolean(mPending); 402 dest.writeLong(mTimeApprovedMs); 403 dest.writeLong(mLastTimeConnectedMs); 404 dest.writeInt(mSystemDataSyncFlags); 405 } 406 AssociationInfo(@onNull Parcel in)407 private AssociationInfo(@NonNull Parcel in) { 408 mId = in.readInt(); 409 mUserId = in.readInt(); 410 mPackageName = in.readString(); 411 mTag = in.readString(); 412 mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR); 413 mDisplayName = in.readCharSequence(); 414 mDeviceProfile = in.readString(); 415 mAssociatedDevice = in.readTypedObject(AssociatedDevice.CREATOR); 416 mSelfManaged = in.readBoolean(); 417 mNotifyOnDeviceNearby = in.readBoolean(); 418 mRevoked = in.readBoolean(); 419 mPending = in.readBoolean(); 420 mTimeApprovedMs = in.readLong(); 421 mLastTimeConnectedMs = in.readLong(); 422 mSystemDataSyncFlags = in.readInt(); 423 } 424 425 @NonNull 426 public static final Parcelable.Creator<AssociationInfo> CREATOR = 427 new Parcelable.Creator<AssociationInfo>() { 428 @Override 429 public AssociationInfo[] newArray(int size) { 430 return new AssociationInfo[size]; 431 } 432 433 @Override 434 public AssociationInfo createFromParcel(@NonNull Parcel in) { 435 return new AssociationInfo(in); 436 } 437 }; 438 439 /** 440 * Builder for {@link AssociationInfo} 441 * 442 * @hide 443 */ 444 @FlaggedApi(Flags.FLAG_NEW_ASSOCIATION_BUILDER) 445 @TestApi 446 public static final class Builder { 447 private final int mId; 448 private final int mUserId; 449 private final String mPackageName; 450 private String mTag; 451 private MacAddress mDeviceMacAddress; 452 private CharSequence mDisplayName; 453 private String mDeviceProfile; 454 private AssociatedDevice mAssociatedDevice; 455 private boolean mSelfManaged; 456 private boolean mNotifyOnDeviceNearby; 457 private boolean mRevoked; 458 private boolean mPending; 459 private long mTimeApprovedMs; 460 private long mLastTimeConnectedMs; 461 private int mSystemDataSyncFlags; 462 463 /** @hide */ 464 @TestApi Builder(int id, int userId, @NonNull String packageName)465 public Builder(int id, int userId, @NonNull String packageName) { 466 mId = id; 467 mUserId = userId; 468 mPackageName = packageName; 469 } 470 471 /** @hide */ 472 @TestApi Builder(@onNull AssociationInfo info)473 public Builder(@NonNull AssociationInfo info) { 474 mId = info.mId; 475 mUserId = info.mUserId; 476 mPackageName = info.mPackageName; 477 mTag = info.mTag; 478 mDeviceMacAddress = info.mDeviceMacAddress; 479 mDisplayName = info.mDisplayName; 480 mDeviceProfile = info.mDeviceProfile; 481 mAssociatedDevice = info.mAssociatedDevice; 482 mSelfManaged = info.mSelfManaged; 483 mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; 484 mRevoked = info.mRevoked; 485 mPending = info.mPending; 486 mTimeApprovedMs = info.mTimeApprovedMs; 487 mLastTimeConnectedMs = info.mLastTimeConnectedMs; 488 mSystemDataSyncFlags = info.mSystemDataSyncFlags; 489 } 490 491 /** 492 * This builder is used specifically to create a new association to be restored to a device 493 * that is potentially using a different user ID from the backed-up device. 494 * 495 * @hide 496 */ Builder(int id, int userId, @NonNull String packageName, AssociationInfo info)497 public Builder(int id, int userId, @NonNull String packageName, AssociationInfo info) { 498 mId = id; 499 mUserId = userId; 500 mPackageName = packageName; 501 mTag = info.mTag; 502 mDeviceMacAddress = info.mDeviceMacAddress; 503 mDisplayName = info.mDisplayName; 504 mDeviceProfile = info.mDeviceProfile; 505 mAssociatedDevice = info.mAssociatedDevice; 506 mSelfManaged = info.mSelfManaged; 507 mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; 508 mRevoked = info.mRevoked; 509 mPending = info.mPending; 510 mTimeApprovedMs = info.mTimeApprovedMs; 511 mLastTimeConnectedMs = info.mLastTimeConnectedMs; 512 mSystemDataSyncFlags = info.mSystemDataSyncFlags; 513 } 514 515 /** @hide */ 516 @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG) 517 @TestApi 518 @NonNull setTag(@ullable String tag)519 public Builder setTag(@Nullable String tag) { 520 mTag = tag; 521 return this; 522 } 523 524 /** @hide */ 525 @TestApi 526 @NonNull setDeviceMacAddress(@ullable MacAddress deviceMacAddress)527 public Builder setDeviceMacAddress(@Nullable MacAddress deviceMacAddress) { 528 mDeviceMacAddress = deviceMacAddress; 529 return this; 530 } 531 532 /** @hide */ 533 @TestApi 534 @NonNull setDisplayName(@ullable CharSequence displayName)535 public Builder setDisplayName(@Nullable CharSequence displayName) { 536 mDisplayName = displayName; 537 return this; 538 } 539 540 /** @hide */ 541 @TestApi 542 @NonNull setDeviceProfile(@ullable String deviceProfile)543 public Builder setDeviceProfile(@Nullable String deviceProfile) { 544 mDeviceProfile = deviceProfile; 545 return this; 546 } 547 548 /** @hide */ 549 @TestApi 550 @NonNull setAssociatedDevice(@ullable AssociatedDevice associatedDevice)551 public Builder setAssociatedDevice(@Nullable AssociatedDevice associatedDevice) { 552 mAssociatedDevice = associatedDevice; 553 return this; 554 } 555 556 /** @hide */ 557 @TestApi 558 @NonNull setSelfManaged(boolean selfManaged)559 public Builder setSelfManaged(boolean selfManaged) { 560 mSelfManaged = selfManaged; 561 return this; 562 } 563 564 /** @hide */ 565 @TestApi 566 @NonNull 567 @SuppressLint("MissingGetterMatchingBuilder") setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby)568 public Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) { 569 mNotifyOnDeviceNearby = notifyOnDeviceNearby; 570 return this; 571 } 572 573 /** @hide */ 574 @TestApi 575 @NonNull 576 @SuppressLint("MissingGetterMatchingBuilder") setRevoked(boolean revoked)577 public Builder setRevoked(boolean revoked) { 578 mRevoked = revoked; 579 return this; 580 } 581 582 /** @hide */ 583 @NonNull 584 @SuppressLint("MissingGetterMatchingBuilder") setPending(boolean pending)585 public Builder setPending(boolean pending) { 586 mPending = pending; 587 return this; 588 } 589 590 /** @hide */ 591 @TestApi 592 @NonNull 593 @SuppressLint("MissingGetterMatchingBuilder") setTimeApproved(long timeApprovedMs)594 public Builder setTimeApproved(long timeApprovedMs) { 595 if (timeApprovedMs < 0) { 596 throw new IllegalArgumentException("timeApprovedMs must be positive. Was given (" 597 + timeApprovedMs + ")"); 598 } 599 mTimeApprovedMs = timeApprovedMs; 600 return this; 601 } 602 603 /** @hide */ 604 @TestApi 605 @NonNull 606 @SuppressLint("MissingGetterMatchingBuilder") setLastTimeConnected(long lastTimeConnectedMs)607 public Builder setLastTimeConnected(long lastTimeConnectedMs) { 608 if (lastTimeConnectedMs < 0) { 609 throw new IllegalArgumentException( 610 "lastTimeConnectedMs must not be negative! (Given " + lastTimeConnectedMs 611 + " )"); 612 } 613 mLastTimeConnectedMs = lastTimeConnectedMs; 614 return this; 615 } 616 617 /** @hide */ 618 @TestApi 619 @NonNull setSystemDataSyncFlags(int flags)620 public Builder setSystemDataSyncFlags(int flags) { 621 mSystemDataSyncFlags = flags; 622 return this; 623 } 624 625 /** @hide */ 626 @TestApi 627 @NonNull build()628 public AssociationInfo build() { 629 if (mId <= 0) { 630 throw new IllegalArgumentException("Association ID should be greater than 0"); 631 } 632 if (mDeviceMacAddress == null && mDisplayName == null) { 633 throw new IllegalArgumentException("MAC address and the display name must NOT be " 634 + "null at the same time"); 635 } 636 return new AssociationInfo( 637 mId, 638 mUserId, 639 mPackageName, 640 mTag, 641 mDeviceMacAddress, 642 mDisplayName, 643 mDeviceProfile, 644 mAssociatedDevice, 645 mSelfManaged, 646 mNotifyOnDeviceNearby, 647 mRevoked, 648 mPending, 649 mTimeApprovedMs, 650 mLastTimeConnectedMs, 651 mSystemDataSyncFlags 652 ); 653 } 654 } 655 } 656