• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
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.health.connect.datatypes.FhirVersion;
28 import android.net.Uri;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 
32 /**
33  * A create request for {@link HealthConnectManager#createMedicalDataSource}.
34  *
35  * <p>Medical data is represented using the <a href="https://hl7.org/fhir/">Fast Healthcare
36  * Interoperability Resources (FHIR)</a> standard.
37  */
38 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD)
39 public final class CreateMedicalDataSourceRequest implements Parcelable {
40     // The character limit for the {@code mDisplayName}
41     private static final int DISPLAY_NAME_CHARACTER_LIMIT = 90;
42     // The character limit for the {@code mFhirBaseUri}
43     private static final int FHIR_BASE_URI_CHARACTER_LIMIT = 2000;
44 
45     @NonNull private final Uri mFhirBaseUri;
46     @NonNull private final String mDisplayName;
47     @NonNull private final FhirVersion mFhirVersion;
48     private long mDataSize;
49 
50     @NonNull
51     public static final Creator<CreateMedicalDataSourceRequest> CREATOR =
52             new Creator<CreateMedicalDataSourceRequest>() {
53                 @NonNull
54                 @Override
55                 /*
56                  * @throws IllegalArgumentException if the {@code mFhirBaseUri} or
57                  * {@code mDisplayName} exceed the character limits.
58                  */
59                 public CreateMedicalDataSourceRequest createFromParcel(@NonNull Parcel in) {
60                     return new CreateMedicalDataSourceRequest(in);
61                 }
62 
63                 @NonNull
64                 @Override
65                 public CreateMedicalDataSourceRequest[] newArray(int size) {
66                     return new CreateMedicalDataSourceRequest[size];
67                 }
68             };
69 
70     /**
71      * Creates a new instance of {@link CreateMedicalDataSourceRequest}. Please see {@link
72      * CreateMedicalDataSourceRequest.Builder} for more detailed parameters information.
73      */
CreateMedicalDataSourceRequest( @onNull Uri fhirBaseUri, @NonNull String displayName, @NonNull FhirVersion fhirVersion)74     private CreateMedicalDataSourceRequest(
75             @NonNull Uri fhirBaseUri,
76             @NonNull String displayName,
77             @NonNull FhirVersion fhirVersion) {
78         requireNonNull(fhirBaseUri);
79         requireNonNull(displayName);
80         requireNonNull(fhirVersion);
81         validateFhirBaseUriCharacterLimit(fhirBaseUri);
82         validateDisplayNameCharacterLimit(displayName);
83         validateFhirVersion(fhirVersion);
84 
85         mFhirBaseUri = fhirBaseUri;
86         mDisplayName = displayName;
87         mFhirVersion = fhirVersion;
88     }
89 
CreateMedicalDataSourceRequest(@onNull Parcel in)90     private CreateMedicalDataSourceRequest(@NonNull Parcel in) {
91         requireNonNull(in);
92         mDataSize = in.dataSize();
93 
94         mFhirBaseUri = requireNonNull(in.readParcelable(Uri.class.getClassLoader(), Uri.class));
95         mDisplayName = requireNonNull(in.readString());
96         mFhirVersion =
97                 requireNonNull(
98                         in.readParcelable(FhirVersion.class.getClassLoader(), FhirVersion.class));
99 
100         validateFhirBaseUriCharacterLimit(mFhirBaseUri);
101         validateDisplayNameCharacterLimit(mDisplayName);
102         validateFhirVersion(mFhirVersion);
103     }
104 
105     /**
106      * Returns the FHIR base URI. For data coming from a FHIR server this is <a
107      * href="https://hl7.org/fhir/R4/http.html#root">the base URL</a>.
108      */
109     @NonNull
getFhirBaseUri()110     public Uri getFhirBaseUri() {
111         return mFhirBaseUri;
112     }
113 
114     /** Returns the display name. For the request to succeed this must be unique per app. */
115     @NonNull
getDisplayName()116     public String getDisplayName() {
117         return mDisplayName;
118     }
119 
120     /**
121      * Returns the FHIR version. For the request to succeeds this must be a version supported by
122      * Health Connect, as documented on the {@link FhirVersion}.
123      */
124     @NonNull
getFhirVersion()125     public FhirVersion getFhirVersion() {
126         return mFhirVersion;
127     }
128 
129     /**
130      * Returns the size of the parcel when the class was created from Parcel.
131      *
132      * @hide
133      */
getDataSize()134     public long getDataSize() {
135         return mDataSize;
136     }
137 
138     @Override
describeContents()139     public int describeContents() {
140         return 0;
141     }
142 
143     @Override
writeToParcel(@onNull Parcel dest, int flags)144     public void writeToParcel(@NonNull Parcel dest, int flags) {
145         dest.writeParcelable(mFhirBaseUri, 0);
146         dest.writeString(mDisplayName);
147         dest.writeParcelable(mFhirVersion, 0);
148     }
149 
150     @Override
equals(@ullable Object o)151     public boolean equals(@Nullable Object o) {
152         if (this == o) return true;
153         if (!(o instanceof CreateMedicalDataSourceRequest that)) return false;
154         return getFhirBaseUri().equals(that.getFhirBaseUri())
155                 && getDisplayName().equals(that.getDisplayName())
156                 && getFhirVersion().equals(that.getFhirVersion());
157     }
158 
159     @Override
hashCode()160     public int hashCode() {
161         return hash(getFhirBaseUri(), getDisplayName(), getFhirVersion());
162     }
163 
164     @Override
toString()165     public String toString() {
166         StringBuilder sb = new StringBuilder();
167         sb.append(this.getClass().getSimpleName()).append("{");
168         sb.append("fhirBaseUri=").append(getFhirBaseUri());
169         sb.append(",displayName=").append(getDisplayName());
170         sb.append(",fhirVersion=").append(getFhirVersion());
171         sb.append("}");
172         return sb.toString();
173     }
174 
175     /** Builder class for {@link CreateMedicalDataSourceRequest}. */
176     public static final class Builder {
177         @NonNull private Uri mFhirBaseUri;
178         @NonNull private String mDisplayName;
179         @NonNull private FhirVersion mFhirVersion;
180 
181         /**
182          * Constructs a new {@link CreateMedicalDataSourceRequest.Builder} instance.
183          *
184          * @param fhirBaseUri The FHIR base URI of the data source. For data coming from a FHIR
185          *     server this should be the <a href="https://hl7.org/fhir/R4/http.html#root">FHIR base
186          *     URL</a> (e.g. `https://example.com/fhir`). If the data is generated by an app without
187          *     a FHIR URL, this can be populated by a unique and understandable URI defined by the
188          *     app (e.g. `myapp://..`) that points to the source of the data. We recommend not to
189          *     use a domain name that you don't control. If your app supports <a
190          *     href="https://developer.android.com/training/app-links/deep-linking">app deep
191          *     linking</a>, this URI would ideally link to the source data. The maximum length for
192          *     the URI is 2000 characters.
193          * @param displayName The display name that describes the data source. The maximum length
194          *     for the display name is 90 characters. This must be unique per app.
195          * @param fhirVersion The FHIR version of the medical data that will be linked to this data
196          *     source. This has to be a version supported by Health Connect, as documented on the
197          *     {@link FhirVersion}.
198          */
Builder( @onNull Uri fhirBaseUri, @NonNull String displayName, @NonNull FhirVersion fhirVersion)199         public Builder(
200                 @NonNull Uri fhirBaseUri,
201                 @NonNull String displayName,
202                 @NonNull FhirVersion fhirVersion) {
203             requireNonNull(fhirBaseUri);
204             requireNonNull(displayName);
205             requireNonNull(fhirVersion);
206 
207             mFhirBaseUri = fhirBaseUri;
208             mDisplayName = displayName;
209             mFhirVersion = fhirVersion;
210         }
211 
212         /** Constructs a clone of the other {@link CreateMedicalDataSourceRequest.Builder}. */
Builder(@onNull Builder other)213         public Builder(@NonNull Builder other) {
214             requireNonNull(other);
215             mFhirBaseUri = other.mFhirBaseUri;
216             mDisplayName = other.mDisplayName;
217             mFhirVersion = other.mFhirVersion;
218         }
219 
220         /** Constructs a clone of the other {@link CreateMedicalDataSourceRequest} instance. */
Builder(@onNull CreateMedicalDataSourceRequest other)221         public Builder(@NonNull CreateMedicalDataSourceRequest other) {
222             requireNonNull(other);
223             mFhirBaseUri = other.getFhirBaseUri();
224             mDisplayName = other.getDisplayName();
225             mFhirVersion = other.getFhirVersion();
226         }
227 
228         /**
229          * Sets the FHIR base URI. For data coming from a FHIR server this should be the <a
230          * href="https://hl7.org/fhir/R4/http.html#root">FHIR base URL</a> (e.g.
231          * `https://example.com/fhir`).
232          *
233          * <p>If the data is generated by an app without a FHIR base URL, this can be populated by a
234          * URI defined by the app (e.g. `myapp://..`) that should:
235          *
236          * <ul>
237          *   <li>Be a unique and understandable URI.
238          *   <li>Point to the source of the data. We recommend not to use a domain name that you
239          *       don't control.
240          *   <li>Ideally allow linking to the data source if your app supports <a
241          *       href="https://developer.android.com/training/app-links/deep-linking">app deep
242          *       linking</a> to the data.
243          * </ul>
244          *
245          * <p>The URI may not exceed 2000 characters.
246          */
247         @NonNull
setFhirBaseUri(@onNull Uri fhirBaseUri)248         public Builder setFhirBaseUri(@NonNull Uri fhirBaseUri) {
249             requireNonNull(fhirBaseUri);
250             mFhirBaseUri = fhirBaseUri;
251             return this;
252         }
253 
254         /**
255          * Sets the display name. For the request to succeed this must be unique per app.
256          *
257          * <p>The display name may not exceed 90 characters.
258          */
259         @NonNull
setDisplayName(@onNull String displayName)260         public Builder setDisplayName(@NonNull String displayName) {
261             requireNonNull(displayName);
262             mDisplayName = displayName;
263             return this;
264         }
265 
266         /**
267          * Sets the FHIR version of data from this data source.
268          *
269          * <p>This has to be a version supported by Health Connect, as documented on the {@link
270          * FhirVersion}.
271          */
272         @NonNull
setFhirVersion(@onNull FhirVersion fhirVersion)273         public Builder setFhirVersion(@NonNull FhirVersion fhirVersion) {
274             requireNonNull(fhirVersion);
275             mFhirVersion = fhirVersion;
276             return this;
277         }
278 
279         /**
280          * Returns a new instance of {@link CreateMedicalDataSourceRequest} with the specified
281          * parameters.
282          *
283          * @throws IllegalArgumentException if the {@code mFhirBaseUri} or {@code mDisplayName}
284          *     exceed the character limits or if the {@code mFhirVersion} is not supported by Health
285          *     Connect.
286          */
287         @NonNull
build()288         public CreateMedicalDataSourceRequest build() {
289             return new CreateMedicalDataSourceRequest(mFhirBaseUri, mDisplayName, mFhirVersion);
290         }
291     }
292 
validateDisplayNameCharacterLimit(String displayName)293     private static void validateDisplayNameCharacterLimit(String displayName) {
294         if (displayName.isEmpty()) {
295             throw new IllegalArgumentException("Display name cannot be empty.");
296         }
297         if (displayName.length() > DISPLAY_NAME_CHARACTER_LIMIT) {
298             throw new IllegalArgumentException(
299                     "Display name cannot be longer than "
300                             + DISPLAY_NAME_CHARACTER_LIMIT
301                             + " characters.");
302         }
303     }
304 
validateFhirBaseUriCharacterLimit(Uri fhirBaseUri)305     private static void validateFhirBaseUriCharacterLimit(Uri fhirBaseUri) {
306         String fhirBaseUriString = fhirBaseUri.toString();
307         if (fhirBaseUriString.isEmpty()) {
308             throw new IllegalArgumentException("FHIR base URI cannot be empty.");
309         }
310         if (fhirBaseUriString.length() > FHIR_BASE_URI_CHARACTER_LIMIT) {
311             throw new IllegalArgumentException(
312                     "FHIR base URI cannot be longer than "
313                             + FHIR_BASE_URI_CHARACTER_LIMIT
314                             + " characters.");
315         }
316     }
317 
validateFhirVersion(FhirVersion fhirVersion)318     private static void validateFhirVersion(FhirVersion fhirVersion) {
319         if (!fhirVersion.isSupportedFhirVersion()) {
320             throw new IllegalArgumentException("Unsupported FHIR version " + fhirVersion + ".");
321         }
322     }
323 }
324