• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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