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 android.health.connect.datatypes.MedicalDataSource.validateMedicalDataSourceIds; 20 import static android.health.connect.datatypes.MedicalResource.validateMedicalResourceType; 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.NonNull; 29 import android.health.connect.datatypes.MedicalDataSource; 30 import android.health.connect.datatypes.MedicalResource; 31 import android.health.connect.datatypes.MedicalResource.MedicalResourceType; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 35 import java.util.ArrayList; 36 import java.util.HashSet; 37 import java.util.Set; 38 39 /** 40 * A delete request for {@link HealthConnectManager#deleteMedicalResources}. 41 * 42 * <p>Each field in the request acts as a cumulative filter. So if a set of data sources and a set 43 * of types are specified, then only resources which are both from data sources in the given set and 44 * of types in the given set are deleted. 45 * 46 * <p>At least one filter must be specified - you cannot construct a request to say delete 47 * everything. And for any given requirement set, it must be a non-empty set (empty means the filter 48 * does not exist). 49 */ 50 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD) 51 public final class DeleteMedicalResourcesRequest implements Parcelable { 52 @NonNull private final Set<String> mDataSourceIds; 53 @NonNull @MedicalResourceType private final Set<Integer> mMedicalResourceTypes; 54 55 /** 56 * Creates a new instance of {@link DeleteMedicalResourcesRequest}. Please see {@link 57 * DeleteMedicalResourcesRequest.Builder} for more detailed parameters information. 58 */ DeleteMedicalResourcesRequest( @onNull Set<String> dataSourceIds, @NonNull @MedicalResourceType Set<Integer> medicalResourceTypes)59 private DeleteMedicalResourcesRequest( 60 @NonNull Set<String> dataSourceIds, 61 @NonNull @MedicalResourceType Set<Integer> medicalResourceTypes) { 62 if (dataSourceIds.isEmpty() && medicalResourceTypes.isEmpty()) { 63 throw new IllegalArgumentException( 64 "No restrictions specified for delete. The request must restrict by data source" 65 + " or resource type"); 66 } 67 validateMedicalDataSourceIds(dataSourceIds); 68 medicalResourceTypes.forEach(MedicalResource::validateMedicalResourceType); 69 mDataSourceIds = dataSourceIds; 70 mMedicalResourceTypes = medicalResourceTypes; 71 } 72 73 /** 74 * Constructs this object with the data present in {@code parcel}. It should be in the same 75 * order as {@link DeleteMedicalResourcesRequest#writeToParcel}. 76 */ DeleteMedicalResourcesRequest(@onNull Parcel in)77 private DeleteMedicalResourcesRequest(@NonNull Parcel in) { 78 requireNonNull(in); 79 ArrayList<String> dataSourceIdList = requireNonNull(in.createStringArrayList()); 80 int[] resourceTypes = requireNonNull(in.createIntArray()); 81 if (dataSourceIdList.isEmpty() && resourceTypes.length == 0) { 82 throw new IllegalArgumentException("Empty data sources and resource types in parcel"); 83 } 84 mDataSourceIds = new HashSet<>(dataSourceIdList); 85 validateMedicalDataSourceIds(mDataSourceIds); 86 mMedicalResourceTypes = new HashSet<>(); 87 for (int resourceType : resourceTypes) { 88 validateMedicalResourceType(resourceType); 89 mMedicalResourceTypes.add(resourceType); 90 } 91 } 92 93 @NonNull 94 public static final Creator<DeleteMedicalResourcesRequest> CREATOR = 95 new Creator<>() { 96 @Override 97 public DeleteMedicalResourcesRequest createFromParcel(Parcel in) { 98 return new DeleteMedicalResourcesRequest(in); 99 } 100 101 @Override 102 public DeleteMedicalResourcesRequest[] newArray(int size) { 103 return new DeleteMedicalResourcesRequest[size]; 104 } 105 }; 106 107 /** 108 * Returns the IDs for the {@link MedicalDataSource} that are being requested to delete. 109 * 110 * <p>These IDs should come from {@link HealthConnectManager#createMedicalDataSource}, or other 111 * {@link HealthConnectManager} data source methods. 112 * 113 * <p>If the set is empty it means resources from any data source should be deleted. 114 */ 115 @NonNull getDataSourceIds()116 public Set<String> getDataSourceIds() { 117 return new HashSet<>(mDataSourceIds); 118 } 119 120 /** 121 * Returns the {@link MedicalResource} types that should be deleted. 122 * 123 * <p>If the set is empty it means resources of all types should be deleted. 124 */ 125 @NonNull 126 @MedicalResourceType getMedicalResourceTypes()127 public Set<Integer> getMedicalResourceTypes() { 128 return new HashSet<>(mMedicalResourceTypes); 129 } 130 131 @Override describeContents()132 public int describeContents() { 133 return 0; 134 } 135 136 @Override writeToParcel(@onNull Parcel dest, int flags)137 public void writeToParcel(@NonNull Parcel dest, int flags) { 138 dest.writeStringList(new ArrayList<>(mDataSourceIds)); 139 dest.writeIntArray(mMedicalResourceTypes.stream().mapToInt(Integer::intValue).toArray()); 140 } 141 142 @Override equals(Object o)143 public boolean equals(Object o) { 144 if (this == o) return true; 145 if (!(o instanceof DeleteMedicalResourcesRequest that)) return false; 146 return mDataSourceIds.equals(that.mDataSourceIds) 147 && mMedicalResourceTypes.equals(that.mMedicalResourceTypes); 148 } 149 150 @Override hashCode()151 public int hashCode() { 152 return hash(mDataSourceIds, mMedicalResourceTypes); 153 } 154 155 @Override toString()156 public String toString() { 157 StringBuilder sb = new StringBuilder(); 158 sb.append(this.getClass().getSimpleName()).append("{"); 159 sb.append("dataSourceIds=").append(mDataSourceIds); 160 sb.append(",medicalResourceTypes=").append(mMedicalResourceTypes); 161 sb.append("}"); 162 return sb.toString(); 163 } 164 165 /** Builder class for {@link DeleteMedicalResourcesRequest}. */ 166 public static final class Builder { 167 private final Set<String> mDataSourceIds = new HashSet<>(); 168 @MedicalResourceType private final Set<Integer> mMedicalResourceTypes = new HashSet<>(); 169 170 /** Constructs a new {@link DeleteMedicalResourcesRequest.Builder} with no filters. */ Builder()171 public Builder() {} 172 173 /** Constructs a clone of the other {@link DeleteMedicalResourcesRequest.Builder}. */ Builder(@onNull Builder other)174 public Builder(@NonNull Builder other) { 175 mDataSourceIds.addAll(other.mDataSourceIds); 176 mMedicalResourceTypes.addAll(other.mMedicalResourceTypes); 177 } 178 179 /** Constructs a clone of the other {@link DeleteMedicalResourcesRequest} instance. */ Builder(@onNull DeleteMedicalResourcesRequest other)180 public Builder(@NonNull DeleteMedicalResourcesRequest other) { 181 mDataSourceIds.addAll(other.getDataSourceIds()); 182 mMedicalResourceTypes.addAll(other.getMedicalResourceTypes()); 183 } 184 185 /** 186 * Adds the data source ID to request to delete. This should be an ID of the existing {@link 187 * MedicalDataSource}. 188 * 189 * <p>If the set of data source IDs is empty, it means resources from any data source should 190 * be deleted. 191 * 192 * @throws IllegalArgumentException if the provided {@code dataSourceId} is not a valid ID. 193 */ 194 @NonNull addDataSourceId(@onNull String dataSourceId)195 public Builder addDataSourceId(@NonNull String dataSourceId) { 196 mDataSourceIds.add(requireNonNull(dataSourceId)); 197 validateMedicalDataSourceIds(Set.of(dataSourceId)); 198 return this; 199 } 200 201 /** 202 * Adds the medical resource type to request to delete. 203 * 204 * @throws IllegalArgumentException if the provided {@code resourceType} is not supported. 205 */ 206 @NonNull addMedicalResourceType(@edicalResourceType int resourceType)207 public Builder addMedicalResourceType(@MedicalResourceType int resourceType) { 208 validateMedicalResourceType(resourceType); 209 mMedicalResourceTypes.add(resourceType); 210 return this; 211 } 212 213 /** Clears all data source IDs. */ 214 @NonNull clearDataSourceIds()215 public Builder clearDataSourceIds() { 216 mDataSourceIds.clear(); 217 return this; 218 } 219 220 /** Clears all medical resource types. */ 221 @NonNull clearMedicalResourceTypes()222 public Builder clearMedicalResourceTypes() { 223 mMedicalResourceTypes.clear(); 224 return this; 225 } 226 227 /** 228 * Returns a new instance of {@link DeleteMedicalResourcesRequest} with the specified 229 * parameters. 230 * 231 * @throws IllegalArgumentException if no data source IDs or medical resource types have 232 * been added. 233 */ 234 @NonNull build()235 public DeleteMedicalResourcesRequest build() { 236 return new DeleteMedicalResourcesRequest(mDataSourceIds, mMedicalResourceTypes); 237 } 238 } 239 } 240