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