1 /*
2  * Copyright (C) 2022 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 com.android.server.healthconnect.storage.request;
18 
19 import static android.health.connect.Constants.DEFAULT_LONG;
20 
21 import android.health.connect.Constants;
22 import android.health.connect.RecordIdFilter;
23 import android.health.connect.aidl.DeleteUsingFiltersRequestParcel;
24 import android.health.connect.internal.datatypes.utils.RecordMapper;
25 import android.util.ArrayMap;
26 import android.util.ArraySet;
27 import android.util.Slog;
28 
29 import com.android.server.healthconnect.storage.datatypehelpers.AppInfoHelper;
30 import com.android.server.healthconnect.storage.datatypehelpers.RecordHelper;
31 import com.android.server.healthconnect.storage.utils.RecordHelperProvider;
32 import com.android.server.healthconnect.storage.utils.StorageUtils;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Objects;
38 import java.util.Set;
39 import java.util.UUID;
40 
41 /** @hide */
42 public final class DeleteTransactionRequest {
43     private static final String TAG = "HealthConnectDelete";
44     private final List<DeleteTableRequest> mDeleteTableRequests;
45     private final long mRequestingPackageNameId;
46     private boolean mHasHealthDataManagementPermission;
47 
48     @SuppressWarnings("NullAway.Init") // TODO(b/317029272): fix this suppression
DeleteTransactionRequest(String packageName, DeleteUsingFiltersRequestParcel request)49     public DeleteTransactionRequest(String packageName, DeleteUsingFiltersRequestParcel request) {
50         Objects.requireNonNull(packageName);
51         mDeleteTableRequests = new ArrayList<>(request.getRecordTypeFilters().size());
52         mRequestingPackageNameId = AppInfoHelper.getInstance().getAppInfoId(packageName);
53         if (request.usesIdFilters()) {
54             List<RecordIdFilter> recordIds =
55                     request.getRecordIdFiltersParcel().getRecordIdFilters();
56             Set<UUID> uuidSet = new ArraySet<>();
57             Map<RecordHelper<?>, List<UUID>> recordTypeToUuids = new ArrayMap<>();
58             for (RecordIdFilter recordId : recordIds) {
59                 RecordHelper<?> recordHelper =
60                         RecordHelperProvider.getRecordHelper(
61                                 RecordMapper.getInstance().getRecordType(recordId.getRecordType()));
62                 UUID uuid = StorageUtils.getUUIDFor(recordId, packageName);
63                 if (uuidSet.contains(uuid)) {
64                     // id has been already been processed;
65                     continue;
66                 }
67                 recordTypeToUuids.putIfAbsent(recordHelper, new ArrayList<>());
68                 Objects.requireNonNull(recordTypeToUuids.get(recordHelper)).add(uuid);
69                 uuidSet.add(uuid);
70             }
71 
72             recordTypeToUuids.forEach(
73                     (recordHelper, uuids) ->
74                             mDeleteTableRequests.add(recordHelper.getDeleteTableRequest(uuids)));
75 
76             // We currently only support either using filters or ids, so if we are deleting using
77             // ids no need to proceed further.
78             return;
79         }
80 
81         // No ids are present, so we are good to go to use filters to process our request
82         List<Integer> recordTypeFilters = request.getRecordTypeFilters();
83         if (recordTypeFilters == null || recordTypeFilters.isEmpty()) {
84             recordTypeFilters =
85                     new ArrayList<>(
86                             RecordMapper.getInstance()
87                                     .getRecordIdToExternalRecordClassMap()
88                                     .keySet());
89         }
90 
91         recordTypeFilters.forEach(
92                 (recordType) -> {
93                     RecordHelper<?> recordHelper = RecordHelperProvider.getRecordHelper(recordType);
94                     Objects.requireNonNull(recordHelper);
95 
96                     mDeleteTableRequests.add(
97                             recordHelper.getDeleteTableRequest(
98                                     request.getPackageNameFilters(),
99                                     request.getStartTime(),
100                                     request.getEndTime(),
101                                     request.isLocalTimeFilter()));
102                 });
103     }
104 
105     // Used for auto delete only
DeleteTransactionRequest(List<DeleteTableRequest> deleteTableRequests)106     public DeleteTransactionRequest(List<DeleteTableRequest> deleteTableRequests) {
107         mDeleteTableRequests = List.copyOf(deleteTableRequests);
108         mHasHealthDataManagementPermission = true;
109         mRequestingPackageNameId = DEFAULT_LONG;
110     }
111 
getDeleteTableRequests()112     public List<DeleteTableRequest> getDeleteTableRequests() {
113         if (Constants.DEBUG) {
114             Slog.d(TAG, "num of delete requests: " + mDeleteTableRequests.size());
115         }
116         return mDeleteTableRequests;
117     }
118 
enforcePackageCheck(UUID uuid, long appInfoId)119     public void enforcePackageCheck(UUID uuid, long appInfoId) {
120         if (mHasHealthDataManagementPermission) {
121             // Skip this check if the caller has data management permission
122             return;
123         }
124 
125         if (mRequestingPackageNameId != appInfoId) {
126             throw new IllegalArgumentException(
127                     mRequestingPackageNameId + " is not the owner for " + uuid);
128         }
129     }
130 
setHasManageHealthDataPermission( boolean hasHealthDataManagementPermission)131     public DeleteTransactionRequest setHasManageHealthDataPermission(
132             boolean hasHealthDataManagementPermission) {
133         mHasHealthDataManagementPermission = hasHealthDataManagementPermission;
134 
135         return this;
136     }
137 }
138