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.DELETE; 20 21 import android.annotation.NonNull; 22 import android.health.connect.Constants; 23 import android.health.connect.RecordIdFilter; 24 import android.health.connect.aidl.DeleteUsingFiltersRequestParcel; 25 import android.health.connect.datatypes.RecordTypeIdentifier; 26 import android.health.connect.internal.datatypes.utils.RecordMapper; 27 import android.util.ArrayMap; 28 import android.util.ArraySet; 29 import android.util.Slog; 30 31 import com.android.server.healthconnect.storage.datatypehelpers.AppInfoHelper; 32 import com.android.server.healthconnect.storage.datatypehelpers.ChangeLogsHelper; 33 import com.android.server.healthconnect.storage.datatypehelpers.RecordHelper; 34 import com.android.server.healthconnect.storage.utils.RecordHelperProvider; 35 import com.android.server.healthconnect.storage.utils.StorageUtils; 36 37 import java.time.Instant; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 import java.util.Set; 44 import java.util.UUID; 45 46 /** @hide */ 47 public final class DeleteTransactionRequest { 48 private static final String TAG = "HealthConnectDelete"; 49 private final List<DeleteTableRequest> mDeleteTableRequests; 50 private final long mRequestingPackageNameId; 51 private ChangeLogsHelper.ChangeLogs mChangeLogs; 52 private boolean mHasHealthDataManagementPermission; 53 DeleteTransactionRequest(String packageName, DeleteUsingFiltersRequestParcel request)54 public DeleteTransactionRequest(String packageName, DeleteUsingFiltersRequestParcel request) { 55 Objects.requireNonNull(packageName); 56 mDeleteTableRequests = new ArrayList<>(request.getRecordTypeFilters().size()); 57 mRequestingPackageNameId = AppInfoHelper.getInstance().getAppInfoId(packageName); 58 if (request.usesIdFilters()) { 59 // We don't keep change logs for bulk deletes 60 mChangeLogs = 61 new ChangeLogsHelper.ChangeLogs( 62 DELETE, packageName, Instant.now().toEpochMilli()); 63 List<RecordIdFilter> recordIds = 64 request.getRecordIdFiltersParcel().getRecordIdFilters(); 65 Set<UUID> uuidSet = new ArraySet<>(); 66 Map<RecordHelper<?>, List<UUID>> recordTypeToUuids = new ArrayMap<>(); 67 for (RecordIdFilter recordId : recordIds) { 68 RecordHelper<?> recordHelper = 69 RecordHelperProvider.getInstance() 70 .getRecordHelper( 71 RecordMapper.getInstance() 72 .getRecordType(recordId.getRecordType())); 73 UUID uuid = StorageUtils.getUUIDFor(recordId, packageName); 74 if (uuidSet.contains(uuid)) { 75 // id has been already been processed; 76 continue; 77 } 78 recordTypeToUuids.putIfAbsent(recordHelper, new ArrayList<>()); 79 Objects.requireNonNull(recordTypeToUuids.get(recordHelper)).add(uuid); 80 uuidSet.add(uuid); 81 } 82 83 recordTypeToUuids.forEach( 84 (recordHelper, uuids) -> 85 mDeleteTableRequests.add(recordHelper.getDeleteTableRequest(uuids))); 86 87 // We currently only support either using filters or ids, so if we are deleting using 88 // ids no need to proceed further. 89 return; 90 } 91 92 // No ids are present, so we are good to go to use filters to process our request 93 List<Integer> recordTypeFilters = request.getRecordTypeFilters(); 94 if (recordTypeFilters == null || recordTypeFilters.isEmpty()) { 95 recordTypeFilters = 96 new ArrayList<>( 97 RecordMapper.getInstance() 98 .getRecordIdToExternalRecordClassMap() 99 .keySet()); 100 } 101 102 recordTypeFilters.forEach( 103 (recordType) -> { 104 RecordHelper<?> recordHelper = 105 RecordHelperProvider.getInstance().getRecordHelper(recordType); 106 Objects.requireNonNull(recordHelper); 107 108 mDeleteTableRequests.add( 109 recordHelper.getDeleteTableRequest( 110 request.getPackageNameFilters(), 111 request.getStartTime(), 112 request.getEndTime(), 113 request.isLocalTimeFilter())); 114 }); 115 } 116 getDeleteTableRequests()117 public List<DeleteTableRequest> getDeleteTableRequests() { 118 if (Constants.DEBUG) { 119 Slog.d(TAG, "num of delete requests: " + mDeleteTableRequests.size()); 120 } 121 return mDeleteTableRequests; 122 } 123 124 /** 125 * Function to add an uuid corresponding to given pair of @recordType and @appId to 126 * recordTypeAndAppIdToUUIDMap of changeLogs 127 */ onRecordFetched( @ecordTypeIdentifier.RecordType int recordType, long appId, UUID uuid)128 public void onRecordFetched( 129 @RecordTypeIdentifier.RecordType int recordType, long appId, UUID uuid) { 130 if (mChangeLogs == null) { 131 return; 132 } 133 mChangeLogs.addUUID(recordType, appId, uuid); 134 } 135 136 @NonNull getChangeLogUpsertRequests()137 public List<UpsertTableRequest> getChangeLogUpsertRequests() { 138 if (mChangeLogs == null) { 139 return Collections.emptyList(); 140 } 141 142 return mChangeLogs.getUpsertTableRequests(); 143 } 144 enforcePackageCheck(UUID uuid, long appInfoId)145 public void enforcePackageCheck(UUID uuid, long appInfoId) { 146 if (mHasHealthDataManagementPermission) { 147 // Skip this check if the caller has data management permission 148 return; 149 } 150 151 if (mRequestingPackageNameId != appInfoId) { 152 throw new IllegalArgumentException( 153 mRequestingPackageNameId + " is not the owner for " + uuid); 154 } 155 } 156 setHasManageHealthDataPermission( boolean hasHealthDataManagementPermission)157 public DeleteTransactionRequest setHasManageHealthDataPermission( 158 boolean hasHealthDataManagementPermission) { 159 mHasHealthDataManagementPermission = hasHealthDataManagementPermission; 160 161 return this; 162 } 163 } 164