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.validation.ValidationUtils.validateIntDefValue;
20 
21 import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD;
22 
23 import static java.util.Objects.hash;
24 import static java.util.Objects.requireNonNull;
25 
26 import android.annotation.FlaggedApi;
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.util.Set;
35 
36 /**
37  * Captures the user's medical data. This is the class used for all medical resource types, and the
38  * type is specified via {@link MedicalResourceType}.
39  */
40 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD)
41 public final class MedicalResource implements Parcelable {
42     /** Unknown medical resource type. */
43     public static final int MEDICAL_RESOURCE_TYPE_UNKNOWN = 0;
44 
45     /** Medical resource type to capture the immunizations data. */
46     public static final int MEDICAL_RESOURCE_TYPE_IMMUNIZATION = 1;
47 
48     /** @hide */
49     @IntDef({MEDICAL_RESOURCE_TYPE_UNKNOWN, MEDICAL_RESOURCE_TYPE_IMMUNIZATION})
50     @Retention(RetentionPolicy.SOURCE)
51     public @interface MedicalResourceType {}
52 
53     @NonNull private final String mId;
54     @MedicalResourceType private final int mType;
55     @NonNull private final String mDataSourceId;
56     @NonNull private final String mData;
57 
58     /**
59      * @param id The unique identifier of this data, assigned by the Android Health Platform at
60      *     insertion time.
61      * @param type The medical resource type assigned by the Android Health Platform at insertion
62      *     time.
63      * @param dataSourceId Where the data comes from.
64      * @param data The FHIR resource data in JSON representation.
65      */
MedicalResource( @onNull String id, @MedicalResourceType int type, @NonNull String dataSourceId, @NonNull String data)66     private MedicalResource(
67             @NonNull String id,
68             @MedicalResourceType int type,
69             @NonNull String dataSourceId,
70             @NonNull String data) {
71         requireNonNull(id);
72         requireNonNull(dataSourceId);
73         requireNonNull(data);
74         validateMedicalResourceType(type);
75 
76         mId = id;
77         mType = type;
78         mDataSourceId = dataSourceId;
79         mData = data;
80     }
81 
82     /**
83      * Constructs this object with the data present in {@code parcel}. It should be in the same
84      * order as {@link MedicalResource#writeToParcel}.
85      */
MedicalResource(@onNull Parcel in)86     private MedicalResource(@NonNull Parcel in) {
87         requireNonNull(in);
88         mId = requireNonNull(in.readString());
89         mType = in.readInt();
90         mDataSourceId = requireNonNull(in.readString());
91         mData = requireNonNull(in.readString());
92     }
93 
94     @NonNull
95     public static final Creator<MedicalResource> CREATOR =
96             new Creator<>() {
97                 /**
98                  * Reading from the {@link Parcel} should have the same order as {@link
99                  * MedicalResource#writeToParcel}.
100                  */
101                 @Override
102                 public MedicalResource createFromParcel(Parcel in) {
103                     return new MedicalResource(in);
104                 }
105 
106                 @Override
107                 public MedicalResource[] newArray(int size) {
108                     return new MedicalResource[size];
109                 }
110             };
111 
112     /** Returns the unique identifier of this data. */
113     @NonNull
getId()114     public String getId() {
115         return mId;
116     }
117 
118     /** Returns the medical resource type. */
119     @MedicalResourceType
getType()120     public int getType() {
121         return mType;
122     }
123 
124     /** Returns The data source ID where the data comes from. */
125     @NonNull
getDataSourceId()126     public String getDataSourceId() {
127         return mDataSourceId;
128     }
129 
130     /** Returns the FHIR resource data in JSON representation. */
131     @NonNull
getData()132     public String getData() {
133         return mData;
134     }
135 
136     @Override
describeContents()137     public int describeContents() {
138         return 0;
139     }
140 
141     /** Populates a {@link Parcel} with the self information. */
142     @Override
writeToParcel(@onNull Parcel dest, int flags)143     public void writeToParcel(@NonNull Parcel dest, int flags) {
144         requireNonNull(dest);
145         dest.writeString(getId());
146         dest.writeInt(getType());
147         dest.writeString(getDataSourceId());
148         dest.writeString(getData());
149     }
150 
151     /**
152      * Valid set of values for this IntDef. Update this set when add new type or deprecate existing
153      * type.
154      *
155      * @hide
156      */
157     public static final Set<Integer> VALID_TYPES =
158             Set.of(MEDICAL_RESOURCE_TYPE_UNKNOWN, MEDICAL_RESOURCE_TYPE_IMMUNIZATION);
159 
160     /**
161      * Validates the provided {@code medicalResourceType} is in the {@link
162      * MedicalResource#VALID_TYPES} set.
163      *
164      * <p>Throws {@link IllegalArgumentException} if not.
165      *
166      * @hide
167      */
validateMedicalResourceType(@edicalResourceType int medicalResourceType)168     public static void validateMedicalResourceType(@MedicalResourceType int medicalResourceType) {
169         validateIntDefValue(
170                 medicalResourceType, VALID_TYPES, MedicalResourceType.class.getSimpleName());
171     }
172 
173     /** Indicates whether some other object is "equal to" this one. */
174     @Override
equals(Object o)175     public boolean equals(Object o) {
176         if (this == o) return true;
177         if (!(o instanceof MedicalResource that)) return false;
178         return getId().equals(that.getId())
179                 && getType() == that.getType()
180                 && getDataSourceId().equals(that.getDataSourceId())
181                 && getData().equals(that.getData());
182     }
183 
184     /** Returns a hash code value for the object. */
185     @Override
hashCode()186     public int hashCode() {
187         return hash(getId(), getType(), getDataSourceId(), getData());
188     }
189 
190     /** Returns a string representation of this {@link MedicalResource}. */
191     @Override
toString()192     public String toString() {
193         StringBuilder sb = new StringBuilder();
194         sb.append(this.getClass().getSimpleName()).append("{");
195         sb.append("id=").append(getId());
196         sb.append(",type=").append(getType());
197         sb.append(",dataSourceId=").append(getDataSourceId());
198         sb.append(",data=").append(getData());
199         sb.append("}");
200         return sb.toString();
201     }
202 
203     /** Builder class for {@link MedicalResource} */
204     public static final class Builder {
205         @NonNull private String mId;
206         @MedicalResourceType private int mType;
207         @NonNull private String mDataSourceId;
208         @NonNull private String mData;
209 
Builder( @onNull String id, @MedicalResourceType int type, @NonNull String dataSourceId, @NonNull String data)210         public Builder(
211                 @NonNull String id,
212                 @MedicalResourceType int type,
213                 @NonNull String dataSourceId,
214                 @NonNull String data) {
215             requireNonNull(id);
216             requireNonNull(dataSourceId);
217             requireNonNull(data);
218             validateMedicalResourceType(type);
219 
220             mId = id;
221             mType = type;
222             mDataSourceId = dataSourceId;
223             mData = data;
224         }
225 
Builder(@onNull Builder original)226         public Builder(@NonNull Builder original) {
227             requireNonNull(original);
228             mId = original.mId;
229             mType = original.mType;
230             mDataSourceId = original.mDataSourceId;
231             mData = original.mData;
232         }
233 
Builder(@onNull MedicalResource original)234         public Builder(@NonNull MedicalResource original) {
235             requireNonNull(original);
236             mId = original.getId();
237             mType = original.getType();
238             mDataSourceId = original.getDataSourceId();
239             mData = original.getData();
240         }
241 
242         /**
243          * Sets the unique identifier of this data, assigned by the Android Health Platform at
244          * insertion time.
245          */
246         @NonNull
setId(@onNull String id)247         public Builder setId(@NonNull String id) {
248             requireNonNull(id);
249             mId = id;
250             return this;
251         }
252 
253         /**
254          * Sets the medical resource type, assigned by the Android Health Platform at insertion
255          * time.
256          */
257         @NonNull
setType(@edicalResourceType int type)258         public Builder setType(@MedicalResourceType int type) {
259             validateMedicalResourceType(type);
260             mType = type;
261             return this;
262         }
263 
264         /** Sets the data source ID where the data comes from. */
265         @NonNull
setDataSourceId(@onNull String dataSourceId)266         public Builder setDataSourceId(@NonNull String dataSourceId) {
267             requireNonNull(dataSourceId);
268             mDataSourceId = dataSourceId;
269             return this;
270         }
271 
272         /** Sets the FHIR resource data in JSON representation. */
273         @NonNull
setData(@onNull String data)274         public Builder setData(@NonNull String data) {
275             requireNonNull(data);
276             mData = data;
277             return this;
278         }
279 
280         /** Returns a new instance of {@link MedicalResource} with the specified parameters. */
281         @NonNull
build()282         public MedicalResource build() {
283             return new MedicalResource(mId, mType, mDataSourceId, mData);
284         }
285     }
286 }
287