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