1 /* 2 * Copyright (C) 2017 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 17 package android.companion; 18 19 import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED; 20 21 import static com.android.internal.util.CollectionUtils.emptyIfNull; 22 23 import static java.util.Objects.requireNonNull; 24 25 import android.Manifest; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.RequiresPermission; 29 import android.annotation.StringDef; 30 import android.annotation.UserIdInt; 31 import android.compat.annotation.UnsupportedAppUsage; 32 import android.os.Build; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.provider.OneTimeUseBuilder; 36 37 import com.android.internal.util.ArrayUtils; 38 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.Objects; 44 45 /** 46 * A request for the user to select a companion device to associate with. 47 * 48 * You can optionally set {@link Builder#addDeviceFilter filters} for which devices to show to the 49 * user to select from. 50 * The exact type and fields of the filter you can set depend on the 51 * medium type. See {@link Builder}'s static factory methods for specific protocols that are 52 * supported. 53 * 54 * You can also set {@link Builder#setSingleDevice single device} to request a popup with single 55 * device to be shown instead of a list to choose from 56 */ 57 public final class AssociationRequest implements Parcelable { 58 /** 59 * Device profile: watch. 60 * 61 * If specified, the current request may have a modified UI to highlight that the device being 62 * set up is a specific kind of device, and some extra permissions may be granted to the app 63 * as a result. 64 * 65 * Using it requires declaring uses-permission 66 * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH} in the manifest. 67 * 68 * <a href="{@docRoot}about/versions/12/features#cdm-profiles">Learn more</a> 69 * about device profiles. 70 * 71 * @see AssociationRequest.Builder#setDeviceProfile 72 */ 73 public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH"; 74 75 /** 76 * Device profile: glasses. 77 * 78 * If specified, the current request may have a modified UI to highlight that the device being 79 * set up is a glasses device, and some extra permissions may be granted to the app 80 * as a result. 81 * 82 * Using it requires declaring uses-permission 83 * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_GLASSES} in the manifest. 84 * 85 * @see AssociationRequest.Builder#setDeviceProfile 86 */ 87 @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_GLASSES) 88 public static final String DEVICE_PROFILE_GLASSES = "android.app.role.COMPANION_DEVICE_GLASSES"; 89 90 /** 91 * Device profile: a virtual display capable of rendering Android applications, and sending back 92 * input events. 93 * 94 * Only applications that have been granted 95 * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_APP_STREAMING} are allowed to 96 * request to be associated with such devices. 97 * 98 * @see AssociationRequest.Builder#setDeviceProfile 99 */ 100 @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING) 101 public static final String DEVICE_PROFILE_APP_STREAMING = 102 "android.app.role.COMPANION_DEVICE_APP_STREAMING"; 103 104 /** 105 * Device profile: a virtual device capable of rendering content from an Android host to a 106 * nearby device. 107 * 108 * Only applications that have been granted 109 * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING} 110 * are allowed to request to be associated with such devices. 111 * 112 * @see AssociationRequest.Builder#setDeviceProfile 113 */ 114 @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING) 115 public static final String DEVICE_PROFILE_NEARBY_DEVICE_STREAMING = 116 "android.app.role.COMPANION_DEVICE_NEARBY_DEVICE_STREAMING"; 117 118 /** 119 * Device profile: Android Automotive Projection 120 * 121 * Only applications that have been granted 122 * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION} are 123 * allowed to request to be associated with such devices. 124 * 125 * @see AssociationRequest.Builder#setDeviceProfile 126 */ 127 @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) 128 public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION = 129 "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"; 130 131 /** 132 * Device profile: Allows the companion app to access notification, recent photos and media for 133 * computer cross-device features. 134 * 135 * Only applications that have been granted 136 * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_COMPUTER} are allowed to 137 * request to be associated with such devices. 138 * 139 * @see AssociationRequest.Builder#setDeviceProfile 140 */ 141 @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER) 142 public static final String DEVICE_PROFILE_COMPUTER = 143 "android.app.role.COMPANION_DEVICE_COMPUTER"; 144 145 /** @hide */ 146 @Retention(RetentionPolicy.SOURCE) 147 @StringDef(value = { DEVICE_PROFILE_WATCH, DEVICE_PROFILE_COMPUTER, 148 DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, DEVICE_PROFILE_APP_STREAMING, 149 DEVICE_PROFILE_GLASSES, DEVICE_PROFILE_NEARBY_DEVICE_STREAMING }) 150 public @interface DeviceProfile {} 151 152 /** 153 * Whether only a single device should match the provided filter. 154 * 155 * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac 156 * address, bonded devices are also searched among. This allows to obtain the necessary app 157 * privileges even if the device is already paired. 158 */ 159 private final boolean mSingleDevice; 160 161 /** 162 * If set, only devices matching either of the given filters will be shown to the user 163 */ 164 @NonNull 165 private final List<DeviceFilter<?>> mDeviceFilters; 166 167 /** 168 * Profile of the device. 169 */ 170 @Nullable 171 @DeviceProfile 172 private final String mDeviceProfile; 173 174 /** 175 * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for 176 * "self-managed" association. 177 */ 178 @Nullable 179 private CharSequence mDisplayName; 180 181 /** 182 * The device that was associated. Will be null for "self-managed" association. 183 */ 184 @Nullable 185 private AssociatedDevice mAssociatedDevice; 186 187 /** 188 * Whether the association is to be managed by the companion application. 189 */ 190 private final boolean mSelfManaged; 191 192 /** 193 * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit 194 * confirmation from the user before creating an association, even if such confirmation is not 195 * required. 196 */ 197 private final boolean mForceConfirmation; 198 199 /** 200 * The app package name of the application the association will belong to. 201 * Populated by the system. 202 * @hide 203 */ 204 @Nullable 205 private String mPackageName; 206 207 /** 208 * The UserId of the user the association will belong to. 209 * Populated by the system. 210 * @hide 211 */ 212 @UserIdInt 213 private int mUserId; 214 215 /** 216 * The user-readable description of the device profile's privileges. 217 * Populated by the system. 218 * @hide 219 */ 220 @Nullable 221 private String mDeviceProfilePrivilegesDescription; 222 223 /** 224 * The time at which his request was created 225 * @hide 226 */ 227 private final long mCreationTime; 228 229 /** 230 * Whether the user-prompt may be skipped once the device is found. 231 * Populated by the system. 232 * @hide 233 */ 234 private boolean mSkipPrompt; 235 236 /** 237 * Creates a new AssociationRequest. 238 * 239 * @param singleDevice 240 * Whether only a single device should match the provided filter. 241 * 242 * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac 243 * address, bonded devices are also searched among. This allows to obtain the necessary app 244 * privileges even if the device is already paired. 245 * @param deviceFilters 246 * If set, only devices matching either of the given filters will be shown to the user 247 * @param deviceProfile 248 * Profile of the device. 249 * @param displayName 250 * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for 251 * "self-managed" association. 252 * @param selfManaged 253 * Whether the association is to be managed by the companion application. 254 */ AssociationRequest( boolean singleDevice, @NonNull List<DeviceFilter<?>> deviceFilters, @Nullable @DeviceProfile String deviceProfile, @Nullable CharSequence displayName, boolean selfManaged, boolean forceConfirmation)255 private AssociationRequest( 256 boolean singleDevice, 257 @NonNull List<DeviceFilter<?>> deviceFilters, 258 @Nullable @DeviceProfile String deviceProfile, 259 @Nullable CharSequence displayName, 260 boolean selfManaged, 261 boolean forceConfirmation) { 262 mSingleDevice = singleDevice; 263 mDeviceFilters = requireNonNull(deviceFilters); 264 mDeviceProfile = deviceProfile; 265 mDisplayName = displayName; 266 mSelfManaged = selfManaged; 267 mForceConfirmation = forceConfirmation; 268 269 mCreationTime = System.currentTimeMillis(); 270 } 271 272 /** 273 * @return profile of the companion device. 274 */ 275 @Nullable 276 @DeviceProfile getDeviceProfile()277 public String getDeviceProfile() { 278 return mDeviceProfile; 279 } 280 281 /** 282 * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for 283 * "self-managed" association. 284 */ 285 @Nullable getDisplayName()286 public CharSequence getDisplayName() { 287 return mDisplayName; 288 } 289 290 /** 291 * Whether the association is to be managed by the companion application. 292 * 293 * @see Builder#setSelfManaged(boolean) 294 */ isSelfManaged()295 public boolean isSelfManaged() { 296 return mSelfManaged; 297 } 298 299 /** 300 * Indicates whether the application requires the {@link CompanionDeviceManager} service to 301 * collect an explicit confirmation from the user before creating an association, even if 302 * such confirmation is not required from the service's perspective. 303 * 304 * @see Builder#setForceConfirmation(boolean) 305 */ isForceConfirmation()306 public boolean isForceConfirmation() { 307 return mForceConfirmation; 308 } 309 310 /** 311 * Whether only a single device should match the provided filter. 312 * 313 * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac 314 * address, bonded devices are also searched among. This allows to obtain the necessary app 315 * privileges even if the device is already paired. 316 */ isSingleDevice()317 public boolean isSingleDevice() { 318 return mSingleDevice; 319 } 320 321 /** @hide */ setPackageName(@onNull String packageName)322 public void setPackageName(@NonNull String packageName) { 323 mPackageName = packageName; 324 } 325 326 /** @hide */ setUserId(@serIdInt int userId)327 public void setUserId(@UserIdInt int userId) { 328 mUserId = userId; 329 } 330 331 /** @hide */ setDeviceProfilePrivilegesDescription(@onNull String desc)332 public void setDeviceProfilePrivilegesDescription(@NonNull String desc) { 333 mDeviceProfilePrivilegesDescription = desc; 334 } 335 336 /** @hide */ setSkipPrompt(boolean value)337 public void setSkipPrompt(boolean value) { 338 mSkipPrompt = value; 339 } 340 341 /** @hide */ setDisplayName(CharSequence displayName)342 public void setDisplayName(CharSequence displayName) { 343 mDisplayName = displayName; 344 } 345 346 /** @hide */ setAssociatedDevice(AssociatedDevice associatedDevice)347 public void setAssociatedDevice(AssociatedDevice associatedDevice) { 348 mAssociatedDevice = associatedDevice; 349 } 350 351 /** @hide */ 352 @NonNull 353 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getDeviceFilters()354 public List<DeviceFilter<?>> getDeviceFilters() { 355 return mDeviceFilters; 356 } 357 358 /** 359 * A builder for {@link AssociationRequest} 360 */ 361 public static final class Builder extends OneTimeUseBuilder<AssociationRequest> { 362 private boolean mSingleDevice = false; 363 private ArrayList<DeviceFilter<?>> mDeviceFilters = null; 364 private String mDeviceProfile; 365 private CharSequence mDisplayName; 366 private boolean mSelfManaged = false; 367 private boolean mForceConfirmation = false; 368 Builder()369 public Builder() {} 370 371 /** 372 * Whether only a single device should match the provided filter. 373 * 374 * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac 375 * address, bonded devices are also searched among. This allows to obtain the necessary app 376 * privileges even if the device is already paired. 377 * 378 * @param singleDevice if true, scanning for a device will stop as soon as at least one 379 * fitting device is found 380 */ 381 @NonNull setSingleDevice(boolean singleDevice)382 public Builder setSingleDevice(boolean singleDevice) { 383 checkNotUsed(); 384 this.mSingleDevice = singleDevice; 385 return this; 386 } 387 388 /** 389 * @param deviceFilter if set, only devices matching the given filter will be shown to the 390 * user 391 */ 392 @NonNull addDeviceFilter(@ullable DeviceFilter<?> deviceFilter)393 public Builder addDeviceFilter(@Nullable DeviceFilter<?> deviceFilter) { 394 checkNotUsed(); 395 if (deviceFilter != null) { 396 mDeviceFilters = ArrayUtils.add(mDeviceFilters, deviceFilter); 397 } 398 return this; 399 } 400 401 /** 402 * If set, association will be requested as a corresponding kind of device 403 */ 404 @NonNull setDeviceProfile(@onNull @eviceProfile String deviceProfile)405 public Builder setDeviceProfile(@NonNull @DeviceProfile String deviceProfile) { 406 checkNotUsed(); 407 mDeviceProfile = deviceProfile; 408 return this; 409 } 410 411 /** 412 * Adds a display name. 413 * Generally {@link AssociationRequest}s are not required to provide a display name, except 414 * for request for creating "self-managed" associations, which MUST provide a display name. 415 * 416 * @param displayName the display name of the device. 417 */ 418 @NonNull setDisplayName(@onNull CharSequence displayName)419 public Builder setDisplayName(@NonNull CharSequence displayName) { 420 checkNotUsed(); 421 mDisplayName = requireNonNull(displayName); 422 return this; 423 } 424 425 /** 426 * Indicate whether the association would be managed by the companion application. 427 * 428 * Requests for creating "self-managed" association MUST provide a Display name. 429 * 430 * @see #setDisplayName(CharSequence) 431 */ 432 @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED) 433 @NonNull setSelfManaged(boolean selfManaged)434 public Builder setSelfManaged(boolean selfManaged) { 435 checkNotUsed(); 436 mSelfManaged = selfManaged; 437 return this; 438 } 439 440 /** 441 * Indicates whether the application requires the {@link CompanionDeviceManager} service to 442 * collect an explicit confirmation from the user before creating an association, even if 443 * such confirmation is not required from the service's perspective. 444 */ 445 @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED) 446 @NonNull setForceConfirmation(boolean forceConfirmation)447 public Builder setForceConfirmation(boolean forceConfirmation) { 448 checkNotUsed(); 449 mForceConfirmation = forceConfirmation; 450 return this; 451 } 452 453 /** @inheritDoc */ 454 @NonNull 455 @Override build()456 public AssociationRequest build() { 457 markUsed(); 458 if (mSelfManaged && mDisplayName == null) { 459 throw new IllegalStateException("Request for a self-managed association MUST " 460 + "provide the display name of the device"); 461 } 462 return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters), 463 mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation); 464 } 465 } 466 467 /** 468 * The device that was associated. Will be null for "self-managed" association. 469 * 470 * @hide 471 */ 472 @Nullable getAssociatedDevice()473 public AssociatedDevice getAssociatedDevice() { 474 return mAssociatedDevice; 475 } 476 477 /** 478 * The app package name of the application the association will belong to. 479 * Populated by the system. 480 * 481 * @hide 482 */ 483 @Nullable getPackageName()484 public String getPackageName() { 485 return mPackageName; 486 } 487 488 /** 489 * The UserId of the user the association will belong to. 490 * Populated by the system. 491 * 492 * @hide 493 */ 494 @UserIdInt getUserId()495 public int getUserId() { 496 return mUserId; 497 } 498 499 /** 500 * The user-readable description of the device profile's privileges. 501 * Populated by the system. 502 * 503 * @hide 504 */ 505 @Nullable getDeviceProfilePrivilegesDescription()506 public String getDeviceProfilePrivilegesDescription() { 507 return mDeviceProfilePrivilegesDescription; 508 } 509 510 /** 511 * The time at which his request was created 512 * 513 * @hide 514 */ getCreationTime()515 public long getCreationTime() { 516 return mCreationTime; 517 } 518 519 /** 520 * Whether the user-prompt may be skipped once the device is found. 521 * Populated by the system. 522 * 523 * @hide 524 */ isSkipPrompt()525 public boolean isSkipPrompt() { 526 return mSkipPrompt; 527 } 528 529 @Override toString()530 public String toString() { 531 return "AssociationRequest { " 532 + "singleDevice = " + mSingleDevice 533 + ", deviceFilters = " + mDeviceFilters 534 + ", deviceProfile = " + mDeviceProfile 535 + ", displayName = " + mDisplayName 536 + ", associatedDevice = " + mAssociatedDevice 537 + ", selfManaged = " + mSelfManaged 538 + ", forceConfirmation = " + mForceConfirmation 539 + ", packageName = " + mPackageName 540 + ", userId = " + mUserId 541 + ", deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription 542 + ", creationTime = " + mCreationTime 543 + ", skipPrompt = " + mSkipPrompt 544 + " }"; 545 } 546 547 @Override equals(@ullable Object o)548 public boolean equals(@Nullable Object o) { 549 if (this == o) return true; 550 if (o == null || getClass() != o.getClass()) return false; 551 AssociationRequest that = (AssociationRequest) o; 552 return mSingleDevice == that.mSingleDevice 553 && Objects.equals(mDeviceFilters, that.mDeviceFilters) 554 && Objects.equals(mDeviceProfile, that.mDeviceProfile) 555 && Objects.equals(mDisplayName, that.mDisplayName) 556 && Objects.equals(mAssociatedDevice, that.mAssociatedDevice) 557 && mSelfManaged == that.mSelfManaged 558 && mForceConfirmation == that.mForceConfirmation 559 && Objects.equals(mPackageName, that.mPackageName) 560 && mUserId == that.mUserId 561 && Objects.equals(mDeviceProfilePrivilegesDescription, 562 that.mDeviceProfilePrivilegesDescription) 563 && mCreationTime == that.mCreationTime 564 && mSkipPrompt == that.mSkipPrompt; 565 } 566 567 @Override hashCode()568 public int hashCode() { 569 int _hash = 1; 570 _hash = 31 * _hash + Boolean.hashCode(mSingleDevice); 571 _hash = 31 * _hash + Objects.hashCode(mDeviceFilters); 572 _hash = 31 * _hash + Objects.hashCode(mDeviceProfile); 573 _hash = 31 * _hash + Objects.hashCode(mDisplayName); 574 _hash = 31 * _hash + Objects.hashCode(mAssociatedDevice); 575 _hash = 31 * _hash + Boolean.hashCode(mSelfManaged); 576 _hash = 31 * _hash + Boolean.hashCode(mForceConfirmation); 577 _hash = 31 * _hash + Objects.hashCode(mPackageName); 578 _hash = 31 * _hash + mUserId; 579 _hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription); 580 _hash = 31 * _hash + Long.hashCode(mCreationTime); 581 _hash = 31 * _hash + Boolean.hashCode(mSkipPrompt); 582 return _hash; 583 } 584 585 @Override writeToParcel(@onNull Parcel dest, int flags)586 public void writeToParcel(@NonNull Parcel dest, int flags) { 587 int flg = 0; 588 if (mSingleDevice) flg |= 0x1; 589 if (mSelfManaged) flg |= 0x2; 590 if (mForceConfirmation) flg |= 0x4; 591 if (mSkipPrompt) flg |= 0x8; 592 if (mDeviceProfile != null) flg |= 0x10; 593 if (mDisplayName != null) flg |= 0x20; 594 if (mAssociatedDevice != null) flg |= 0x40; 595 if (mPackageName != null) flg |= 0x80; 596 if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100; 597 598 dest.writeInt(flg); 599 dest.writeParcelableList(mDeviceFilters, flags); 600 if (mDeviceProfile != null) dest.writeString(mDeviceProfile); 601 if (mDisplayName != null) dest.writeCharSequence(mDisplayName); 602 if (mAssociatedDevice != null) dest.writeTypedObject(mAssociatedDevice, flags); 603 if (mPackageName != null) dest.writeString(mPackageName); 604 dest.writeInt(mUserId); 605 if (mDeviceProfilePrivilegesDescription != null) { 606 dest.writeString8(mDeviceProfilePrivilegesDescription); 607 } 608 dest.writeLong(mCreationTime); 609 } 610 611 @Override describeContents()612 public int describeContents() { 613 return 0; 614 } 615 616 /** @hide */ 617 @SuppressWarnings("unchecked") AssociationRequest(@onNull Parcel in)618 /* package-private */ AssociationRequest(@NonNull Parcel in) { 619 int flg = in.readInt(); 620 boolean singleDevice = (flg & 0x1) != 0; 621 boolean selfManaged = (flg & 0x2) != 0; 622 boolean forceConfirmation = (flg & 0x4) != 0; 623 boolean skipPrompt = (flg & 0x8) != 0; 624 List<DeviceFilter<?>> deviceFilters = new ArrayList<>(); 625 in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(), 626 (Class<android.companion.DeviceFilter<?>>) (Class<?>) 627 android.companion.DeviceFilter.class); 628 String deviceProfile = (flg & 0x10) == 0 ? null : in.readString(); 629 CharSequence displayName = (flg & 0x20) == 0 ? null : in.readCharSequence(); 630 AssociatedDevice associatedDevice = (flg & 0x40) == 0 ? null 631 : in.readTypedObject(AssociatedDevice.CREATOR); 632 String packageName = (flg & 0x80) == 0 ? null : in.readString(); 633 int userId = in.readInt(); 634 String deviceProfilePrivilegesDescription = (flg & 0x100) == 0 ? null : in.readString8(); 635 long creationTime = in.readLong(); 636 637 this.mSingleDevice = singleDevice; 638 this.mDeviceFilters = deviceFilters; 639 com.android.internal.util.AnnotationValidations.validate( 640 NonNull.class, null, mDeviceFilters); 641 this.mDeviceProfile = deviceProfile; 642 this.mDisplayName = displayName; 643 this.mAssociatedDevice = associatedDevice; 644 this.mSelfManaged = selfManaged; 645 this.mForceConfirmation = forceConfirmation; 646 this.mPackageName = packageName; 647 this.mUserId = userId; 648 com.android.internal.util.AnnotationValidations.validate( 649 UserIdInt.class, null, mUserId); 650 this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; 651 this.mCreationTime = creationTime; 652 this.mSkipPrompt = skipPrompt; 653 } 654 655 @NonNull 656 public static final Parcelable.Creator<AssociationRequest> CREATOR = 657 new Parcelable.Creator<AssociationRequest>() { 658 @Override 659 public AssociationRequest[] newArray(int size) { 660 return new AssociationRequest[size]; 661 } 662 663 @Override 664 public AssociationRequest createFromParcel(@NonNull Parcel in) { 665 return new AssociationRequest(in); 666 } 667 }; 668 } 669