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.RecordMapper; 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(recordType -> !RecordMapper.getInstance().hasRecordType(recordType)) 67 .map(Class::getName) 68 .collect(Collectors.toSet()); 69 if (!invalidRecordTypes.isEmpty()) { 70 throw new IllegalArgumentException( 71 "Requested record types must not contain any of " + invalidRecordTypes); 72 } 73 } 74 ChangeLogTokenRequest(@onNull Parcel in)75 private ChangeLogTokenRequest(@NonNull Parcel in) { 76 RecordMapper recordMapper = RecordMapper.getInstance(); 77 Set<Class<? extends Record>> recordTypes = new ArraySet<>(); 78 for (@RecordTypeIdentifier.RecordType int recordType : in.createIntArray()) { 79 recordTypes.add(recordMapper.getRecordIdToExternalRecordClassMap().get(recordType)); 80 } 81 mRecordTypes = recordTypes; 82 Set<DataOrigin> dataOrigin = new ArraySet<>(); 83 for (String packageName : in.createStringArrayList()) { 84 dataOrigin.add(new DataOrigin.Builder().setPackageName(packageName).build()); 85 } 86 mDataOriginFilters = dataOrigin; 87 } 88 89 @NonNull 90 public static final Creator<ChangeLogTokenRequest> CREATOR = 91 new Creator<ChangeLogTokenRequest>() { 92 @Override 93 public ChangeLogTokenRequest createFromParcel(@NonNull Parcel in) { 94 return new ChangeLogTokenRequest(in); 95 } 96 97 @Override 98 public ChangeLogTokenRequest[] newArray(int size) { 99 return new ChangeLogTokenRequest[size]; 100 } 101 }; 102 103 /** Returns list of package names corresponding to which the logs are required */ 104 @NonNull getDataOriginFilters()105 public Set<DataOrigin> getDataOriginFilters() { 106 return mDataOriginFilters; 107 } 108 109 /** Returns list of record classes for which the logs are to be fetched */ 110 @NonNull getRecordTypes()111 public Set<Class<? extends Record>> getRecordTypes() { 112 return mRecordTypes; 113 } 114 115 /** 116 * Returns List of Record types for which logs are to be fetched 117 * 118 * @hide 119 */ 120 @NonNull getRecordTypesArray()121 public int[] getRecordTypesArray() { 122 return getRecordTypesAsInteger(); 123 } 124 125 /** 126 * Returns List of Record types for which logs are to be fetched 127 * 128 * @hide 129 */ 130 @NonNull getRecordTypesList()131 public List<Integer> getRecordTypesList() { 132 return Arrays.stream(getRecordTypesAsInteger()).boxed().collect(Collectors.toList()); 133 } 134 135 /** 136 * Returns list of package names corresponding to which the logs are required 137 * 138 * @hide 139 */ 140 @NonNull getPackageNamesToFilter()141 public List<String> getPackageNamesToFilter() { 142 return getPackageNames(); 143 } 144 145 @Override describeContents()146 public int describeContents() { 147 return 0; 148 } 149 150 @Override writeToParcel(@onNull Parcel dest, int flags)151 public void writeToParcel(@NonNull Parcel dest, int flags) { 152 dest.writeIntArray(getRecordTypesAsInteger()); 153 dest.writeStringList(getPackageNames()); 154 } 155 156 @NonNull getRecordTypesAsInteger()157 private int[] getRecordTypesAsInteger() { 158 int[] recordTypes = new int[mRecordTypes.size()]; 159 int index = 0; 160 for (Class<? extends Record> recordClass : mRecordTypes) { 161 recordTypes[index++] = RecordMapper.getInstance().getRecordType(recordClass); 162 } 163 return recordTypes; 164 } 165 166 @NonNull getPackageNames()167 private List<String> getPackageNames() { 168 List<String> packageNamesToFilter = new ArrayList<>(mDataOriginFilters.size()); 169 mDataOriginFilters.forEach( 170 (dataOrigin) -> packageNamesToFilter.add(dataOrigin.getPackageName())); 171 return packageNamesToFilter; 172 } 173 174 /** Builder for {@link ChangeLogTokenRequest} */ 175 public static final class Builder { 176 private final Set<Class<? extends Record>> mRecordTypes = new ArraySet<>(); 177 private final Set<DataOrigin> mDataOriginFilters = new ArraySet<>(); 178 179 /** 180 * @param recordType type of record for which change log is required. At least one record 181 * type must be set. 182 */ 183 @NonNull addRecordType(@onNull Class<? extends Record> recordType)184 public Builder addRecordType(@NonNull Class<? extends Record> recordType) { 185 Objects.requireNonNull(recordType); 186 187 mRecordTypes.add(recordType); 188 return this; 189 } 190 191 /** 192 * @param dataOriginFilter list of package names on which to filter the data. 193 * <p>If not set logs from all the sources will be returned 194 */ 195 @NonNull addDataOriginFilter(@onNull DataOrigin dataOriginFilter)196 public Builder addDataOriginFilter(@NonNull DataOrigin dataOriginFilter) { 197 Objects.requireNonNull(dataOriginFilter); 198 199 mDataOriginFilters.add(dataOriginFilter); 200 return this; 201 } 202 203 /** 204 * Returns Object of {@link ChangeLogTokenRequest} 205 * 206 * @throws IllegalArgumentException if record types are empty 207 */ 208 @NonNull build()209 public ChangeLogTokenRequest build() { 210 return new ChangeLogTokenRequest(mDataOriginFilters, mRecordTypes); 211 } 212 } 213 } 214