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 android.health.connect.datatypes.MedicalDataSource.validateMedicalDataSourceIds; 20 import static android.health.connect.datatypes.validation.ValidationUtils.validateIntDefValue; 21 22 import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD; 23 24 import static java.util.Objects.hash; 25 import static java.util.Objects.requireNonNull; 26 27 import android.annotation.FlaggedApi; 28 import android.annotation.IntDef; 29 import android.annotation.NonNull; 30 import android.health.connect.MedicalResourceId; 31 import android.os.Parcel; 32 import android.os.Parcelable; 33 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 import java.util.Set; 37 38 /** 39 * A class to capture the user's medical data. This is the class used for all medical resource 40 * types. 41 * 42 * <p>The data representation follows the <a href="https://hl7.org/fhir/">Fast Healthcare 43 * Interoperability Resources (FHIR)</a> standard. 44 */ 45 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD) 46 public final class MedicalResource implements Parcelable { 47 48 /** Medical resource type labelling data as vaccines. */ 49 public static final int MEDICAL_RESOURCE_TYPE_VACCINES = 1; 50 51 /** Medical resource type labelling data as allergies or intolerances. */ 52 public static final int MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES = 2; 53 54 /** Medical resource type labelling data as to do with pregnancy. */ 55 public static final int MEDICAL_RESOURCE_TYPE_PREGNANCY = 3; 56 57 /** Medical resource type labelling data as social history. */ 58 public static final int MEDICAL_RESOURCE_TYPE_SOCIAL_HISTORY = 4; 59 60 /** Medical resource type labelling data as vital signs. */ 61 public static final int MEDICAL_RESOURCE_TYPE_VITAL_SIGNS = 5; 62 63 /** Medical resource type labelling data as results (Laboratory or pathology). */ 64 public static final int MEDICAL_RESOURCE_TYPE_LABORATORY_RESULTS = 6; 65 66 /** 67 * Medical resource type labelling data as medical conditions (clinical condition, problem, 68 * diagnosis etc). 69 */ 70 public static final int MEDICAL_RESOURCE_TYPE_CONDITIONS = 7; 71 72 /** Medical resource type labelling data as procedures (actions taken on or for a patient). */ 73 public static final int MEDICAL_RESOURCE_TYPE_PROCEDURES = 8; 74 75 /** Medical resource type labelling data as medication related. */ 76 public static final int MEDICAL_RESOURCE_TYPE_MEDICATIONS = 9; 77 78 /** 79 * Medical resource type labelling data as related to personal details, including demographic 80 * information such as name, date of birth, and contact details such as address or telephone 81 * numbers. 82 */ 83 public static final int MEDICAL_RESOURCE_TYPE_PERSONAL_DETAILS = 10; 84 85 /** 86 * Medical resource type labelling data as related to practitioners. This is information about 87 * the doctors, nurses, masseurs, physios, etc who have been involved with the user. 88 */ 89 public static final int MEDICAL_RESOURCE_TYPE_PRACTITIONER_DETAILS = 11; 90 91 /** 92 * Medical resource type labelling data as related to an encounter with a practitioner. This 93 * includes visits to healthcare providers and remote encounters such as telephone and 94 * videoconference appointments, and information about the time, location and organization who 95 * is being met. 96 */ 97 public static final int MEDICAL_RESOURCE_TYPE_VISITS = 12; 98 99 /** @hide */ 100 @IntDef({ 101 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES, 102 MEDICAL_RESOURCE_TYPE_CONDITIONS, 103 MEDICAL_RESOURCE_TYPE_LABORATORY_RESULTS, 104 MEDICAL_RESOURCE_TYPE_MEDICATIONS, 105 MEDICAL_RESOURCE_TYPE_PERSONAL_DETAILS, 106 MEDICAL_RESOURCE_TYPE_PRACTITIONER_DETAILS, 107 MEDICAL_RESOURCE_TYPE_PREGNANCY, 108 MEDICAL_RESOURCE_TYPE_PROCEDURES, 109 MEDICAL_RESOURCE_TYPE_SOCIAL_HISTORY, 110 MEDICAL_RESOURCE_TYPE_VACCINES, 111 MEDICAL_RESOURCE_TYPE_VISITS, 112 MEDICAL_RESOURCE_TYPE_VITAL_SIGNS, 113 }) 114 @Retention(RetentionPolicy.SOURCE) 115 public @interface MedicalResourceType {} 116 117 @MedicalResourceType private final int mType; 118 @NonNull private final MedicalResourceId mId; 119 @NonNull private final String mDataSourceId; 120 @NonNull private final FhirVersion mFhirVersion; 121 @NonNull private final FhirResource mFhirResource; 122 123 /** @hide */ 124 private long mLastModifiedTimestamp; 125 126 /** 127 * Creates a new instance of {@link MedicalResource} which takes in {@code 128 * lastModifiedTimestamp} as a parameter as well. The {@code lastModifiedTimestamp} is currently 129 * only used internally to ensure D2D merge process, copies over the exact timestamp of when the 130 * {@link MedicalResource} was modified. 131 * 132 * @hide 133 */ MedicalResource( @edicalResourceType int type, @NonNull String dataSourceId, @NonNull FhirVersion fhirVersion, @NonNull FhirResource fhirResource, long lastModifiedTimestamp)134 public MedicalResource( 135 @MedicalResourceType int type, 136 @NonNull String dataSourceId, 137 @NonNull FhirVersion fhirVersion, 138 @NonNull FhirResource fhirResource, 139 long lastModifiedTimestamp) { 140 this(type, dataSourceId, fhirVersion, fhirResource); 141 mLastModifiedTimestamp = lastModifiedTimestamp; 142 } 143 144 /** 145 * Creates a new instance of {@link MedicalResource}. Please see {@link MedicalResource.Builder} 146 * for more detailed parameters information. 147 */ MedicalResource( @edicalResourceType int type, @NonNull String dataSourceId, @NonNull FhirVersion fhirVersion, @NonNull FhirResource fhirResource)148 private MedicalResource( 149 @MedicalResourceType int type, 150 @NonNull String dataSourceId, 151 @NonNull FhirVersion fhirVersion, 152 @NonNull FhirResource fhirResource) { 153 requireNonNull(dataSourceId); 154 requireNonNull(fhirVersion); 155 requireNonNull(fhirResource); 156 validateMedicalResourceType(type); 157 validateMedicalDataSourceIds(Set.of(dataSourceId)); 158 159 mType = type; 160 mDataSourceId = dataSourceId; 161 mFhirVersion = fhirVersion; 162 mFhirResource = fhirResource; 163 mId = new MedicalResourceId(dataSourceId, fhirResource.getType(), fhirResource.getId()); 164 } 165 166 /** 167 * Constructs this object with the data present in {@code parcel}. It should be in the same 168 * order as {@link MedicalResource#writeToParcel}. 169 */ MedicalResource(@onNull Parcel in)170 private MedicalResource(@NonNull Parcel in) { 171 requireNonNull(in); 172 mType = in.readInt(); 173 validateMedicalResourceType(mType); 174 mDataSourceId = requireNonNull(in.readString()); 175 validateMedicalDataSourceIds(Set.of(mDataSourceId)); 176 mFhirVersion = 177 requireNonNull( 178 in.readParcelable(FhirVersion.class.getClassLoader(), FhirVersion.class)); 179 mFhirResource = 180 requireNonNull( 181 in.readParcelable(FhirResource.class.getClassLoader(), FhirResource.class)); 182 mId = new MedicalResourceId(mDataSourceId, mFhirResource.getType(), mFhirResource.getId()); 183 } 184 185 @NonNull 186 public static final Creator<MedicalResource> CREATOR = 187 new Creator<>() { 188 @Override 189 public MedicalResource createFromParcel(Parcel in) { 190 return new MedicalResource(in); 191 } 192 193 @Override 194 public MedicalResource[] newArray(int size) { 195 return new MedicalResource[size]; 196 } 197 }; 198 199 /** 200 * Returns the medical resource type, assigned by the Android Health Platform at insertion time. 201 * 202 * <p>For a list of supported types, see the {@link MedicalResource} type constants, such as 203 * {@link #MEDICAL_RESOURCE_TYPE_VACCINES}. Clients should be aware that this list is non 204 * exhaustive and may increase in future releases when additional types will need to be handled. 205 */ 206 @MedicalResourceType getType()207 public int getType() { 208 return mType; 209 } 210 211 /** Returns the ID of this {@link MedicalResource} as {@link MedicalResourceId}. */ 212 @NonNull getId()213 public MedicalResourceId getId() { 214 return mId; 215 } 216 217 /** Returns the unique {@link MedicalDataSource} ID of where the data comes from. */ 218 @NonNull getDataSourceId()219 public String getDataSourceId() { 220 return mDataSourceId; 221 } 222 223 /** Returns the FHIR version being used for {@code mFhirResource} */ 224 @NonNull getFhirVersion()225 public FhirVersion getFhirVersion() { 226 return mFhirVersion; 227 } 228 229 /** Returns the enclosed {@link FhirResource} object. */ 230 @NonNull getFhirResource()231 public FhirResource getFhirResource() { 232 return mFhirResource; 233 } 234 235 /** 236 * Returns the last modified timestamp for this {@link MedicalResource}. 237 * 238 * @hide 239 */ getLastModifiedTimestamp()240 public long getLastModifiedTimestamp() { 241 return mLastModifiedTimestamp; 242 } 243 244 @Override describeContents()245 public int describeContents() { 246 return 0; 247 } 248 249 @Override writeToParcel(@onNull Parcel dest, int flags)250 public void writeToParcel(@NonNull Parcel dest, int flags) { 251 requireNonNull(dest); 252 dest.writeInt(getType()); 253 dest.writeString(getDataSourceId()); 254 dest.writeParcelable(getFhirVersion(), 0); 255 dest.writeParcelable(getFhirResource(), 0); 256 } 257 258 /** 259 * Valid set of values for this IntDef. Update this set when add new type or deprecate existing 260 * type. 261 * 262 * @hide 263 */ 264 public static final Set<Integer> VALID_TYPES = 265 Set.of( 266 MEDICAL_RESOURCE_TYPE_ALLERGIES_INTOLERANCES, 267 MEDICAL_RESOURCE_TYPE_CONDITIONS, 268 MEDICAL_RESOURCE_TYPE_LABORATORY_RESULTS, 269 MEDICAL_RESOURCE_TYPE_MEDICATIONS, 270 MEDICAL_RESOURCE_TYPE_PERSONAL_DETAILS, 271 MEDICAL_RESOURCE_TYPE_PRACTITIONER_DETAILS, 272 MEDICAL_RESOURCE_TYPE_PREGNANCY, 273 MEDICAL_RESOURCE_TYPE_PROCEDURES, 274 MEDICAL_RESOURCE_TYPE_SOCIAL_HISTORY, 275 MEDICAL_RESOURCE_TYPE_VACCINES, 276 MEDICAL_RESOURCE_TYPE_VISITS, 277 MEDICAL_RESOURCE_TYPE_VITAL_SIGNS); 278 279 /** 280 * Validates the provided {@code medicalResourceType} is in the {@link 281 * MedicalResource#VALID_TYPES} set. 282 * 283 * <p>Throws {@link IllegalArgumentException} if not. 284 * 285 * @hide 286 */ validateMedicalResourceType(@edicalResourceType int medicalResourceType)287 public static void validateMedicalResourceType(@MedicalResourceType int medicalResourceType) { 288 validateIntDefValue( 289 medicalResourceType, VALID_TYPES, MedicalResourceType.class.getSimpleName()); 290 } 291 292 @Override equals(Object o)293 public boolean equals(Object o) { 294 if (this == o) return true; 295 if (!(o instanceof MedicalResource that)) return false; 296 return getType() == that.getType() 297 && getDataSourceId().equals(that.getDataSourceId()) 298 && getFhirVersion().equals(that.getFhirVersion()) 299 && getFhirResource().equals(that.getFhirResource()); 300 } 301 302 @Override hashCode()303 public int hashCode() { 304 return hash(getType(), getDataSourceId(), getFhirVersion(), getFhirResource()); 305 } 306 307 @Override toString()308 public String toString() { 309 StringBuilder sb = new StringBuilder(); 310 sb.append(this.getClass().getSimpleName()).append("{"); 311 sb.append("type=").append(getType()); 312 sb.append(",dataSourceId=").append(getDataSourceId()); 313 sb.append(",fhirVersion=").append(getFhirVersion()); 314 sb.append(",fhirResource=").append(getFhirResource()); 315 sb.append("}"); 316 return sb.toString(); 317 } 318 319 /** Builder class for {@link MedicalResource}. */ 320 public static final class Builder { 321 @MedicalResourceType private int mType; 322 @NonNull private String mDataSourceId; 323 @NonNull private FhirVersion mFhirVersion; 324 @NonNull private FhirResource mFhirResource; 325 326 /** 327 * Constructs a new {@link MedicalResource.Builder} instance. 328 * 329 * @param type The medical resource type. 330 * @param dataSourceId The unique {@link MedicalDataSource} ID of where the data comes from. 331 * @param fhirVersion the FHIR version being used for {@code fhirResource}. 332 * @param fhirResource The enclosed {@link FhirResource} object. 333 * @throws IllegalArgumentException if the provided medical resource {@code type} is not a 334 * valid supported type, or {@code dataSourceId} is not a valid ID. 335 */ Builder( @edicalResourceType int type, @NonNull String dataSourceId, @NonNull FhirVersion fhirVersion, @NonNull FhirResource fhirResource)336 public Builder( 337 @MedicalResourceType int type, 338 @NonNull String dataSourceId, 339 @NonNull FhirVersion fhirVersion, 340 @NonNull FhirResource fhirResource) { 341 requireNonNull(dataSourceId); 342 requireNonNull(fhirVersion); 343 requireNonNull(fhirResource); 344 validateMedicalResourceType(type); 345 validateMedicalDataSourceIds(Set.of(dataSourceId)); 346 347 mType = type; 348 mDataSourceId = dataSourceId; 349 mFhirVersion = fhirVersion; 350 mFhirResource = fhirResource; 351 } 352 353 /** Constructs a clone of the other {@link MedicalResource.Builder}. */ Builder(@onNull Builder other)354 public Builder(@NonNull Builder other) { 355 requireNonNull(other); 356 mType = other.mType; 357 mDataSourceId = other.mDataSourceId; 358 mFhirVersion = other.mFhirVersion; 359 mFhirResource = other.mFhirResource; 360 } 361 362 /** Constructs a clone of the other {@link MedicalResource} instance. */ Builder(@onNull MedicalResource other)363 public Builder(@NonNull MedicalResource other) { 364 requireNonNull(other); 365 mType = other.getType(); 366 mDataSourceId = other.getDataSourceId(); 367 mFhirVersion = other.getFhirVersion(); 368 mFhirResource = other.getFhirResource(); 369 } 370 371 /** 372 * Sets the medical resource type. 373 * 374 * @throws IllegalArgumentException if the provided medical resource {@code type} is not a 375 * valid supported type. 376 */ 377 @NonNull setType(@edicalResourceType int type)378 public Builder setType(@MedicalResourceType int type) { 379 validateMedicalResourceType(type); 380 mType = type; 381 return this; 382 } 383 384 /** 385 * Sets the unique {@link MedicalDataSource} ID of where the data comes from. 386 * 387 * @throws IllegalArgumentException if the provided {@code dataSourceId} is not a valid ID. 388 */ 389 @NonNull setDataSourceId(@onNull String dataSourceId)390 public Builder setDataSourceId(@NonNull String dataSourceId) { 391 requireNonNull(dataSourceId); 392 validateMedicalDataSourceIds(Set.of(dataSourceId)); 393 mDataSourceId = dataSourceId; 394 return this; 395 } 396 397 /** Sets the FHIR version being used for {@code fhirResource}. */ 398 @NonNull setFhirVersion(@onNull FhirVersion fhirVersion)399 public Builder setFhirVersion(@NonNull FhirVersion fhirVersion) { 400 requireNonNull(fhirVersion); 401 mFhirVersion = fhirVersion; 402 return this; 403 } 404 405 /** Sets the enclosed {@link FhirResource} object. */ 406 @NonNull setFhirResource(@onNull FhirResource fhirResource)407 public Builder setFhirResource(@NonNull FhirResource fhirResource) { 408 requireNonNull(fhirResource); 409 mFhirResource = fhirResource; 410 return this; 411 } 412 413 /** Returns a new instance of {@link MedicalResource} with the specified parameters. */ 414 @NonNull build()415 public MedicalResource build() { 416 return new MedicalResource(mType, mDataSourceId, mFhirVersion, mFhirResource); 417 } 418 } 419 } 420