1 /* 2 * Copyright (C) 2024 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.health.connect.datatypes; 18 19 import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD; 20 21 import static java.util.Objects.hash; 22 import static java.util.Objects.requireNonNull; 23 24 import android.annotation.FlaggedApi; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.net.Uri; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 31 import java.time.Instant; 32 import java.util.HashSet; 33 import java.util.Objects; 34 import java.util.Set; 35 import java.util.UUID; 36 37 /** 38 * Captures the data source information of medical data. All {@link MedicalResource}s are associated 39 * with a {@code MedicalDataSource}. 40 * 41 * <p>The medical data is represented using the <a href="https://hl7.org/fhir/">Fast Healthcare 42 * Interoperability Resources (FHIR)</a> standard. 43 */ 44 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD) 45 public final class MedicalDataSource implements Parcelable { 46 @NonNull private final String mId; 47 @NonNull private final String mPackageName; 48 @NonNull private final Uri mFhirBaseUri; 49 @NonNull private final String mDisplayName; 50 @NonNull private final FhirVersion mFhirVersion; 51 @Nullable private final Instant mLastDataUpdateTime; 52 53 @NonNull 54 public static final Creator<MedicalDataSource> CREATOR = 55 new Creator<MedicalDataSource>() { 56 @NonNull 57 @Override 58 public MedicalDataSource createFromParcel(@NonNull Parcel in) { 59 return new MedicalDataSource(in); 60 } 61 62 @NonNull 63 @Override 64 public MedicalDataSource[] newArray(int size) { 65 return new MedicalDataSource[size]; 66 } 67 }; 68 69 /** 70 * Creates a new instance of {@link MedicalDataSource}. Please see {@link 71 * MedicalDataSource.Builder} for more detailed parameters information. 72 */ MedicalDataSource( @onNull String id, @NonNull String packageName, @NonNull Uri fhirBaseUri, @NonNull String displayName, @NonNull FhirVersion fhirVersion, @Nullable Instant lastDataUpdateTime)73 private MedicalDataSource( 74 @NonNull String id, 75 @NonNull String packageName, 76 @NonNull Uri fhirBaseUri, 77 @NonNull String displayName, 78 @NonNull FhirVersion fhirVersion, 79 @Nullable Instant lastDataUpdateTime) { 80 requireNonNull(id); 81 requireNonNull(packageName); 82 requireNonNull(fhirBaseUri); 83 requireNonNull(displayName); 84 requireNonNull(fhirVersion); 85 86 mId = id; 87 mPackageName = packageName; 88 mFhirBaseUri = fhirBaseUri; 89 mDisplayName = displayName; 90 mFhirVersion = fhirVersion; 91 mLastDataUpdateTime = lastDataUpdateTime; 92 } 93 MedicalDataSource(@onNull Parcel in)94 private MedicalDataSource(@NonNull Parcel in) { 95 requireNonNull(in); 96 mId = requireNonNull(in.readString()); 97 mPackageName = requireNonNull(in.readString()); 98 mFhirBaseUri = requireNonNull(in.readParcelable(Uri.class.getClassLoader(), Uri.class)); 99 mDisplayName = requireNonNull(in.readString()); 100 mFhirVersion = 101 requireNonNull( 102 in.readParcelable(FhirVersion.class.getClassLoader(), FhirVersion.class)); 103 long lastDataUpdateTimeMillis = in.readLong(); 104 mLastDataUpdateTime = 105 lastDataUpdateTimeMillis == 0 106 ? null 107 : Instant.ofEpochMilli(lastDataUpdateTimeMillis); 108 } 109 110 /** Returns the unique identifier, assigned by the Android Health Platform at insertion time. */ 111 @NonNull getId()112 public String getId() { 113 return mId; 114 } 115 116 /** Returns the corresponding package name of the owning app. */ 117 @NonNull getPackageName()118 public String getPackageName() { 119 return mPackageName; 120 } 121 122 /** Returns the display name. */ 123 @NonNull getDisplayName()124 public String getDisplayName() { 125 return mDisplayName; 126 } 127 128 /** Returns the FHIR version of {@link MedicalResource}s from this source. */ 129 @NonNull getFhirVersion()130 public FhirVersion getFhirVersion() { 131 return mFhirVersion; 132 } 133 134 /** 135 * Returns the time {@link MedicalResource}s linked to this data source were last updated, or 136 * {@code null} if the data source has no linked resources. 137 * 138 * <p>This time is based on resources that currently exist in HealthConnect, so does not reflect 139 * data deletion. 140 */ 141 @Nullable getLastDataUpdateTime()142 public Instant getLastDataUpdateTime() { 143 return mLastDataUpdateTime; 144 } 145 146 /** Returns the FHIR base URI, where data written for this data source came from. */ 147 @NonNull getFhirBaseUri()148 public Uri getFhirBaseUri() { 149 return mFhirBaseUri; 150 } 151 152 @Override describeContents()153 public int describeContents() { 154 return 0; 155 } 156 157 @Override writeToParcel(@onNull Parcel dest, int flags)158 public void writeToParcel(@NonNull Parcel dest, int flags) { 159 dest.writeString(mId); 160 dest.writeString(mPackageName); 161 dest.writeParcelable(mFhirBaseUri, 0); 162 dest.writeString(mDisplayName); 163 dest.writeParcelable(mFhirVersion, 0); 164 dest.writeLong(mLastDataUpdateTime == null ? 0 : mLastDataUpdateTime.toEpochMilli()); 165 } 166 167 /** 168 * Validates all of the provided {@code ids} are valid. 169 * 170 * <p>Throws {@link IllegalArgumentException} with all invalid IDs if not. 171 * 172 * @hide 173 */ validateMedicalDataSourceIds(@onNull Set<String> ids)174 public static Set<UUID> validateMedicalDataSourceIds(@NonNull Set<String> ids) { 175 Set<String> invalidIds = new HashSet<>(); 176 Set<UUID> uuids = new HashSet<>(); 177 for (String id : ids) { 178 try { 179 uuids.add(UUID.fromString(id)); 180 } catch (IllegalArgumentException e) { 181 invalidIds.add(id); 182 } 183 } 184 if (!invalidIds.isEmpty()) { 185 throw new IllegalArgumentException("Invalid data source ID(s): " + invalidIds); 186 } 187 return uuids; 188 } 189 190 /** Indicates whether some other object is "equal to" this one. */ 191 @Override equals(@ullable Object o)192 public boolean equals(@Nullable Object o) { 193 if (this == o) return true; 194 if (!(o instanceof MedicalDataSource that)) return false; 195 return getId().equals(that.getId()) 196 && getPackageName().equals(that.getPackageName()) 197 && getFhirBaseUri().equals(that.getFhirBaseUri()) 198 && getDisplayName().equals(that.getDisplayName()) 199 && getFhirVersion().equals(that.getFhirVersion()) 200 && Objects.equals(getLastDataUpdateTime(), that.getLastDataUpdateTime()); 201 } 202 203 /** Returns a hash code value for the object. */ 204 @Override hashCode()205 public int hashCode() { 206 return hash( 207 getId(), 208 getPackageName(), 209 getFhirBaseUri(), 210 getDisplayName(), 211 getFhirVersion(), 212 getLastDataUpdateTime()); 213 } 214 215 /** Returns a string representation of this {@link MedicalDataSource}. */ 216 @Override toString()217 public String toString() { 218 StringBuilder sb = new StringBuilder(); 219 sb.append(this.getClass().getSimpleName()).append("{"); 220 sb.append("id=").append(getId()); 221 sb.append(",packageName=").append(getPackageName()); 222 sb.append(",fhirBaseUri=").append(getFhirBaseUri()); 223 sb.append(",displayName=").append(getDisplayName()); 224 sb.append(",fhirVersion=").append(getFhirVersion()); 225 sb.append(",lastDataUpdateTime=").append(getLastDataUpdateTime()); 226 sb.append("}"); 227 return sb.toString(); 228 } 229 230 /** Builder class for {@link MedicalDataSource}. */ 231 public static final class Builder { 232 @NonNull private String mId; 233 @NonNull private String mPackageName; 234 @NonNull private Uri mFhirBaseUri; 235 @NonNull private String mDisplayName; 236 @NonNull private FhirVersion mFhirVersion; 237 @Nullable private Instant mLastDataUpdateTime; 238 239 /** 240 * Constructs a new {@link MedicalDataSource.Builder} instance. 241 * 242 * @param id The unique identifier of this data source. 243 * @param packageName The package name of the owning app. 244 * @param fhirBaseUri The FHIR base URI of the data source. 245 * @param displayName The display name that describes the data source. 246 * @param fhirVersion The FHIR version of {@link MedicalResource}s linked to this source. 247 */ Builder( @onNull String id, @NonNull String packageName, @NonNull Uri fhirBaseUri, @NonNull String displayName, @NonNull FhirVersion fhirVersion)248 public Builder( 249 @NonNull String id, 250 @NonNull String packageName, 251 @NonNull Uri fhirBaseUri, 252 @NonNull String displayName, 253 @NonNull FhirVersion fhirVersion) { 254 requireNonNull(id); 255 requireNonNull(packageName); 256 requireNonNull(fhirBaseUri); 257 requireNonNull(displayName); 258 requireNonNull(fhirVersion); 259 260 mId = id; 261 mPackageName = packageName; 262 mFhirBaseUri = fhirBaseUri; 263 mDisplayName = displayName; 264 mFhirVersion = fhirVersion; 265 } 266 267 /** Constructs a clone of the other {@link MedicalDataSource.Builder}. */ Builder(@onNull Builder other)268 public Builder(@NonNull Builder other) { 269 requireNonNull(other); 270 mId = other.mId; 271 mPackageName = other.mPackageName; 272 mFhirBaseUri = other.mFhirBaseUri; 273 mDisplayName = other.mDisplayName; 274 mFhirVersion = other.mFhirVersion; 275 mLastDataUpdateTime = other.mLastDataUpdateTime; 276 } 277 278 /** Constructs a clone of the other {@link MedicalDataSource} instance. */ Builder(@onNull MedicalDataSource other)279 public Builder(@NonNull MedicalDataSource other) { 280 requireNonNull(other); 281 mId = other.getId(); 282 mPackageName = other.getPackageName(); 283 mFhirBaseUri = other.getFhirBaseUri(); 284 mDisplayName = other.getDisplayName(); 285 mFhirVersion = other.getFhirVersion(); 286 mLastDataUpdateTime = other.getLastDataUpdateTime(); 287 } 288 289 /** Sets unique identifier of this data source. */ 290 @NonNull setId(@onNull String id)291 public Builder setId(@NonNull String id) { 292 requireNonNull(id); 293 mId = id; 294 return this; 295 } 296 297 /** 298 * Sets the package name of the contributing package. Auto-populated by the platform at 299 * source creation time. 300 */ 301 @NonNull setPackageName(@onNull String packageName)302 public Builder setPackageName(@NonNull String packageName) { 303 requireNonNull(packageName); 304 mPackageName = packageName; 305 return this; 306 } 307 308 /** Sets the FHIR base URI of this data source. */ 309 @NonNull setFhirBaseUri(@onNull Uri fhirBaseUri)310 public Builder setFhirBaseUri(@NonNull Uri fhirBaseUri) { 311 requireNonNull(fhirBaseUri); 312 mFhirBaseUri = fhirBaseUri; 313 return this; 314 } 315 316 /** Sets the display name that describes this data source. */ 317 @NonNull setDisplayName(@onNull String displayName)318 public Builder setDisplayName(@NonNull String displayName) { 319 requireNonNull(displayName); 320 mDisplayName = displayName; 321 return this; 322 } 323 324 /** Sets the FHIR version of {@link MedicalResource}s linked to this source. */ 325 @NonNull setFhirVersion(@onNull FhirVersion fhirVersion)326 public Builder setFhirVersion(@NonNull FhirVersion fhirVersion) { 327 requireNonNull(fhirVersion); 328 mFhirVersion = fhirVersion; 329 return this; 330 } 331 332 /** Sets the time {@link MedicalResource}s linked to this data source were last updated. */ 333 @NonNull setLastDataUpdateTime(@ullable Instant lastDataUpdateTime)334 public Builder setLastDataUpdateTime(@Nullable Instant lastDataUpdateTime) { 335 mLastDataUpdateTime = lastDataUpdateTime; 336 return this; 337 } 338 339 /** Returns a new instance of {@link MedicalDataSource} with the specified parameters. */ 340 @NonNull build()341 public MedicalDataSource build() { 342 return new MedicalDataSource( 343 mId, 344 mPackageName, 345 mFhirBaseUri, 346 mDisplayName, 347 mFhirVersion, 348 mLastDataUpdateTime); 349 } 350 } 351 } 352