• 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.health.connect.datatypes.MedicalDataSource;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.util.ArraySet;
30 
31 import java.util.ArrayList;
32 import java.util.HashSet;
33 import java.util.Objects;
34 import java.util.Set;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 
38 /**
39  * A create request for {@link HealthConnectManager#getMedicalDataSources}.
40  *
41  * <p>If no {@link GetMedicalDataSourcesRequest#getPackageNames() package names} are set, requests
42  * all {@link MedicalDataSource}s from all packages. Otherwise the request is limited to the
43  * requested package names.
44  */
45 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD)
46 public final class GetMedicalDataSourcesRequest implements Parcelable {
47     @NonNull private final Set<String> mPackageNames;
48 
49     // A full Java-language-style package name for the Android app can contain uppercase
50     // or lowercase letters, numbers, and underscores ('_'). It must have at least two segments (one
51     // or more dots), and individual package name parts can only start with letters. See the
52     // <a
53     // href="https://developer.android.com/guide/topics/manifest/manifest-element.html#package">Android developer doc</a>.
54     private static final String PACKAGE_NAME_REGEX =
55             "^([A-Za-z][a-zA-Z0-9_]*\\.)+[A-Za-z][a-zA-Z0-9_]*$";
56 
57     @NonNull
58     public static final Creator<GetMedicalDataSourcesRequest> CREATOR =
59             new Creator<>() {
60                 @Override
61                 public GetMedicalDataSourcesRequest createFromParcel(Parcel in) {
62                     return new GetMedicalDataSourcesRequest(in);
63                 }
64 
65                 @Override
66                 public GetMedicalDataSourcesRequest[] newArray(int size) {
67                     return new GetMedicalDataSourcesRequest[size];
68                 }
69             };
70 
71     /**
72      * Creates a new instance of {@link GetMedicalDataSourcesRequest}. Please see {@link
73      * GetMedicalDataSourcesRequest.Builder} for more detailed parameters information.
74      */
GetMedicalDataSourcesRequest(@onNull Set<String> packageNames)75     private GetMedicalDataSourcesRequest(@NonNull Set<String> packageNames) {
76         Objects.requireNonNull(packageNames);
77         validatePackageNames(packageNames);
78         mPackageNames = packageNames;
79     }
80 
81     /**
82      * Constructs this object with the data present in {@code parcel}. It should be in the same
83      * order as {@link #writeToParcel}.
84      */
GetMedicalDataSourcesRequest(@onNull Parcel in)85     private GetMedicalDataSourcesRequest(@NonNull Parcel in) {
86         Objects.requireNonNull(in);
87         mPackageNames = new ArraySet<>(requireNonNull(in.createStringArrayList()));
88         validatePackageNames(mPackageNames);
89     }
90 
91     /**
92      * Returns the package names for which {@link MedicalDataSource}s are being requested, or an
93      * empty set for no filter.
94      */
95     @NonNull
getPackageNames()96     public Set<String> getPackageNames() {
97         return new ArraySet<>(mPackageNames);
98     }
99 
100     /**
101      * Validates all of the provided {@code packageNames} are valid, which matches with the {@link
102      * #PACKAGE_NAME_REGEX}.
103      *
104      * @throws IllegalArgumentException with all invalid package names if not all {@code
105      *     packageNames} are valid.
106      */
validatePackageNames(Set<String> packageNames)107     private static void validatePackageNames(Set<String> packageNames) {
108         Pattern pattern = Pattern.compile(PACKAGE_NAME_REGEX);
109 
110         Set<String> invalidPackageNames = new HashSet<>();
111         for (String packageName : packageNames) {
112             Matcher matcher = pattern.matcher(packageName);
113             if (!matcher.matches()) {
114                 invalidPackageNames.add(packageName);
115             }
116         }
117         if (!invalidPackageNames.isEmpty()) {
118             throw new IllegalArgumentException("Invalid package name(s): " + invalidPackageNames);
119         }
120     }
121 
122     @Override
describeContents()123     public int describeContents() {
124         return 0;
125     }
126 
127     @Override
writeToParcel(@onNull Parcel dest, int flags)128     public void writeToParcel(@NonNull Parcel dest, int flags) {
129         dest.writeStringList(new ArrayList<>(mPackageNames));
130     }
131 
132     @Override
equals(Object o)133     public boolean equals(Object o) {
134         if (this == o) return true;
135         if (!(o instanceof GetMedicalDataSourcesRequest that)) return false;
136         return mPackageNames.equals(that.mPackageNames);
137     }
138 
139     @Override
hashCode()140     public int hashCode() {
141         return hash(mPackageNames);
142     }
143 
144     @Override
toString()145     public String toString() {
146         StringBuilder sb = new StringBuilder();
147         sb.append(this.getClass().getSimpleName()).append("{");
148         sb.append("packageNames=").append(mPackageNames);
149         sb.append("}");
150         return sb.toString();
151     }
152 
153     /** Builder class for {@link GetMedicalDataSourcesRequest}. */
154     public static final class Builder {
155         private final Set<String> mPackageNames = new ArraySet<>();
156 
157         /** Constructs a new {@link GetMedicalDataSourcesRequest.Builder} with no filters set. */
Builder()158         public Builder() {}
159 
160         /** Constructs a clone of the other {@link GetMedicalDataSourcesRequest.Builder}. */
Builder(@onNull Builder other)161         public Builder(@NonNull Builder other) {
162             requireNonNull(other);
163             mPackageNames.addAll(other.mPackageNames);
164         }
165 
166         /** Constructs a clone of the other {@link GetMedicalDataSourcesRequest} instance. */
Builder(@onNull GetMedicalDataSourcesRequest other)167         public Builder(@NonNull GetMedicalDataSourcesRequest other) {
168             requireNonNull(other);
169             mPackageNames.addAll(other.getPackageNames());
170         }
171 
172         /**
173          * Adds a package name to limit this request to.
174          *
175          * <p>If the list of package names is empty, {@link MedicalDataSource}s for all packages
176          * will be requested. Otherwise only those for the added package names are requested.
177          *
178          * @throws IllegalArgumentException if the provided {@code packageName} is not valid.
179          */
180         @NonNull
addPackageName(@onNull String packageName)181         public Builder addPackageName(@NonNull String packageName) {
182             Objects.requireNonNull(packageName);
183             validatePackageNames(Set.of(packageName));
184             mPackageNames.add(packageName);
185             return this;
186         }
187 
188         /** Clears all package names. */
189         @NonNull
clearPackageNames()190         public Builder clearPackageNames() {
191             mPackageNames.clear();
192             return this;
193         }
194 
195         /**
196          * Returns a new instance of {@link GetMedicalDataSourcesRequest} with the specified
197          * parameters.
198          */
199         @NonNull
build()200         public GetMedicalDataSourcesRequest build() {
201             return new GetMedicalDataSourcesRequest(mPackageNames);
202         }
203     }
204 }
205