1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.companion;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.SystemApi;
21 import android.annotation.UserIdInt;
22 import android.net.MacAddress;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 
26 import java.util.Date;
27 import java.util.Objects;
28 
29 /**
30  * Details for a specific "association" that has been established between an app and companion
31  * device.
32  * <p>
33  * An association gives an app the ability to interact with a companion device without needing to
34  * acquire broader runtime permissions. An association only exists after the user has confirmed that
35  * an app should have access to a companion device.
36  */
37 public final class AssociationInfo implements Parcelable {
38     /**
39      * A String indicates the selfManaged device is not connected.
40      */
41     private static final String LAST_TIME_CONNECTED_NONE = "None";
42     /**
43      * A unique ID of this Association record.
44      * Disclosed to the clients (ie. companion applications) for referring to this record (eg. in
45      * {@code disassociate()} API call).
46      */
47     private final int mId;
48 
49     private final @UserIdInt int mUserId;
50     private final @NonNull String mPackageName;
51 
52     private final @Nullable MacAddress mDeviceMacAddress;
53     private final @Nullable CharSequence mDisplayName;
54     private final @Nullable String mDeviceProfile;
55     private final @Nullable AssociatedDevice mAssociatedDevice;
56 
57     private final boolean mSelfManaged;
58     private final boolean mNotifyOnDeviceNearby;
59     private final int mSystemDataSyncFlags;
60 
61     /**
62      * Indicates that the association has been revoked (removed), but we keep the association
63      * record for final clean up (e.g. removing the app from the list of the role holders).
64      *
65      * @see CompanionDeviceManager#disassociate(int)
66      */
67     private final boolean mRevoked;
68     private final long mTimeApprovedMs;
69     /**
70      * A long value indicates the last time connected reported by selfManaged devices
71      * Default value is Long.MAX_VALUE.
72      */
73     private final long mLastTimeConnectedMs;
74 
75     /**
76      * Creates a new Association.
77      *
78      * @hide
79      */
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, long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags)80     public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
81             @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
82             @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
83             boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked,
84             long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags) {
85         if (id <= 0) {
86             throw new IllegalArgumentException("Association ID should be greater than 0");
87         }
88         if (macAddress == null && displayName == null) {
89             throw new IllegalArgumentException("MAC address and the Display Name must NOT be null "
90                     + "at the same time");
91         }
92 
93         mId = id;
94 
95         mUserId = userId;
96         mPackageName = packageName;
97 
98         mDeviceMacAddress = macAddress;
99         mDisplayName = displayName;
100         mDeviceProfile = deviceProfile;
101         mAssociatedDevice = associatedDevice;
102 
103         mSelfManaged = selfManaged;
104         mNotifyOnDeviceNearby = notifyOnDeviceNearby;
105         mRevoked = revoked;
106         mTimeApprovedMs = timeApprovedMs;
107         mLastTimeConnectedMs = lastTimeConnectedMs;
108         mSystemDataSyncFlags = systemDataSyncFlags;
109     }
110 
111     /**
112      * @return the unique ID of this association record.
113      */
getId()114     public int getId() {
115         return mId;
116     }
117 
118     /**
119      * @return the ID of the user who "owns" this association.
120      * @hide
121      */
getUserId()122     public @UserIdInt int getUserId() {
123         return mUserId;
124     }
125 
126     /**
127      * @return the package name of the app which this association refers to.
128      * @hide
129      */
130     @SystemApi
getPackageName()131     public @NonNull String getPackageName() {
132         return mPackageName;
133     }
134 
135     /**
136      * @return the MAC address of the device.
137      */
getDeviceMacAddress()138     public @Nullable MacAddress getDeviceMacAddress() {
139         return mDeviceMacAddress;
140     }
141 
142     /** @hide */
getDeviceMacAddressAsString()143     public @Nullable String getDeviceMacAddressAsString() {
144         return mDeviceMacAddress != null ? mDeviceMacAddress.toString().toUpperCase() : null;
145     }
146 
147     /**
148      * @return the display name of the companion device (optionally) provided by the companion
149      * application.
150      *
151      * @see AssociationRequest.Builder#setDisplayName(CharSequence)
152      */
getDisplayName()153     public @Nullable CharSequence getDisplayName() {
154         return mDisplayName;
155     }
156 
157     /**
158      * @return the companion device profile used when establishing this
159      *         association, or {@code null} if no specific profile was used.
160      * @see AssociationRequest.Builder#setDeviceProfile(String)
161      */
getDeviceProfile()162     public @Nullable String getDeviceProfile() {
163         return mDeviceProfile;
164     }
165 
166     /**
167      * Companion device that was associated. Note that this field is not persisted across sessions.
168      * Device can be one of the following types:
169      *
170      * <ul>
171      *     <li>for classic Bluetooth - {@link AssociatedDevice#getBluetoothDevice()}</li>
172      *     <li>for Bluetooth LE - {@link AssociatedDevice#getBleDevice()}</li>
173      *     <li>for WiFi - {@link AssociatedDevice#getWifiDevice()}</li>
174      * </ul>
175      *
176      * @return the companion device that was associated, or {@code null} if the device is
177      *         self-managed or this association info was retrieved from persistent storage.
178      */
getAssociatedDevice()179     public @Nullable AssociatedDevice getAssociatedDevice() {
180         return mAssociatedDevice;
181     }
182 
183     /**
184      * @return whether the association is managed by the companion application it belongs to.
185      * @see AssociationRequest.Builder#setSelfManaged(boolean)
186      * @hide
187      */
188     @SystemApi
isSelfManaged()189     public boolean isSelfManaged() {
190         return mSelfManaged;
191     }
192 
193     /** @hide */
isNotifyOnDeviceNearby()194     public boolean isNotifyOnDeviceNearby() {
195         return mNotifyOnDeviceNearby;
196     }
197 
198     /** @hide */
getTimeApprovedMs()199     public long getTimeApprovedMs() {
200         return mTimeApprovedMs;
201     }
202 
203     /** @hide */
belongsToPackage(@serIdInt int userId, String packageName)204     public boolean belongsToPackage(@UserIdInt int userId, String packageName) {
205         return mUserId == userId && Objects.equals(mPackageName, packageName);
206     }
207 
208     /**
209      * @return if the association has been revoked (removed).
210      * @hide
211      */
isRevoked()212     public boolean isRevoked() {
213         return mRevoked;
214     }
215 
216     /**
217      * @return the last time self reported disconnected for selfManaged only.
218      * @hide
219      */
getLastTimeConnectedMs()220     public Long getLastTimeConnectedMs() {
221         return mLastTimeConnectedMs;
222     }
223 
224     /**
225      * @return Enabled system data sync flags set via
226      * {@link CompanionDeviceManager#enableSystemDataSync(int, int)} and
227      * {@link CompanionDeviceManager#disableSystemDataSync(int, int)}.
228      * Or by default all flags are 1 (enabled).
229      */
getSystemDataSyncFlags()230     public int getSystemDataSyncFlags() {
231         return mSystemDataSyncFlags;
232     }
233 
234     /**
235      * Utility method for checking if the association represents a device with the given MAC
236      * address.
237      *
238      * @return {@code false} if the association is "self-managed".
239      *         {@code false} if the {@code addr} is {@code null} or is not a valid MAC address.
240      *         Otherwise - the result of {@link MacAddress#equals(Object)}
241      *
242      * @hide
243      */
isLinkedTo(@ullable String addr)244     public boolean isLinkedTo(@Nullable String addr) {
245         if (mSelfManaged) return false;
246 
247         if (addr == null) return false;
248 
249         final MacAddress macAddress;
250         try {
251             macAddress = MacAddress.fromString(addr);
252         } catch (IllegalArgumentException e) {
253             return false;
254         }
255         return macAddress.equals(mDeviceMacAddress);
256     }
257 
258     /**
259      * Utility method to be used by CdmService only.
260      *
261      * @return whether CdmService should bind the companion application that "owns" this association
262      *         when the device is present.
263      *
264      * @hide
265      */
shouldBindWhenPresent()266     public boolean shouldBindWhenPresent() {
267         return mNotifyOnDeviceNearby || mSelfManaged;
268     }
269 
270     /** @hide */
toShortString()271     public @NonNull String toShortString() {
272         final StringBuilder sb = new StringBuilder();
273         sb.append("id=").append(mId);
274         if (mDeviceMacAddress != null) {
275             sb.append(", addr=").append(getDeviceMacAddressAsString());
276         }
277         if (mSelfManaged) {
278             sb.append(", self-managed");
279         }
280         sb.append(", pkg=u").append(mUserId).append('/').append(mPackageName);
281         return sb.toString();
282     }
283 
284     @Override
toString()285     public String toString() {
286         return "Association{"
287                 + "mId=" + mId
288                 + ", mUserId=" + mUserId
289                 + ", mPackageName='" + mPackageName + '\''
290                 + ", mDeviceMacAddress=" + mDeviceMacAddress
291                 + ", mDisplayName='" + mDisplayName + '\''
292                 + ", mDeviceProfile='" + mDeviceProfile + '\''
293                 + ", mSelfManaged=" + mSelfManaged
294                 + ", mAssociatedDevice=" + mAssociatedDevice
295                 + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
296                 + ", mRevoked=" + mRevoked
297                 + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
298                 + ", mLastTimeConnectedMs=" + (
299                     mLastTimeConnectedMs == Long.MAX_VALUE
300                         ? LAST_TIME_CONNECTED_NONE : new Date(mLastTimeConnectedMs))
301                 + ", mSystemDataSyncFlags=" + mSystemDataSyncFlags
302                 + '}';
303     }
304 
305     @Override
equals(Object o)306     public boolean equals(Object o) {
307         if (this == o) return true;
308         if (!(o instanceof AssociationInfo)) return false;
309         final AssociationInfo that = (AssociationInfo) o;
310         return mId == that.mId
311                 && mUserId == that.mUserId
312                 && mSelfManaged == that.mSelfManaged
313                 && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
314                 && mRevoked == that.mRevoked
315                 && mTimeApprovedMs == that.mTimeApprovedMs
316                 && mLastTimeConnectedMs == that.mLastTimeConnectedMs
317                 && Objects.equals(mPackageName, that.mPackageName)
318                 && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
319                 && Objects.equals(mDisplayName, that.mDisplayName)
320                 && Objects.equals(mDeviceProfile, that.mDeviceProfile)
321                 && Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
322                 && mSystemDataSyncFlags == that.mSystemDataSyncFlags;
323     }
324 
325     @Override
hashCode()326     public int hashCode() {
327         return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
328                 mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
329                 mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
330     }
331 
332     @Override
describeContents()333     public int describeContents() {
334         return 0;
335     }
336 
337     @Override
writeToParcel(@onNull Parcel dest, int flags)338     public void writeToParcel(@NonNull Parcel dest, int flags) {
339         dest.writeInt(mId);
340 
341         dest.writeInt(mUserId);
342         dest.writeString(mPackageName);
343 
344         dest.writeTypedObject(mDeviceMacAddress, 0);
345         dest.writeCharSequence(mDisplayName);
346         dest.writeString(mDeviceProfile);
347         dest.writeTypedObject(mAssociatedDevice, 0);
348 
349         dest.writeBoolean(mSelfManaged);
350         dest.writeBoolean(mNotifyOnDeviceNearby);
351         dest.writeBoolean(mRevoked);
352         dest.writeLong(mTimeApprovedMs);
353         dest.writeLong(mLastTimeConnectedMs);
354         dest.writeInt(mSystemDataSyncFlags);
355     }
356 
AssociationInfo(@onNull Parcel in)357     private AssociationInfo(@NonNull Parcel in) {
358         mId = in.readInt();
359 
360         mUserId = in.readInt();
361         mPackageName = in.readString();
362 
363         mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR);
364         mDisplayName = in.readCharSequence();
365         mDeviceProfile = in.readString();
366         mAssociatedDevice = in.readTypedObject(AssociatedDevice.CREATOR);
367 
368         mSelfManaged = in.readBoolean();
369         mNotifyOnDeviceNearby = in.readBoolean();
370         mRevoked = in.readBoolean();
371         mTimeApprovedMs = in.readLong();
372         mLastTimeConnectedMs = in.readLong();
373         mSystemDataSyncFlags = in.readInt();
374     }
375 
376     @NonNull
377     public static final Parcelable.Creator<AssociationInfo> CREATOR =
378             new Parcelable.Creator<AssociationInfo>() {
379         @Override
380         public AssociationInfo[] newArray(int size) {
381             return new AssociationInfo[size];
382         }
383 
384         @Override
385         public AssociationInfo createFromParcel(@NonNull Parcel in) {
386             return new AssociationInfo(in);
387         }
388     };
389 
390     /**
391      * Use this method to obtain a builder that you can use to create a copy of the
392      * given {@link AssociationInfo} with modified values of {@code mLastTimeConnected}
393      * or {@code mNotifyOnDeviceNearby}.
394      * <p>
395      *     Note that you <b>must</b> call either {@link Builder#setLastTimeConnected(long)
396      *     setLastTimeConnected} or {@link Builder#setNotifyOnDeviceNearby(boolean)
397      *     setNotifyOnDeviceNearby} before you will be able to call {@link Builder#build() build}.
398      *
399      *     This is ensured statically at compile time.
400      *
401      * @hide
402      */
403     @NonNull
builder(@onNull AssociationInfo info)404     public static NonActionableBuilder builder(@NonNull AssociationInfo info) {
405         return new Builder(info);
406     }
407 
408     /** @hide */
409     public static final class Builder implements NonActionableBuilder {
410         @NonNull
411         private final AssociationInfo mOriginalInfo;
412         private boolean mNotifyOnDeviceNearby;
413         private boolean mRevoked;
414         private long mLastTimeConnectedMs;
415         private int mSystemDataSyncFlags;
416 
Builder(@onNull AssociationInfo info)417         private Builder(@NonNull AssociationInfo info) {
418             mOriginalInfo = info;
419             mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
420             mRevoked = info.mRevoked;
421             mLastTimeConnectedMs = info.mLastTimeConnectedMs;
422             mSystemDataSyncFlags = info.mSystemDataSyncFlags;
423         }
424 
425         /** @hide */
426         @Override
427         @NonNull
setLastTimeConnected(long lastTimeConnectedMs)428         public Builder setLastTimeConnected(long lastTimeConnectedMs) {
429             if (lastTimeConnectedMs < 0) {
430                 throw new IllegalArgumentException(
431                         "lastTimeConnectedMs must not be negative! (Given " + lastTimeConnectedMs
432                                 + " )");
433             }
434             mLastTimeConnectedMs = lastTimeConnectedMs;
435             return this;
436         }
437 
438         /** @hide */
439         @Override
440         @NonNull
setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby)441         public Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby) {
442             mNotifyOnDeviceNearby = notifyOnDeviceNearby;
443             return this;
444         }
445 
446         /** @hide */
447         @Override
448         @NonNull
setRevoked(boolean revoked)449         public Builder setRevoked(boolean revoked) {
450             mRevoked = revoked;
451             return this;
452         }
453 
454         /** @hide */
455         @Override
456         @NonNull
setSystemDataSyncFlags(int flags)457         public Builder setSystemDataSyncFlags(int flags) {
458             mSystemDataSyncFlags = flags;
459             return this;
460         }
461 
462         /** @hide */
463         @NonNull
build()464         public AssociationInfo build() {
465             return new AssociationInfo(
466                     mOriginalInfo.mId,
467                     mOriginalInfo.mUserId,
468                     mOriginalInfo.mPackageName,
469                     mOriginalInfo.mDeviceMacAddress,
470                     mOriginalInfo.mDisplayName,
471                     mOriginalInfo.mDeviceProfile,
472                     mOriginalInfo.mAssociatedDevice,
473                     mOriginalInfo.mSelfManaged,
474                     mNotifyOnDeviceNearby,
475                     mRevoked,
476                     mOriginalInfo.mTimeApprovedMs,
477                     mLastTimeConnectedMs,
478                     mSystemDataSyncFlags
479             );
480         }
481     }
482 
483     /**
484      * This interface is returned from the
485      * {@link AssociationInfo#builder(android.companion.AssociationInfo) builder} entry point
486      * to indicate that this builder is not yet in a state that can produce a meaningful
487      * {@link AssociationInfo} object that is different from the one originally passed in.
488      *
489      * <p>
490      * Only by calling one of the setter methods is this builder turned into one where calling
491      * {@link Builder#build() build()} makes sense.
492      *
493      * @hide
494      */
495     public interface NonActionableBuilder {
496         /** @hide */
497         @NonNull
setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby)498         Builder setNotifyOnDeviceNearby(boolean notifyOnDeviceNearby);
499 
500         /** @hide */
501         @NonNull
setLastTimeConnected(long lastTimeConnectedMs)502         Builder setLastTimeConnected(long lastTimeConnectedMs);
503 
504         /** @hide */
505         @NonNull
setRevoked(boolean revoked)506         Builder setRevoked(boolean revoked);
507 
508         /** @hide */
509         @NonNull
setSystemDataSyncFlags(int flags)510         Builder setSystemDataSyncFlags(int flags);
511     }
512 }
513