1 /* 2 * Copyright (C) 2023 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.changelog; 18 19 import android.annotation.NonNull; 20 import android.health.connect.HealthConnectManager; 21 import android.health.connect.datatypes.DataOrigin; 22 import android.health.connect.datatypes.Record; 23 import android.health.connect.datatypes.RecordTypeIdentifier; 24 import android.health.connect.internal.datatypes.utils.HealthConnectMappings; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.util.ArraySet; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.List; 32 import java.util.Objects; 33 import java.util.Set; 34 import java.util.stream.Collectors; 35 36 /** 37 * A class to request changelog token using {@link HealthConnectManager#getChangeLogToken} 38 * 39 * @see HealthConnectManager#getChangeLogToken 40 */ 41 public final class ChangeLogTokenRequest implements Parcelable { 42 private final Set<DataOrigin> mDataOriginFilters; 43 private final Set<Class<? extends Record>> mRecordTypes; 44 45 /** 46 * @param dataOriginFilters list of package names to filter the data 47 * @param recordTypes list of records for which change log is required 48 */ ChangeLogTokenRequest( @onNull Set<DataOrigin> dataOriginFilters, @NonNull Set<Class<? extends Record>> recordTypes)49 private ChangeLogTokenRequest( 50 @NonNull Set<DataOrigin> dataOriginFilters, 51 @NonNull Set<Class<? extends Record>> recordTypes) { 52 Objects.requireNonNull(recordTypes); 53 verifyRecordTypes(recordTypes); 54 Objects.requireNonNull(dataOriginFilters); 55 56 mDataOriginFilters = dataOriginFilters; 57 mRecordTypes = recordTypes; 58 } 59 verifyRecordTypes(Set<Class<? extends Record>> recordTypes)60 private void verifyRecordTypes(Set<Class<? extends Record>> recordTypes) { 61 if (recordTypes.isEmpty()) { 62 throw new IllegalArgumentException("Requested record types must not be empty"); 63 } 64 Set<String> invalidRecordTypes = 65 recordTypes.stream() 66 .filter( 67 recordType -> 68 !HealthConnectMappings.getInstance() 69 .hasRecordType(recordType)) 70 .map(Class::getName) 71 .collect(Collectors.toSet()); 72 if (!invalidRecordTypes.isEmpty()) { 73 throw new IllegalArgumentException( 74 "Requested record types must not contain any of " + invalidRecordTypes); 75 } 76 } 77 ChangeLogTokenRequest(@onNull Parcel in)78 private ChangeLogTokenRequest(@NonNull Parcel in) { 79 HealthConnectMappings healthConnectMappings = HealthConnectMappings.getInstance(); 80 Set<Class<? extends Record>> recordTypes = new ArraySet<>(); 81 for (@RecordTypeIdentifier.RecordType int recordType : in.createIntArray()) { 82 recordTypes.add( 83 healthConnectMappings.getRecordIdToExternalRecordClassMap().get(recordType)); 84 } 85 mRecordTypes = recordTypes; 86 Set<DataOrigin> dataOrigin = new ArraySet<>(); 87 for (String packageName : in.createStringArrayList()) { 88 dataOrigin.add(new DataOrigin.Builder().setPackageName(packageName).build()); 89 } 90 mDataOriginFilters = dataOrigin; 91 } 92 93 @NonNull 94 public static final Creator<ChangeLogTokenRequest> CREATOR = 95 new Creator<ChangeLogTokenRequest>() { 96 @Override 97 public ChangeLogTokenRequest createFromParcel(@NonNull Parcel in) { 98 return new ChangeLogTokenRequest(in); 99 } 100 101 @Override 102 public ChangeLogTokenRequest[] newArray(int size) { 103 return new ChangeLogTokenRequest[size]; 104 } 105 }; 106 107 /** Returns list of package names corresponding to which the logs are required */ 108 @NonNull getDataOriginFilters()109 public Set<DataOrigin> getDataOriginFilters() { 110 return mDataOriginFilters; 111 } 112 113 /** Returns list of record classes for which the logs are to be fetched */ 114 @NonNull getRecordTypes()115 public Set<Class<? extends Record>> getRecordTypes() { 116 return mRecordTypes; 117 } 118 119 /** 120 * Returns List of Record types for which logs are to be fetched 121 * 122 * @hide 123 */ 124 @NonNull getRecordTypesArray()125 public int[] getRecordTypesArray() { 126 return getRecordTypesAsInteger(); 127 } 128 129 /** 130 * Returns List of Record types for which logs are to be fetched 131 * 132 * @hide 133 */ 134 @NonNull getRecordTypesList()135 public List<Integer> getRecordTypesList() { 136 return Arrays.stream(getRecordTypesAsInteger()).boxed().collect(Collectors.toList()); 137 } 138 139 /** 140 * Returns list of package names corresponding to which the logs are required 141 * 142 * @hide 143 */ 144 @NonNull getPackageNamesToFilter()145 public List<String> getPackageNamesToFilter() { 146 return getPackageNames(); 147 } 148 149 @Override describeContents()150 public int describeContents() { 151 return 0; 152 } 153 154 @Override writeToParcel(@onNull Parcel dest, int flags)155 public void writeToParcel(@NonNull Parcel dest, int flags) { 156 dest.writeIntArray(getRecordTypesAsInteger()); 157 dest.writeStringList(getPackageNames()); 158 } 159 160 @NonNull getRecordTypesAsInteger()161 private int[] getRecordTypesAsInteger() { 162 int[] recordTypes = new int[mRecordTypes.size()]; 163 int index = 0; 164 for (Class<? extends Record> recordClass : mRecordTypes) { 165 recordTypes[index++] = HealthConnectMappings.getInstance().getRecordType(recordClass); 166 } 167 return recordTypes; 168 } 169 170 @NonNull getPackageNames()171 private List<String> getPackageNames() { 172 List<String> packageNamesToFilter = new ArrayList<>(mDataOriginFilters.size()); 173 mDataOriginFilters.forEach( 174 (dataOrigin) -> packageNamesToFilter.add(dataOrigin.getPackageName())); 175 return packageNamesToFilter; 176 } 177 178 /** Builder for {@link ChangeLogTokenRequest} */ 179 public static final class Builder { 180 private final Set<Class<? extends Record>> mRecordTypes = new ArraySet<>(); 181 private final Set<DataOrigin> mDataOriginFilters = new ArraySet<>(); 182 183 /** 184 * @param recordType type of record for which change log is required. At least one record 185 * type must be set. 186 */ 187 @NonNull addRecordType(@onNull Class<? extends Record> recordType)188 public Builder addRecordType(@NonNull Class<? extends Record> recordType) { 189 Objects.requireNonNull(recordType); 190 191 mRecordTypes.add(recordType); 192 return this; 193 } 194 195 /** 196 * @param dataOriginFilter list of package names on which to filter the data. 197 * <p>If not set logs from all the sources will be returned 198 */ 199 @NonNull addDataOriginFilter(@onNull DataOrigin dataOriginFilter)200 public Builder addDataOriginFilter(@NonNull DataOrigin dataOriginFilter) { 201 Objects.requireNonNull(dataOriginFilter); 202 203 mDataOriginFilters.add(dataOriginFilter); 204 return this; 205 } 206 207 /** 208 * Returns Object of {@link ChangeLogTokenRequest} 209 * 210 * @throws IllegalArgumentException if record types are empty 211 */ 212 @NonNull build()213 public ChangeLogTokenRequest build() { 214 return new ChangeLogTokenRequest(mDataOriginFilters, mRecordTypes); 215 } 216 } 217 } 218