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.accesslog; 18 19 import static android.health.connect.datatypes.MedicalResource.validateMedicalResourceType; 20 import static android.health.connect.datatypes.validation.ValidationUtils.validateIntDefValue; 21 22 import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD; 23 import static com.android.healthfitness.flags.Flags.personalHealthRecord; 24 25 import static java.util.Objects.requireNonNull; 26 27 import android.annotation.FlaggedApi; 28 import android.annotation.IntDef; 29 import android.annotation.NonNull; 30 import android.annotation.SystemApi; 31 import android.health.connect.Constants; 32 import android.health.connect.datatypes.MedicalDataSource; 33 import android.health.connect.datatypes.MedicalResource.MedicalResourceType; 34 import android.health.connect.datatypes.Record; 35 import android.health.connect.datatypes.RecordTypeIdentifier; 36 import android.health.connect.internal.datatypes.utils.HealthConnectMappings; 37 import android.os.Parcel; 38 import android.os.Parcelable; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.time.Instant; 43 import java.util.ArrayList; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Set; 47 48 /** 49 * A class to represent access log which is logged whenever a package requests a read on a record 50 * type 51 * 52 * @hide 53 */ 54 @SystemApi 55 public final class AccessLog implements Parcelable { 56 @NonNull private final String mPackageName; 57 @NonNull private final Instant mAccessTime; 58 @OperationType.OperationTypes private final int mOperationType; 59 @NonNull private final List<Class<? extends Record>> mRecordTypesList = new ArrayList<>(); 60 @NonNull @MedicalResourceType private Set<Integer> mMedicalResourceTypes = new HashSet<>(); 61 private boolean mIsMedicalDataSourceAccessed = false; 62 63 /** 64 * Creates an access logs object that can be used to get access log request for {@code 65 * packageName} 66 * 67 * @param packageName name of the package that requested an access 68 * @param recordTypes List of Record class type the was accessed 69 * @param accessTimeInMillis time when the access was requested 70 * @param operationType Type of access 71 * @hide 72 */ AccessLog( @onNull String packageName, @NonNull @RecordTypeIdentifier.RecordType List<Integer> recordTypes, long accessTimeInMillis, @OperationType.OperationTypes int operationType)73 public AccessLog( 74 @NonNull String packageName, 75 @NonNull @RecordTypeIdentifier.RecordType List<Integer> recordTypes, 76 long accessTimeInMillis, 77 @OperationType.OperationTypes int operationType) { 78 requireNonNull(packageName); 79 requireNonNull(recordTypes); 80 81 mPackageName = packageName; 82 HealthConnectMappings healthConnectMappings = HealthConnectMappings.getInstance(); 83 for (@RecordTypeIdentifier.RecordType int recordType : recordTypes) { 84 mRecordTypesList.add( 85 healthConnectMappings.getRecordIdToExternalRecordClassMap().get(recordType)); 86 } 87 mAccessTime = Instant.ofEpochMilli(accessTimeInMillis); 88 mOperationType = operationType; 89 } 90 91 /** 92 * Creates an access logs object that can be used to get access log request for {@code 93 * packageName} 94 * 95 * @param packageName name of the package that requested an access 96 * @param accessTimeInMillis time when the access was requested 97 * @param operationType Type of access 98 * @param medicalResourceTypes Set of {@link MedicalResourceType}s that was accessed by the app 99 * @param isMedicalDataSourceAccessed Whether or not any {@link MedicalDataSource}s was accessed 100 * @hide 101 */ AccessLog( @onNull String packageName, long accessTimeInMillis, @OperationType.OperationTypes int operationType, @NonNull @MedicalResourceType Set<Integer> medicalResourceTypes, boolean isMedicalDataSourceAccessed)102 public AccessLog( 103 @NonNull String packageName, 104 long accessTimeInMillis, 105 @OperationType.OperationTypes int operationType, 106 @NonNull @MedicalResourceType Set<Integer> medicalResourceTypes, 107 boolean isMedicalDataSourceAccessed) { 108 if (!personalHealthRecord()) { 109 throw new UnsupportedOperationException( 110 "Constructing AccessLog for medical data is not supported"); 111 } 112 requireNonNull(packageName); 113 OperationType.validateOperationType(operationType); 114 requireNonNull(medicalResourceTypes); 115 for (@MedicalResourceType int medicalResourceType : medicalResourceTypes) { 116 validateMedicalResourceType(medicalResourceType); 117 } 118 mPackageName = packageName; 119 mAccessTime = Instant.ofEpochMilli(accessTimeInMillis); 120 mOperationType = operationType; 121 mMedicalResourceTypes = medicalResourceTypes; 122 mIsMedicalDataSourceAccessed = isMedicalDataSourceAccessed; 123 } 124 AccessLog(Parcel in)125 private AccessLog(Parcel in) { 126 HealthConnectMappings healthConnectMappings = HealthConnectMappings.getInstance(); 127 128 int[] recordTypes = requireNonNull(in.createIntArray()); 129 for (@RecordTypeIdentifier.RecordType int recordType : recordTypes) { 130 mRecordTypesList.add( 131 healthConnectMappings.getRecordIdToExternalRecordClassMap().get(recordType)); 132 } 133 mPackageName = requireNonNull(in.readString()); 134 mAccessTime = Instant.ofEpochMilli(in.readLong()); 135 mOperationType = in.readInt(); 136 if (personalHealthRecord()) { 137 int[] medicalResourceTypes = requireNonNull(in.createIntArray()); 138 for (@MedicalResourceType int medicalResourceType : medicalResourceTypes) { 139 mMedicalResourceTypes.add(medicalResourceType); 140 } 141 mIsMedicalDataSourceAccessed = in.readBoolean(); 142 } 143 } 144 145 @NonNull 146 public static final Creator<AccessLog> CREATOR = 147 new Creator<>() { 148 @Override 149 public AccessLog createFromParcel(Parcel in) { 150 return new AccessLog(in); 151 } 152 153 @Override 154 public AccessLog[] newArray(int size) { 155 return new AccessLog[size]; 156 } 157 }; 158 159 /** Returns List of Record types that was accessed by the app */ 160 @NonNull getRecordTypes()161 public List<Class<? extends Record>> getRecordTypes() { 162 return mRecordTypesList; 163 } 164 165 /** Returns package name of app that accessed the records */ 166 @NonNull getPackageName()167 public String getPackageName() { 168 return mPackageName; 169 } 170 171 /** Returns the instant at which the app accessed the record */ 172 @NonNull getAccessTime()173 public Instant getAccessTime() { 174 return mAccessTime; 175 } 176 177 /** Returns the type of operation performed by the app */ 178 @OperationType.OperationTypes getOperationType()179 public int getOperationType() { 180 return mOperationType; 181 } 182 183 /** Returns Set of {@link MedicalResourceType}s that was accessed by the app */ 184 @NonNull 185 @MedicalResourceType 186 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD) getMedicalResourceTypes()187 public Set<Integer> getMedicalResourceTypes() { 188 return mMedicalResourceTypes; 189 } 190 191 /** Returns whether or not any {@link MedicalDataSource}s was accessed by the app */ 192 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD) isMedicalDataSourceAccessed()193 public boolean isMedicalDataSourceAccessed() { 194 return mIsMedicalDataSourceAccessed; 195 } 196 197 /** Identifier for Operation type. */ 198 public static final class OperationType { 199 200 /** Identifier for read operation done on user health data. */ 201 public static final int OPERATION_TYPE_READ = Constants.READ; 202 203 /** Identifier for update or insert operation done on user health data. */ 204 public static final int OPERATION_TYPE_UPSERT = Constants.UPSERT; 205 206 /** Identifier for delete operation done on user health data. */ 207 public static final int OPERATION_TYPE_DELETE = Constants.DELETE; 208 209 /** @hide */ 210 @IntDef({OPERATION_TYPE_UPSERT, OPERATION_TYPE_DELETE, OPERATION_TYPE_READ}) 211 @Retention(RetentionPolicy.SOURCE) 212 public @interface OperationTypes {} 213 214 /** 215 * Validates the provided {@code operationType} is in the valid set. 216 * 217 * <p>Throws {@link IllegalArgumentException} if not. 218 */ validateOperationType(@perationTypes int operationType)219 private static void validateOperationType(@OperationTypes int operationType) { 220 validateIntDefValue( 221 operationType, 222 Set.of(OPERATION_TYPE_UPSERT, OPERATION_TYPE_DELETE, OPERATION_TYPE_READ), 223 OperationTypes.class.getSimpleName()); 224 } 225 OperationType()226 private OperationType() {} 227 } 228 229 @Override describeContents()230 public int describeContents() { 231 return 0; 232 } 233 234 /** 235 * Flatten this object in to a Parcel. 236 * 237 * @param dest The Parcel in which the object should be written. 238 * @param flags Additional flags about how the object should be written. May be 0 or {@link 239 * #PARCELABLE_WRITE_RETURN_VALUE}. 240 */ 241 @Override writeToParcel(@onNull Parcel dest, int flags)242 public void writeToParcel(@NonNull Parcel dest, int flags) { 243 int recordTypeCount = mRecordTypesList.size(); 244 HealthConnectMappings healthConnectMappings = HealthConnectMappings.getInstance(); 245 @RecordTypeIdentifier.RecordType int[] recordTypes = new int[recordTypeCount]; 246 for (int i = 0; i < recordTypeCount; i++) { 247 recordTypes[i] = healthConnectMappings.getRecordType(mRecordTypesList.get(i)); 248 } 249 dest.writeIntArray(recordTypes); 250 dest.writeString(mPackageName); 251 dest.writeLong(mAccessTime.toEpochMilli()); 252 dest.writeInt(mOperationType); 253 if (personalHealthRecord()) { 254 dest.writeIntArray( 255 mMedicalResourceTypes.stream().mapToInt(Integer::intValue).toArray()); 256 dest.writeBoolean(mIsMedicalDataSourceAccessed); 257 } 258 } 259 } 260