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 56 private final boolean mSelfManaged; 57 private final boolean mNotifyOnDeviceNearby; 58 59 /** 60 * Indicates that the association has been revoked (removed), but we keep the association 61 * record for final clean up (e.g. removing the app from the list of the role holders). 62 * 63 * @see CompanionDeviceManager#disassociate(int) 64 */ 65 private final boolean mRevoked; 66 private final long mTimeApprovedMs; 67 /** 68 * A long value indicates the last time connected reported by selfManaged devices 69 * Default value is Long.MAX_VALUE. 70 */ 71 private final long mLastTimeConnectedMs; 72 73 /** 74 * Creates a new Association. 75 * Only to be used by the CompanionDeviceManagerService. 76 * 77 * @hide 78 */ AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, @Nullable MacAddress macAddress, @Nullable CharSequence displayName, @Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked, long timeApprovedMs, long lastTimeConnectedMs)79 public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, 80 @Nullable MacAddress macAddress, @Nullable CharSequence displayName, 81 @Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby, 82 boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) { 83 if (id <= 0) { 84 throw new IllegalArgumentException("Association ID should be greater than 0"); 85 } 86 if (macAddress == null && displayName == null) { 87 throw new IllegalArgumentException("MAC address and the Display Name must NOT be null " 88 + "at the same time"); 89 } 90 91 mId = id; 92 93 mUserId = userId; 94 mPackageName = packageName; 95 96 mDeviceMacAddress = macAddress; 97 mDisplayName = displayName; 98 mDeviceProfile = deviceProfile; 99 100 mSelfManaged = selfManaged; 101 mNotifyOnDeviceNearby = notifyOnDeviceNearby; 102 mRevoked = revoked; 103 mTimeApprovedMs = timeApprovedMs; 104 mLastTimeConnectedMs = lastTimeConnectedMs; 105 } 106 107 /** 108 * @return the unique ID of this association record. 109 */ getId()110 public int getId() { 111 return mId; 112 } 113 114 /** 115 * @return the ID of the user who "owns" this association. 116 * @hide 117 */ getUserId()118 public @UserIdInt int getUserId() { 119 return mUserId; 120 } 121 122 /** 123 * @return the package name of the app which this association refers to. 124 * @hide 125 */ 126 @SystemApi getPackageName()127 public @NonNull String getPackageName() { 128 return mPackageName; 129 } 130 131 /** 132 * @return the MAC address of the device. 133 */ getDeviceMacAddress()134 public @Nullable MacAddress getDeviceMacAddress() { 135 return mDeviceMacAddress; 136 } 137 138 /** @hide */ getDeviceMacAddressAsString()139 public @Nullable String getDeviceMacAddressAsString() { 140 return mDeviceMacAddress != null ? mDeviceMacAddress.toString().toUpperCase() : null; 141 } 142 143 /** 144 * @return the display name of the companion device (optionally) provided by the companion 145 * application. 146 * 147 * @see AssociationRequest.Builder#setDisplayName(CharSequence) 148 */ getDisplayName()149 public @Nullable CharSequence getDisplayName() { 150 return mDisplayName; 151 } 152 153 /** 154 * @return the companion device profile used when establishing this 155 * association, or {@code null} if no specific profile was used. 156 * @see AssociationRequest.Builder#setDeviceProfile(String) 157 */ getDeviceProfile()158 public @Nullable String getDeviceProfile() { 159 return mDeviceProfile; 160 } 161 162 /** 163 * @return whether the association is managed by the companion application it belongs to. 164 * @see AssociationRequest.Builder#setSelfManaged(boolean) 165 * @hide 166 */ 167 @SystemApi isSelfManaged()168 public boolean isSelfManaged() { 169 return mSelfManaged; 170 } 171 172 /** @hide */ isNotifyOnDeviceNearby()173 public boolean isNotifyOnDeviceNearby() { 174 return mNotifyOnDeviceNearby; 175 } 176 177 /** @hide */ getTimeApprovedMs()178 public long getTimeApprovedMs() { 179 return mTimeApprovedMs; 180 } 181 182 /** @hide */ belongsToPackage(@serIdInt int userId, String packageName)183 public boolean belongsToPackage(@UserIdInt int userId, String packageName) { 184 return mUserId == userId && Objects.equals(mPackageName, packageName); 185 } 186 187 /** 188 * @return if the association has been revoked (removed). 189 * @hide 190 */ isRevoked()191 public boolean isRevoked() { 192 return mRevoked; 193 } 194 195 /** 196 * @return the last time self reported disconnected for selfManaged only. 197 * @hide 198 */ getLastTimeConnectedMs()199 public Long getLastTimeConnectedMs() { 200 return mLastTimeConnectedMs; 201 } 202 203 /** 204 * Utility method for checking if the association represents a device with the given MAC 205 * address. 206 * 207 * @return {@code false} if the association is "self-managed". 208 * {@code false} if the {@code addr} is {@code null} or is not a valid MAC address. 209 * Otherwise - the result of {@link MacAddress#equals(Object)} 210 * 211 * @hide 212 */ isLinkedTo(@ullable String addr)213 public boolean isLinkedTo(@Nullable String addr) { 214 if (mSelfManaged) return false; 215 216 if (addr == null) return false; 217 218 final MacAddress macAddress; 219 try { 220 macAddress = MacAddress.fromString(addr); 221 } catch (IllegalArgumentException e) { 222 return false; 223 } 224 return macAddress.equals(mDeviceMacAddress); 225 } 226 227 /** 228 * Utility method to be used by CdmService only. 229 * 230 * @return whether CdmService should bind the companion application that "owns" this association 231 * when the device is present. 232 * 233 * @hide 234 */ shouldBindWhenPresent()235 public boolean shouldBindWhenPresent() { 236 return mNotifyOnDeviceNearby || mSelfManaged; 237 } 238 239 /** @hide */ toShortString()240 public @NonNull String toShortString() { 241 final StringBuilder sb = new StringBuilder(); 242 sb.append("id=").append(mId); 243 if (mDeviceMacAddress != null) { 244 sb.append(", addr=").append(getDeviceMacAddressAsString()); 245 } 246 if (mSelfManaged) { 247 sb.append(", self-managed"); 248 } 249 sb.append(", pkg=u").append(mUserId).append('/').append(mPackageName); 250 return sb.toString(); 251 } 252 253 @Override toString()254 public String toString() { 255 return "Association{" 256 + "mId=" + mId 257 + ", mUserId=" + mUserId 258 + ", mPackageName='" + mPackageName + '\'' 259 + ", mDeviceMacAddress=" + mDeviceMacAddress 260 + ", mDisplayName='" + mDisplayName + '\'' 261 + ", mDeviceProfile='" + mDeviceProfile + '\'' 262 + ", mSelfManaged=" + mSelfManaged 263 + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby 264 + ", mRevoked=" + mRevoked 265 + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs) 266 + ", mLastTimeConnectedMs=" + ( 267 mLastTimeConnectedMs == Long.MAX_VALUE 268 ? LAST_TIME_CONNECTED_NONE : new Date(mLastTimeConnectedMs)) 269 + '}'; 270 } 271 272 @Override equals(Object o)273 public boolean equals(Object o) { 274 if (this == o) return true; 275 if (!(o instanceof AssociationInfo)) return false; 276 final AssociationInfo that = (AssociationInfo) o; 277 return mId == that.mId 278 && mUserId == that.mUserId 279 && mSelfManaged == that.mSelfManaged 280 && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby 281 && mRevoked == that.mRevoked 282 && mTimeApprovedMs == that.mTimeApprovedMs 283 && mLastTimeConnectedMs == that.mLastTimeConnectedMs 284 && Objects.equals(mPackageName, that.mPackageName) 285 && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress) 286 && Objects.equals(mDisplayName, that.mDisplayName) 287 && Objects.equals(mDeviceProfile, that.mDeviceProfile); 288 } 289 290 @Override hashCode()291 public int hashCode() { 292 return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName, 293 mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs, 294 mLastTimeConnectedMs); 295 } 296 297 @Override describeContents()298 public int describeContents() { 299 return 0; 300 } 301 302 @Override writeToParcel(@onNull Parcel dest, int flags)303 public void writeToParcel(@NonNull Parcel dest, int flags) { 304 dest.writeInt(mId); 305 306 dest.writeInt(mUserId); 307 dest.writeString(mPackageName); 308 309 dest.writeTypedObject(mDeviceMacAddress, 0); 310 dest.writeCharSequence(mDisplayName); 311 dest.writeString(mDeviceProfile); 312 313 dest.writeBoolean(mSelfManaged); 314 dest.writeBoolean(mNotifyOnDeviceNearby); 315 dest.writeBoolean(mRevoked); 316 dest.writeLong(mTimeApprovedMs); 317 dest.writeLong(mLastTimeConnectedMs); 318 } 319 AssociationInfo(@onNull Parcel in)320 private AssociationInfo(@NonNull Parcel in) { 321 mId = in.readInt(); 322 323 mUserId = in.readInt(); 324 mPackageName = in.readString(); 325 326 mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR); 327 mDisplayName = in.readCharSequence(); 328 mDeviceProfile = in.readString(); 329 330 mSelfManaged = in.readBoolean(); 331 mNotifyOnDeviceNearby = in.readBoolean(); 332 mRevoked = in.readBoolean(); 333 mTimeApprovedMs = in.readLong(); 334 mLastTimeConnectedMs = in.readLong(); 335 } 336 337 @NonNull 338 public static final Parcelable.Creator<AssociationInfo> CREATOR = 339 new Parcelable.Creator<AssociationInfo>() { 340 @Override 341 public AssociationInfo[] newArray(int size) { 342 return new AssociationInfo[size]; 343 } 344 345 @Override 346 public AssociationInfo createFromParcel(@NonNull Parcel in) { 347 return new AssociationInfo(in); 348 } 349 }; 350 351 /** 352 * Use this method to obtain a builder that you can use to create a copy of the 353 * given {@link AssociationInfo} with modified values of {@code mLastTimeConnected} 354 * or {@code mNotifyOnDeviceNearby}. 355 * <p> 356 * Note that you <b>must</b> call either {@link Builder#setLastTimeConnected(long) 357 * setLastTimeConnected} or {@link Builder#setNotifyOnDeviceNearby(boolean) 358 * setNotifyOnDeviceNearby} before you will be able to call {@link Builder#build() build}. 359 * 360 * This is ensured statically at compile time. 361 * 362 * @hide 363 */ 364 @NonNull builder(@onNull AssociationInfo info)365 public static NonActionableBuilder builder(@NonNull AssociationInfo info) { 366 return new Builder(info); 367 } 368 369 /** 370 * @hide 371 */ 372 public static final class Builder implements NonActionableBuilder { 373 @NonNull 374 private final AssociationInfo mOriginalInfo; 375 private boolean mNotifyOnDeviceNearby; 376 private boolean mRevoked; 377 private long mLastTimeConnectedMs; 378 Builder(@onNull AssociationInfo info)379 private Builder(@NonNull AssociationInfo info) { 380 mOriginalInfo = info; 381 mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; 382 mRevoked = info.mRevoked; 383 mLastTimeConnectedMs = info.mLastTimeConnectedMs; 384 } 385 386 /** 387 * Should only be used by the CompanionDeviceManagerService. 388 * @hide 389 */ 390 @Override 391 @NonNull setLastTimeConnected(long lastTimeConnectedMs)392 public Builder setLastTimeConnected(long lastTimeConnectedMs) { 393 if (lastTimeConnectedMs < 0) { 394 throw new IllegalArgumentException( 395 "lastTimeConnectedMs must not be negative! (Given " + lastTimeConnectedMs 396 + " )"); 397 } 398 mLastTimeConnectedMs = lastTimeConnectedMs; 399 return this; 400 } 401 402 /** 403 * Should only be used by the CompanionDeviceManagerService. 404 * @hide 405 */ 406 @Override 407 @NonNull setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby)408 public Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) { 409 mNotifyOnDeviceNearby = notifyOnDeviceNearby; 410 return this; 411 } 412 413 /** 414 * Should only be used by the CompanionDeviceManagerService. 415 * @hide 416 */ 417 @Override 418 @NonNull setRevoked(boolean revoked)419 public Builder setRevoked(boolean revoked) { 420 mRevoked = revoked; 421 return this; 422 } 423 424 /** 425 * @hide 426 */ 427 @NonNull build()428 public AssociationInfo build() { 429 return new AssociationInfo( 430 mOriginalInfo.mId, 431 mOriginalInfo.mUserId, 432 mOriginalInfo.mPackageName, 433 mOriginalInfo.mDeviceMacAddress, 434 mOriginalInfo.mDisplayName, 435 mOriginalInfo.mDeviceProfile, 436 mOriginalInfo.mSelfManaged, 437 mNotifyOnDeviceNearby, 438 mRevoked, 439 mOriginalInfo.mTimeApprovedMs, 440 mLastTimeConnectedMs 441 ); 442 } 443 } 444 445 /** 446 * This interface is returned from the 447 * {@link AssociationInfo#builder(android.companion.AssociationInfo) builder} entry point 448 * to indicate that this builder is not yet in a state that can produce a meaningful 449 * {@link AssociationInfo} object that is different from the one originally passed in. 450 * 451 * <p> 452 * Only by calling one of the setter methods is this builder turned into one where calling 453 * {@link Builder#build() build()} makes sense. 454 * 455 * @hide 456 */ 457 public interface NonActionableBuilder { 458 /** 459 * Should only be used by the CompanionDeviceManagerService. 460 * @hide 461 */ 462 @NonNull setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby)463 Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby); 464 465 /** 466 * Should only be used by the CompanionDeviceManagerService. 467 * @hide 468 */ 469 @NonNull setLastTimeConnected(long lastTimeConnectedMs)470 Builder setLastTimeConnected(long lastTimeConnectedMs); 471 472 /** 473 * Should only be used by the CompanionDeviceManagerService. 474 * @hide 475 */ 476 @NonNull setRevoked(boolean revoked)477 Builder setRevoked(boolean revoked); 478 } 479 } 480