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 android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.ContentValues; 23 import android.database.Cursor; 24 import android.health.connect.datatypes.RecordTypeIdentifier; 25 import android.health.connect.internal.datatypes.RecordInternal; 26 import android.util.ArrayMap; 27 import android.util.Pair; 28 29 import com.android.server.healthconnect.storage.datatypehelpers.RecordHelper; 30 import com.android.server.healthconnect.storage.utils.StorageUtils; 31 import com.android.server.healthconnect.storage.utils.WhereClauses; 32 33 import java.lang.annotation.ElementType; 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 import java.lang.annotation.Target; 37 import java.util.Collections; 38 import java.util.List; 39 import java.util.Objects; 40 41 /** @hide */ 42 public class UpsertTableRequest { 43 public static final int INVALID_ROW_ID = -1; 44 45 public static final int TYPE_STRING = 0; 46 public static final int TYPE_BLOB = 1; 47 private final String mTable; 48 private ContentValues mContentValues; 49 private final List<Pair<String, Integer>> mUniqueColumns; 50 private List<UpsertTableRequest> mChildTableRequests = Collections.emptyList(); 51 private String mParentCol; 52 private long mRowId = INVALID_ROW_ID; 53 private WhereClauses mWhereClausesForUpdate; 54 private IRequiresUpdate mRequiresUpdate = new IRequiresUpdate() {}; 55 private Integer mRecordType; 56 private RecordInternal<?> mRecordInternal; 57 private RecordHelper<?> mRecordHelper; 58 59 private ArrayMap<String, Boolean> mExtraWritePermissionsStateMapping; 60 UpsertTableRequest(@onNull String table, @NonNull ContentValues contentValues)61 public UpsertTableRequest(@NonNull String table, @NonNull ContentValues contentValues) { 62 this(table, contentValues, Collections.emptyList()); 63 } 64 UpsertTableRequest( @onNull String table, @NonNull ContentValues contentValues, @NonNull List<Pair<String, Integer>> uniqueColumns)65 public UpsertTableRequest( 66 @NonNull String table, 67 @NonNull ContentValues contentValues, 68 @NonNull List<Pair<String, Integer>> uniqueColumns) { 69 Objects.requireNonNull(table); 70 Objects.requireNonNull(contentValues); 71 Objects.requireNonNull(uniqueColumns); 72 73 mTable = table; 74 mContentValues = contentValues; 75 mUniqueColumns = uniqueColumns; 76 } 77 getUniqueColumnsCount()78 public int getUniqueColumnsCount() { 79 return mUniqueColumns.size(); 80 } 81 82 @NonNull withParentKey(long rowId)83 public UpsertTableRequest withParentKey(long rowId) { 84 mRowId = rowId; 85 return this; 86 } 87 88 /** 89 * Use this if you want to add row_id of the parent table to all the child entries in {@code 90 * parentCol} 91 */ 92 @NonNull setParentColumnForChildTables(@ullable String parentCol)93 public UpsertTableRequest setParentColumnForChildTables(@Nullable String parentCol) { 94 mParentCol = parentCol; 95 return this; 96 } 97 98 @NonNull setRequiresUpdateClause(@onNull IRequiresUpdate requiresUpdate)99 public UpsertTableRequest setRequiresUpdateClause(@NonNull IRequiresUpdate requiresUpdate) { 100 Objects.requireNonNull(requiresUpdate); 101 102 mRequiresUpdate = requiresUpdate; 103 return this; 104 } 105 106 @NonNull getTable()107 public String getTable() { 108 return mTable; 109 } 110 111 @NonNull getContentValues()112 public ContentValues getContentValues() { 113 // Set the parent column of the creator of this requested to do that 114 if (!Objects.isNull(mParentCol) && mRowId != INVALID_ROW_ID) { 115 mContentValues.put(mParentCol, mRowId); 116 } 117 118 return mContentValues; 119 } 120 121 @NonNull getChildTableRequests()122 public List<UpsertTableRequest> getChildTableRequests() { 123 return mChildTableRequests; 124 } 125 126 @NonNull setChildTableRequests( @onNull List<UpsertTableRequest> childTableRequests)127 public UpsertTableRequest setChildTableRequests( 128 @NonNull List<UpsertTableRequest> childTableRequests) { 129 Objects.requireNonNull(childTableRequests); 130 131 mChildTableRequests = childTableRequests; 132 return this; 133 } 134 135 @NonNull getUpdateWhereClauses()136 public WhereClauses getUpdateWhereClauses() { 137 if (mWhereClausesForUpdate == null) { 138 return getReadWhereClauses(); 139 } 140 141 return mWhereClausesForUpdate; 142 } 143 setUpdateWhereClauses(WhereClauses whereClauses)144 public UpsertTableRequest setUpdateWhereClauses(WhereClauses whereClauses) { 145 Objects.requireNonNull(whereClauses); 146 147 mWhereClausesForUpdate = whereClauses; 148 return this; 149 } 150 getReadRequest()151 public ReadTableRequest getReadRequest() { 152 return new ReadTableRequest(getTable()).setWhereClause(getReadWhereClauses()); 153 } 154 getReadRequestUsingUpdateClause()155 public ReadTableRequest getReadRequestUsingUpdateClause() { 156 return new ReadTableRequest(getTable()).setWhereClause(getUpdateWhereClauses()); 157 } 158 159 @NonNull getReadWhereClauses()160 private WhereClauses getReadWhereClauses() { 161 WhereClauses readWhereClause = new WhereClauses().setUseOr(true); 162 163 for (Pair<String, Integer> uniqueColumn : mUniqueColumns) { 164 switch (uniqueColumn.second) { 165 case TYPE_BLOB -> readWhereClause.addWhereEqualsClause( 166 uniqueColumn.first, StorageUtils.getHexString( 167 mContentValues.getAsByteArray(uniqueColumn.first))); 168 case TYPE_STRING -> readWhereClause.addWhereEqualsClause( 169 uniqueColumn.first, mContentValues.getAsString(uniqueColumn.first)); 170 default -> throw new UnsupportedOperationException( 171 "Unable to find type: " + uniqueColumn.second); 172 } 173 } 174 175 return readWhereClause; 176 } 177 requiresUpdate(Cursor cursor, UpsertTableRequest request)178 public boolean requiresUpdate(Cursor cursor, UpsertTableRequest request) { 179 return mRequiresUpdate.requiresUpdate(cursor, getContentValues(), request); 180 } 181 getRowIdColName()182 public String getRowIdColName() { 183 return RecordHelper.PRIMARY_COLUMN_NAME; 184 } 185 186 @RecordTypeIdentifier.RecordType getRecordType()187 public int getRecordType() { 188 Objects.requireNonNull(mRecordType); 189 return mRecordType; 190 } 191 setRecordType(@ecordTypeIdentifier.RecordType int recordIdentifier)192 public void setRecordType(@RecordTypeIdentifier.RecordType int recordIdentifier) { 193 mRecordType = recordIdentifier; 194 } 195 setHelper( RecordHelper<?> recordHelper)196 public <T extends RecordInternal<?>> UpsertTableRequest setHelper( 197 RecordHelper<?> recordHelper) { 198 mRecordHelper = recordHelper; 199 200 return this; 201 } 202 203 @NonNull getAllChildTablesToDelete()204 public List<String> getAllChildTablesToDelete() { 205 return mRecordHelper == null 206 ? Collections.emptyList() 207 : mRecordHelper.getChildTablesToDeleteOnRecordUpsert( 208 mExtraWritePermissionsStateMapping); 209 } 210 211 @NonNull getAllChildTables()212 public List<String> getAllChildTables() { 213 return mRecordHelper == null ? Collections.emptyList() : mRecordHelper.getAllChildTables(); 214 } 215 getRecordInternal()216 public RecordInternal<?> getRecordInternal() { 217 return mRecordInternal; 218 } 219 setRecordInternal(RecordInternal<?> recordInternal)220 public void setRecordInternal(RecordInternal<?> recordInternal) { 221 mRecordInternal = recordInternal; 222 } 223 setExtraWritePermissionsStateMapping( ArrayMap<String, Boolean> extraWritePermissionsToState)224 public <T extends RecordInternal<?>> UpsertTableRequest setExtraWritePermissionsStateMapping( 225 ArrayMap<String, Boolean> extraWritePermissionsToState) { 226 mExtraWritePermissionsStateMapping = extraWritePermissionsToState; 227 return this; 228 } 229 230 @Target(ElementType.TYPE_USE) 231 @Retention(RetentionPolicy.SOURCE) 232 @IntDef({TYPE_STRING, TYPE_BLOB}) 233 public @interface ColumnType {} 234 235 public interface IRequiresUpdate { requiresUpdate( Cursor cursor, ContentValues contentValues, UpsertTableRequest request)236 default boolean requiresUpdate( 237 Cursor cursor, ContentValues contentValues, UpsertTableRequest request) { 238 return true; 239 } 240 } 241 } 242