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