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