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.adservices.data.customaudience; 18 19 import android.adservices.common.AdTechIdentifier; 20 import android.adservices.customaudience.PartialCustomAudience; 21 import android.content.pm.PackageManager; 22 import android.net.Uri; 23 import android.util.Pair; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 import androidx.room.ColumnInfo; 28 import androidx.room.Dao; 29 import androidx.room.Delete; 30 import androidx.room.Insert; 31 import androidx.room.OnConflictStrategy; 32 import androidx.room.Query; 33 import androidx.room.Transaction; 34 35 import com.android.adservices.LoggerFactory; 36 import com.android.adservices.data.common.CleanupUtils; 37 import com.android.adservices.data.common.DecisionLogic; 38 import com.android.adservices.data.enrollment.EnrollmentDao; 39 import com.android.adservices.service.Flags; 40 import com.android.adservices.service.adselection.JsVersionHelper; 41 import com.android.adservices.service.customaudience.CustomAudienceUpdatableData; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 import com.google.common.collect.ImmutableMap; 45 46 import java.time.Instant; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.List; 50 import java.util.Objects; 51 import java.util.Set; 52 import java.util.stream.Collectors; 53 54 /** 55 * DAO abstract class used to access Custom Audience persistent storage. 56 * 57 * <p>Annotations will generate Room-based SQLite Dao impl. 58 */ 59 @Dao 60 public abstract class CustomAudienceDao { 61 private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); 62 /** 63 * Add user to a new custom audience. As designed, will override existing one. 64 * 65 * <p>This method is not meant to be used on its own, since custom audiences must be persisted 66 * alongside matching background fetch data. Use {@link 67 * #insertOrOverwriteCustomAudience(DBCustomAudience, Uri)} instead. 68 */ 69 @Insert(onConflict = OnConflictStrategy.REPLACE) persistCustomAudience(@onNull DBCustomAudience customAudience)70 protected abstract void persistCustomAudience(@NonNull DBCustomAudience customAudience); 71 72 /** 73 * Adds or updates background fetch data for a custom audience. 74 * 75 * <p>This method does not update the corresponding custom audience. Use {@link 76 * #updateCustomAudienceAndBackgroundFetchData(DBCustomAudienceBackgroundFetchData, 77 * CustomAudienceUpdatableData)} to do so safely. 78 */ 79 @Insert(onConflict = OnConflictStrategy.REPLACE) persistCustomAudienceBackgroundFetchData( @onNull DBCustomAudienceBackgroundFetchData fetchData)80 public abstract void persistCustomAudienceBackgroundFetchData( 81 @NonNull DBCustomAudienceBackgroundFetchData fetchData); 82 83 /** 84 * Adds a new {@link DBCustomAudienceQuarantine} entry to the {@code custom_audience_quarantine} 85 * table. 86 * 87 * <p>This method is not meant to be used on its own, since it doesn't take into account the 88 * maximum size of {@code custom_audience_quarantine}. Use {@link 89 * #safelyInsertCustomAudienceQuarantine} instead. 90 */ 91 @Insert(onConflict = OnConflictStrategy.REPLACE) persistCustomAudienceQuarantineData( DBCustomAudienceQuarantine dbCustomAudienceQuarantine)92 abstract void persistCustomAudienceQuarantineData( 93 DBCustomAudienceQuarantine dbCustomAudienceQuarantine); 94 95 /** 96 * Adds or updates a given custom audience and background fetch data in a single transaction. 97 * 98 * <p>This transaction is separate in order to minimize the critical region while locking the 99 * database. It is not meant to be exposed or used by itself; use {@link 100 * #insertOrOverwriteCustomAudience(DBCustomAudience, Uri)} instead. 101 */ 102 @Transaction insertOrOverwriteCustomAudienceAndBackgroundFetchData( @onNull DBCustomAudience customAudience, @NonNull DBCustomAudienceBackgroundFetchData fetchData)103 protected void insertOrOverwriteCustomAudienceAndBackgroundFetchData( 104 @NonNull DBCustomAudience customAudience, 105 @NonNull DBCustomAudienceBackgroundFetchData fetchData) { 106 persistCustomAudience(customAudience); 107 persistCustomAudienceBackgroundFetchData(fetchData); 108 } 109 110 /** 111 * Adds the user to the given custom audience. 112 * 113 * <p>If a custom audience already exists, it is overwritten completely. 114 * 115 * <p>Background fetch data is also created based on the given {@code customAudience} and {@code 116 * dailyUpdateUri} and overwrites any existing background fetch data. This method assumes the 117 * input parameters have already been validated and are correct. 118 */ insertOrOverwriteCustomAudience( @onNull DBCustomAudience customAudience, @NonNull Uri dailyUpdateUri, boolean debuggable)119 public void insertOrOverwriteCustomAudience( 120 @NonNull DBCustomAudience customAudience, 121 @NonNull Uri dailyUpdateUri, 122 boolean debuggable) { 123 Objects.requireNonNull(customAudience); 124 Objects.requireNonNull(dailyUpdateUri); 125 126 Instant eligibleUpdateTime; 127 if (customAudience.getUserBiddingSignals() == null 128 || customAudience.getTrustedBiddingData() == null 129 || customAudience.getAds() == null 130 || customAudience.getAds().isEmpty()) { 131 eligibleUpdateTime = Instant.EPOCH; 132 } else { 133 eligibleUpdateTime = 134 DBCustomAudienceBackgroundFetchData 135 .computeNextEligibleUpdateTimeAfterSuccessfulUpdate( 136 customAudience.getCreationTime()); 137 } 138 139 DBCustomAudienceBackgroundFetchData fetchData = 140 DBCustomAudienceBackgroundFetchData.builder() 141 .setOwner(customAudience.getOwner()) 142 .setBuyer(customAudience.getBuyer()) 143 .setName(customAudience.getName()) 144 .setDailyUpdateUri(dailyUpdateUri) 145 .setEligibleUpdateTime(eligibleUpdateTime) 146 .setIsDebuggable(debuggable) 147 .build(); 148 149 insertOrOverwriteCustomAudienceAndBackgroundFetchData(customAudience, fetchData); 150 } 151 152 /** 153 * Updates a custom audience and its background fetch data based on the given {@link 154 * CustomAudienceUpdatableData} in a single transaction. 155 * 156 * <p>If no custom audience is found corresponding to the given {@link 157 * DBCustomAudienceBackgroundFetchData}, no action is taken. 158 */ 159 @Transaction updateCustomAudienceAndBackgroundFetchData( @onNull DBCustomAudienceBackgroundFetchData fetchData, @NonNull CustomAudienceUpdatableData updatableData)160 public void updateCustomAudienceAndBackgroundFetchData( 161 @NonNull DBCustomAudienceBackgroundFetchData fetchData, 162 @NonNull CustomAudienceUpdatableData updatableData) { 163 Objects.requireNonNull(fetchData); 164 Objects.requireNonNull(updatableData); 165 166 DBCustomAudience customAudience = 167 getCustomAudienceByPrimaryKey( 168 fetchData.getOwner(), fetchData.getBuyer(), fetchData.getName()); 169 170 if (customAudience == null) { 171 // This custom audience could have been cleaned up while it was being updated 172 return; 173 } 174 175 customAudience = customAudience.copyWithUpdatableData(updatableData); 176 177 persistCustomAudience(customAudience); 178 persistCustomAudienceBackgroundFetchData(fetchData); 179 } 180 181 /** Returns total size of the {@code custom_audience_quarantine} table. */ 182 @Query("SELECT COUNT(*) FROM custom_audience_quarantine") getTotalNumCustomAudienceQuarantineEntries()183 abstract long getTotalNumCustomAudienceQuarantineEntries(); 184 185 /** 186 * Safely inserts a {@link DBCustomAudienceQuarantine} into the table. 187 * 188 * @throws IllegalStateException if {@link #getTotalNumCustomAudienceQuarantineEntries} exceeds 189 * {@code maxEntries}. 190 * <p>This transaction is separate in order to minimize the critical region while locking 191 * the database. 192 */ 193 @Transaction safelyInsertCustomAudienceQuarantine( @onNull DBCustomAudienceQuarantine dbCustomAudienceQuarantine, long maxEntries)194 public void safelyInsertCustomAudienceQuarantine( 195 @NonNull DBCustomAudienceQuarantine dbCustomAudienceQuarantine, long maxEntries) 196 throws IllegalStateException { 197 Objects.requireNonNull(dbCustomAudienceQuarantine); 198 199 if (getTotalNumCustomAudienceQuarantineEntries() >= maxEntries) { 200 String errorMessage = 201 "Quarantine table maximum has been reached! Not persisting this entry"; 202 sLogger.e(errorMessage); 203 throw new IllegalStateException(errorMessage); 204 } 205 persistCustomAudienceQuarantineData(dbCustomAudienceQuarantine); 206 } 207 208 /** Get count of custom audience. */ 209 @Query("SELECT COUNT(*) FROM custom_audience") getCustomAudienceCount()210 public abstract long getCustomAudienceCount(); 211 212 /** Get count of custom audience of a given owner. */ 213 @Query("SELECT COUNT(*) FROM custom_audience WHERE owner=:owner") getCustomAudienceCountForOwner(String owner)214 public abstract long getCustomAudienceCountForOwner(String owner); 215 216 /** Get the total number of distinct custom audience owner. */ 217 @Query("SELECT COUNT(DISTINCT owner) FROM custom_audience") getCustomAudienceOwnerCount()218 public abstract long getCustomAudienceOwnerCount(); 219 220 /** List all custom audiences by owner and buyer that are marked as debuggable. */ 221 @Query( 222 "SELECT * FROM custom_audience " 223 + "WHERE owner=:owner AND buyer=:buyer AND debuggable=1") 224 @Nullable listDebuggableCustomAudiencesByOwnerAndBuyer( @onNull String owner, @NonNull AdTechIdentifier buyer)225 public abstract List<DBCustomAudience> listDebuggableCustomAudiencesByOwnerAndBuyer( 226 @NonNull String owner, @NonNull AdTechIdentifier buyer); 227 228 /** Get custom audience by owner, buyer and name that are marked as debuggable. */ 229 @Query( 230 "SELECT * FROM custom_audience " 231 + "WHERE owner = :owner AND buyer = :buyer AND name = :name AND debuggable = 1") getDebuggableCustomAudienceByPrimaryKey( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name)232 public abstract DBCustomAudience getDebuggableCustomAudienceByPrimaryKey( 233 @NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name); 234 235 /** 236 * Get the count of total custom audience, the count for the given owner and the count of 237 * distinct owner in one transaction. 238 * 239 * @param owner the owner we need check the count against. 240 * @return the aggregated data of custom audience count 241 */ 242 @Transaction 243 @NonNull getCustomAudienceStats(@onNull String owner)244 public CustomAudienceStats getCustomAudienceStats(@NonNull String owner) { 245 Objects.requireNonNull(owner); 246 247 long customAudienceCount = getCustomAudienceCount(); 248 long customAudienceCountPerOwner = getCustomAudienceCountForOwner(owner); 249 long ownerCount = getCustomAudienceOwnerCount(); 250 251 // TODO(b/255780705): Add buyer and per-buyer stats 252 return CustomAudienceStats.builder() 253 .setOwner(owner) 254 .setTotalCustomAudienceCount(customAudienceCount) 255 .setPerOwnerCustomAudienceCount(customAudienceCountPerOwner) 256 .setTotalOwnerCount(ownerCount) 257 .build(); 258 } 259 260 /** 261 * Add a custom audience override into the table custom_audience_overrides 262 * 263 * @param customAudienceOverride is the CustomAudienceOverride to add to table 264 * custom_audience_overrides. If a {@link DBCustomAudienceOverride} object with the primary 265 * key already exists, this will replace the existing object. 266 */ 267 @Insert(onConflict = OnConflictStrategy.REPLACE) persistCustomAudienceOverride( DBCustomAudienceOverride customAudienceOverride)268 public abstract void persistCustomAudienceOverride( 269 DBCustomAudienceOverride customAudienceOverride); 270 271 /** 272 * Checks if there is a row in the custom audience override data with the unique key combination 273 * of owner, buyer, and name 274 * 275 * @return true if row exists, false otherwise 276 */ 277 @Query( 278 "SELECT EXISTS(SELECT 1 FROM custom_audience_overrides WHERE owner = :owner " 279 + "AND buyer = :buyer AND name = :name LIMIT 1)") doesCustomAudienceOverrideExist( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name)280 public abstract boolean doesCustomAudienceOverrideExist( 281 @NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name); 282 283 /** 284 * Checks if there is a row in the {@code custom_audience_quarantine} with the unique key 285 * combination of owner and buyer. 286 * 287 * @return true if row exists, false otherwise 288 */ 289 @Query( 290 "SELECT EXISTS(SELECT 1 FROM custom_audience_quarantine WHERE owner = :owner " 291 + "AND buyer = :buyer LIMIT 1)") doesCustomAudienceQuarantineExist( @onNull String owner, @NonNull AdTechIdentifier buyer)292 public abstract boolean doesCustomAudienceQuarantineExist( 293 @NonNull String owner, @NonNull AdTechIdentifier buyer); 294 295 /** 296 * Gets expiration time if it exists with the unique key combination of owner and buyer. Returns 297 * null otherwise. 298 */ 299 @Nullable 300 @Query( 301 "SELECT quarantine_expiration_time FROM custom_audience_quarantine WHERE owner = :owner" 302 + " AND buyer = :buyer") getCustomAudienceQuarantineExpiration( @onNull String owner, @NonNull AdTechIdentifier buyer)303 public abstract Instant getCustomAudienceQuarantineExpiration( 304 @NonNull String owner, @NonNull AdTechIdentifier buyer); 305 306 /** 307 * Get custom audience by its unique key. 308 * 309 * @return custom audience result if exists. 310 */ 311 @Query("SELECT * FROM custom_audience WHERE owner = :owner AND buyer = :buyer AND name = :name") 312 @Nullable getCustomAudienceByPrimaryKey( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name)313 public abstract DBCustomAudience getCustomAudienceByPrimaryKey( 314 @NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name); 315 316 /** 317 * Get custom audiences by buyer and name. 318 * 319 * @return custom audiences result if exists. 320 */ 321 @Query("SELECT * FROM custom_audience WHERE buyer = :buyer AND name = :name") 322 @NonNull getCustomAudiencesForBuyerAndName( @onNull AdTechIdentifier buyer, @NonNull String name)323 public abstract List<DBCustomAudience> getCustomAudiencesForBuyerAndName( 324 @NonNull AdTechIdentifier buyer, @NonNull String name); 325 326 /** 327 * Get custom audience background fetch data by its unique key. 328 * 329 * @return custom audience background fetch data if it exists 330 */ 331 @Query( 332 "SELECT * FROM custom_audience_background_fetch_data " 333 + "WHERE owner = :owner AND buyer = :buyer AND name = :name") 334 @Nullable 335 @VisibleForTesting 336 public abstract DBCustomAudienceBackgroundFetchData getCustomAudienceBackgroundFetchDataByPrimaryKey( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name)337 getCustomAudienceBackgroundFetchDataByPrimaryKey( 338 @NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name); 339 340 /** 341 * Get debuggable custom audience background fetch data by its unique key. 342 * 343 * @return custom audience background fetch data if it exists 344 */ 345 @Query( 346 "SELECT * FROM custom_audience_background_fetch_data WHERE owner = :owner AND buyer =" 347 + " :buyer AND name = :name AND is_debuggable = 1") 348 @Nullable 349 public abstract DBCustomAudienceBackgroundFetchData getDebuggableCustomAudienceBackgroundFetchDataByPrimaryKey( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name)350 getDebuggableCustomAudienceBackgroundFetchDataByPrimaryKey( 351 @NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name); 352 353 /** 354 * List debuggable custom audience background fetch data by its unique key. 355 * 356 * @return custom audience background fetch data if it exists 357 */ 358 @Query( 359 "SELECT * FROM custom_audience_background_fetch_data " 360 + "WHERE owner = :owner AND buyer = :buyer AND is_debuggable = 1") 361 @Nullable 362 public abstract List<DBCustomAudienceBackgroundFetchData> listDebuggableCustomAudienceBackgroundFetchData( @onNull String owner, @NonNull AdTechIdentifier buyer)363 listDebuggableCustomAudienceBackgroundFetchData( 364 @NonNull String owner, @NonNull AdTechIdentifier buyer); 365 366 /** 367 * Get custom audience JS override by its unique key. 368 * 369 * <p>This method is not intended to be called on its own. Please use {@link 370 * #getBiddingLogicUriOverride(String, AdTechIdentifier, String, String)} instead. 371 * 372 * @return custom audience override result if exists. 373 */ 374 @Query( 375 "SELECT bidding_logic as bidding_logic_js, bidding_logic_version as" 376 + " buyer_bidding_logic_version FROM custom_audience_overrides WHERE owner =" 377 + " :owner AND buyer = :buyer AND name = :name AND app_package_name=" 378 + " :appPackageName") 379 @Nullable getBiddingLogicUriOverrideInternal( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name, @NonNull String appPackageName)380 protected abstract BiddingLogicJsWithVersion getBiddingLogicUriOverrideInternal( 381 @NonNull String owner, 382 @NonNull AdTechIdentifier buyer, 383 @NonNull String name, 384 @NonNull String appPackageName); 385 386 /** 387 * Get custom audience JS override by its unique key. 388 * 389 * @return custom audience override result if exists. 390 */ getBiddingLogicUriOverride( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name, @NonNull String appPackageName)391 public DecisionLogic getBiddingLogicUriOverride( 392 @NonNull String owner, 393 @NonNull AdTechIdentifier buyer, 394 @NonNull String name, 395 @NonNull String appPackageName) { 396 BiddingLogicJsWithVersion biddingLogicJsWithVersion = 397 getBiddingLogicUriOverrideInternal(owner, buyer, name, appPackageName); 398 399 if (Objects.isNull(biddingLogicJsWithVersion)) { 400 return null; 401 } 402 403 ImmutableMap.Builder<Integer, Long> versionMap = new ImmutableMap.Builder<>(); 404 if (Objects.nonNull(biddingLogicJsWithVersion.getBuyerBiddingLogicVersion())) { 405 versionMap.put( 406 JsVersionHelper.JS_PAYLOAD_TYPE_BUYER_BIDDING_LOGIC_JS, 407 biddingLogicJsWithVersion.getBuyerBiddingLogicVersion()); 408 } 409 410 return DecisionLogic.create( 411 biddingLogicJsWithVersion.getBiddingLogicJs(), versionMap.build()); 412 } 413 414 /** 415 * Get trusted bidding data override by its unique key. 416 * 417 * @return custom audience override result if exists. 418 */ 419 @Query( 420 "SELECT trusted_bidding_data FROM custom_audience_overrides WHERE owner = :owner " 421 + "AND buyer = :buyer AND name = :name AND app_package_name= :appPackageName") 422 @Nullable getTrustedBiddingDataOverride( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name, @NonNull String appPackageName)423 public abstract String getTrustedBiddingDataOverride( 424 @NonNull String owner, 425 @NonNull AdTechIdentifier buyer, 426 @NonNull String name, 427 @NonNull String appPackageName); 428 429 /** Delete the custom audience given owner, buyer, and name. */ 430 @Query("DELETE FROM custom_audience WHERE owner = :owner AND buyer = :buyer AND name = :name") deleteCustomAudienceByPrimaryKey( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name)431 protected abstract void deleteCustomAudienceByPrimaryKey( 432 @NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name); 433 434 /** Delete background fetch data for the custom audience given owner, buyer, and name. */ 435 @Query( 436 "DELETE FROM custom_audience_background_fetch_data WHERE owner = :owner " 437 + "AND buyer = :buyer AND name = :name") deleteCustomAudienceBackgroundFetchDataByPrimaryKey( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name)438 protected abstract void deleteCustomAudienceBackgroundFetchDataByPrimaryKey( 439 @NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name); 440 441 /** 442 * Delete all custom audience data corresponding to the given {@code owner}, {@code buyer}, and 443 * {@code name} in a single transaction. 444 */ 445 @Transaction deleteAllCustomAudienceDataByPrimaryKey( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name)446 public void deleteAllCustomAudienceDataByPrimaryKey( 447 @NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name) { 448 deleteCustomAudienceByPrimaryKey(owner, buyer, name); 449 deleteCustomAudienceBackgroundFetchDataByPrimaryKey(owner, buyer, name); 450 } 451 452 /** 453 * Deletes all custom audiences which are expired, where the custom audiences' expiration times 454 * match or precede the given {@code expiryTime}. 455 * 456 * <p>This method is not intended to be called on its own. Please use {@link 457 * #deleteAllExpiredCustomAudienceData(Instant)} instead. 458 * 459 * @return the number of deleted custom audiences 460 */ 461 @Query("DELETE FROM custom_audience WHERE expiration_time <= :expiryTime") deleteAllExpiredCustomAudiences(@onNull Instant expiryTime)462 protected abstract int deleteAllExpiredCustomAudiences(@NonNull Instant expiryTime); 463 464 /** 465 * Deletes all expired entries of the {@code custom_audience_quarantine} table. 466 * 467 * @return the number of deleted entries 468 */ 469 @Query( 470 "DELETE FROM custom_audience_quarantine WHERE quarantine_expiration_time <=" 471 + " :expiryTime") deleteAllExpiredQuarantineEntries(@onNull Instant expiryTime)472 public abstract int deleteAllExpiredQuarantineEntries(@NonNull Instant expiryTime); 473 474 /** 475 * Deletes all entries with the unique combination of owner and buyer. 476 * 477 * @return the number of deleted entries 478 */ 479 @Query("DELETE FROM custom_audience_quarantine WHERE owner = :owner " + "AND buyer = :buyer") deleteQuarantineEntry( @onNull String owner, @NonNull AdTechIdentifier buyer)480 public abstract int deleteQuarantineEntry( 481 @NonNull String owner, @NonNull AdTechIdentifier buyer); 482 483 /** 484 * Deletes background fetch data for all custom audiences which are expired, where the custom 485 * audiences' expiration times match or precede the given {@code expiryTime}. 486 * 487 * <p>This method is not intended to be called on its own. Please use {@link 488 * #deleteAllExpiredCustomAudienceData(Instant)} instead. 489 */ 490 @Query( 491 "DELETE FROM custom_audience_background_fetch_data WHERE ROWID IN " 492 + "(SELECT bgf.ROWID FROM custom_audience_background_fetch_data AS bgf " 493 + "INNER JOIN custom_audience AS ca " 494 + "ON bgf.buyer = ca.buyer AND bgf.owner = ca.owner AND bgf.name = ca.name " 495 + "WHERE expiration_time <= :expiryTime)") deleteAllExpiredCustomAudienceBackgroundFetchData( @onNull Instant expiryTime)496 protected abstract void deleteAllExpiredCustomAudienceBackgroundFetchData( 497 @NonNull Instant expiryTime); 498 499 /** 500 * Deletes all expired custom audience data in a single transaction, where the custom audiences' 501 * expiration times match or precede the given {@code expiryTime}. 502 * 503 * @return the number of deleted custom audiences 504 */ 505 @Transaction deleteAllExpiredCustomAudienceData(@onNull Instant expiryTime)506 public int deleteAllExpiredCustomAudienceData(@NonNull Instant expiryTime) { 507 deleteAllExpiredCustomAudienceBackgroundFetchData(expiryTime); 508 return deleteAllExpiredCustomAudiences(expiryTime); 509 } 510 511 /** Returns the set of all unique owner apps in the custom audience table. */ 512 @Query("SELECT DISTINCT owner FROM custom_audience") getAllCustomAudienceOwners()513 public abstract List<String> getAllCustomAudienceOwners(); 514 515 /** 516 * Deletes all custom audiences belonging to any app in the given set of {@code ownersToRemove}. 517 * 518 * <p>This method is not intended to be called on its own. Please use {@link 519 * #deleteAllDisallowedOwnerCustomAudienceData(PackageManager, Flags)} instead. 520 * 521 * @return the number of deleted custom audiences 522 */ 523 @Query("DELETE FROM custom_audience WHERE owner IN (:ownersToRemove)") deleteCustomAudiencesByOwner(@onNull List<String> ownersToRemove)524 protected abstract int deleteCustomAudiencesByOwner(@NonNull List<String> ownersToRemove); 525 526 /** 527 * Deletes all custom audience background fetch data belonging to any app in the given set of 528 * {@code ownersToRemove}. 529 * 530 * <p>This method is not intended to be called on its own. Please use {@link 531 * #deleteAllDisallowedOwnerCustomAudienceData(PackageManager, Flags)} instead. 532 */ 533 @Query("DELETE FROM custom_audience_background_fetch_data WHERE owner IN (:ownersToRemove)") deleteCustomAudienceBackgroundFetchDataByOwner( @onNull List<String> ownersToRemove)534 protected abstract void deleteCustomAudienceBackgroundFetchDataByOwner( 535 @NonNull List<String> ownersToRemove); 536 537 /** 538 * Deletes all custom audience data belonging to disallowed owner apps in a single transaction, 539 * where the custom audiences' owner apps cannot be found in the installed list or where the 540 * owner apps are not found in the app allowlist. 541 * 542 * @return a {@link CustomAudienceStats} object containing only the number of deleted custom 543 * audiences and the number of disallowed owner apps found 544 */ 545 @Transaction 546 @NonNull deleteAllDisallowedOwnerCustomAudienceData( @onNull PackageManager packageManager, @NonNull Flags flags)547 public CustomAudienceStats deleteAllDisallowedOwnerCustomAudienceData( 548 @NonNull PackageManager packageManager, @NonNull Flags flags) { 549 Objects.requireNonNull(packageManager); 550 Objects.requireNonNull(flags); 551 List<String> ownersToRemove = getAllCustomAudienceOwners(); 552 553 CleanupUtils.removeAllowedPackages( 554 ownersToRemove, packageManager, Arrays.asList(flags.getPpapiAppAllowList())); 555 556 long numDisallowedOwnersFound = ownersToRemove.size(); 557 long numRemovedCustomAudiences = 0; 558 if (!ownersToRemove.isEmpty()) { 559 deleteCustomAudienceBackgroundFetchDataByOwner(ownersToRemove); 560 numRemovedCustomAudiences = deleteCustomAudiencesByOwner(ownersToRemove); 561 } 562 563 return CustomAudienceStats.builder() 564 .setTotalCustomAudienceCount(numRemovedCustomAudiences) 565 .setTotalOwnerCount(numDisallowedOwnersFound) 566 .build(); 567 } 568 569 /** Returns the set of all unique buyer ad techs in the custom audience table. */ 570 @Query("SELECT DISTINCT buyer FROM custom_audience") getAllCustomAudienceBuyers()571 public abstract List<AdTechIdentifier> getAllCustomAudienceBuyers(); 572 573 /** 574 * Deletes all custom audiences belonging to any ad tech in the given set of {@code 575 * buyersToRemove}. 576 * 577 * <p>This method is not intended to be called on its own. Please use {@link 578 * #deleteAllDisallowedBuyerCustomAudienceData(EnrollmentDao, Flags)} instead. 579 * 580 * @return the number of deleted custom audiences 581 */ 582 @Query("DELETE FROM custom_audience WHERE buyer IN (:buyersToRemove)") deleteCustomAudiencesByBuyer( @onNull List<AdTechIdentifier> buyersToRemove)583 protected abstract int deleteCustomAudiencesByBuyer( 584 @NonNull List<AdTechIdentifier> buyersToRemove); 585 586 /** 587 * Deletes all custom audience background fetch data belonging to any ad tech in the given set 588 * of {@code buyersToRemove}. 589 * 590 * <p>This method is not intended to be called on its own. Please use {@link 591 * #deleteAllDisallowedBuyerCustomAudienceData(EnrollmentDao, Flags)} instead. 592 */ 593 @Query("DELETE FROM custom_audience_background_fetch_data WHERE buyer IN (:buyersToRemove)") deleteCustomAudienceBackgroundFetchDataByBuyer( @onNull List<AdTechIdentifier> buyersToRemove)594 protected abstract void deleteCustomAudienceBackgroundFetchDataByBuyer( 595 @NonNull List<AdTechIdentifier> buyersToRemove); 596 597 /** 598 * Deletes all custom audience data belonging to disallowed buyer ad techs in a single 599 * transaction, where the custom audiences' buyer ad techs cannot be found in the enrollment 600 * database. 601 * 602 * @return a {@link CustomAudienceStats} object containing only the number of deleted custom 603 * audiences and the number of disallowed owner apps found 604 */ 605 @Transaction 606 @NonNull deleteAllDisallowedBuyerCustomAudienceData( @onNull EnrollmentDao enrollmentDao, @NonNull Flags flags)607 public CustomAudienceStats deleteAllDisallowedBuyerCustomAudienceData( 608 @NonNull EnrollmentDao enrollmentDao, @NonNull Flags flags) { 609 Objects.requireNonNull(enrollmentDao); 610 Objects.requireNonNull(flags); 611 612 if (flags.getDisableFledgeEnrollmentCheck()) { 613 sLogger.d("FLEDGE enrollment check disabled; skipping enrolled buyer cleanup"); 614 return CustomAudienceStats.builder() 615 .setTotalCustomAudienceCount(0) 616 .setTotalBuyerCount(0) 617 .build(); 618 } 619 620 List<AdTechIdentifier> buyersToRemove = getAllCustomAudienceBuyers(); 621 622 if (!buyersToRemove.isEmpty()) { 623 Set<AdTechIdentifier> allowedAdTechs = enrollmentDao.getAllFledgeEnrolledAdTechs(); 624 buyersToRemove.removeAll(allowedAdTechs); 625 } 626 627 long numDisallowedBuyersFound = buyersToRemove.size(); 628 long numRemovedCustomAudiences = 0; 629 if (!buyersToRemove.isEmpty()) { 630 deleteCustomAudienceBackgroundFetchDataByBuyer(buyersToRemove); 631 numRemovedCustomAudiences = deleteCustomAudiencesByBuyer(buyersToRemove); 632 } 633 634 return CustomAudienceStats.builder() 635 .setTotalCustomAudienceCount(numRemovedCustomAudiences) 636 .setTotalBuyerCount(numDisallowedBuyersFound) 637 .build(); 638 } 639 640 /** 641 * Deletes ALL custom audiences from the table. 642 * 643 * <p>This method is not intended to be called on its own. Please use {@link 644 * #deleteAllCustomAudienceData(boolean)} instead. 645 */ 646 @Query("DELETE FROM custom_audience") deleteAllCustomAudiences()647 protected abstract void deleteAllCustomAudiences(); 648 649 /** 650 * Deletes ALL custom audience background fetch data from the table. 651 * 652 * <p>This method is not intended to be called on its own. Please use {@link 653 * #deleteAllCustomAudienceData(boolean)} instead. 654 */ 655 @Query("DELETE FROM custom_audience_background_fetch_data") deleteAllCustomAudienceBackgroundFetchData()656 protected abstract void deleteAllCustomAudienceBackgroundFetchData(); 657 658 /** 659 * Deletes ALL custom audience overrides from the table. 660 * 661 * <p>This method is not intended to be called on its own. Please use {@link 662 * #deleteAllCustomAudienceData(boolean)} instead. 663 */ 664 @Query("DELETE FROM custom_audience_overrides") deleteAllCustomAudienceOverrides()665 protected abstract void deleteAllCustomAudienceOverrides(); 666 667 /** Deletes ALL custom audience data from the database in a single transaction. */ 668 @Transaction deleteAllCustomAudienceData(boolean scheduleCustomAudienceEnabled)669 public void deleteAllCustomAudienceData(boolean scheduleCustomAudienceEnabled) { 670 deleteAllCustomAudiences(); 671 deleteAllCustomAudienceBackgroundFetchData(); 672 deleteAllCustomAudienceOverrides(); 673 if (scheduleCustomAudienceEnabled) { 674 deleteAllScheduledCustomAudienceUpdates(); 675 } 676 } 677 678 /** 679 * Deletes all custom audiences belonging to the {@code owner} application from the table. 680 * 681 * <p>This method is not intended to be called on its own. Please use {@link 682 * #deleteCustomAudienceDataByOwner(String, boolean)} instead. 683 */ 684 @Query("DELETE FROM custom_audience WHERE owner = :owner") deleteCustomAudiencesByOwner(@onNull String owner)685 protected abstract void deleteCustomAudiencesByOwner(@NonNull String owner); 686 687 /** 688 * Deletes all custom audience background fetch data belonging to the {@code owner} application 689 * from the table. 690 * 691 * <p>This method is not intended to be called on its own. Please use {@link 692 * #deleteCustomAudienceDataByOwner(String, boolean)} instead. 693 */ 694 @Query("DELETE FROM custom_audience_background_fetch_data WHERE owner = :owner") deleteCustomAudienceBackgroundFetchDataByOwner(@onNull String owner)695 protected abstract void deleteCustomAudienceBackgroundFetchDataByOwner(@NonNull String owner); 696 697 /** 698 * Deletes all custom audience overrides belonging to the {@code owner} application from the 699 * table. 700 * 701 * <p>This method is not intended to be called on its own. Please use {@link 702 * #deleteCustomAudienceDataByOwner(String, boolean)} instead. 703 */ 704 @Query("DELETE FROM custom_audience_overrides WHERE owner = :owner") deleteCustomAudienceOverridesByOwner(@onNull String owner)705 protected abstract void deleteCustomAudienceOverridesByOwner(@NonNull String owner); 706 707 /** 708 * Deletes all custom audience data belonging to the {@code owner} application from the database 709 * in a single transaction. 710 */ 711 @Transaction deleteCustomAudienceDataByOwner( @onNull String owner, boolean scheduleCustomAudienceEnabled)712 public void deleteCustomAudienceDataByOwner( 713 @NonNull String owner, boolean scheduleCustomAudienceEnabled) { 714 deleteCustomAudiencesByOwner(owner); 715 deleteCustomAudienceBackgroundFetchDataByOwner(owner); 716 deleteCustomAudienceOverridesByOwner(owner); 717 if (scheduleCustomAudienceEnabled) { 718 deleteScheduledCustomAudienceUpdatesByOwner(owner); 719 } 720 } 721 722 /** Clean up selected custom audience override data by its primary key */ 723 @Query( 724 "DELETE FROM custom_audience_overrides WHERE owner = :owner AND buyer = :buyer " 725 + "AND name = :name AND app_package_name = :appPackageName") removeCustomAudienceOverrideByPrimaryKeyAndPackageName( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name, @NonNull String appPackageName)726 public abstract void removeCustomAudienceOverrideByPrimaryKeyAndPackageName( 727 @NonNull String owner, 728 @NonNull AdTechIdentifier buyer, 729 @NonNull String name, 730 @NonNull String appPackageName); 731 732 /** Clean up all custom audience override data for the given package name. */ 733 @Query("DELETE FROM custom_audience_overrides WHERE app_package_name = :appPackageName") removeCustomAudienceOverridesByPackageName(@onNull String appPackageName)734 public abstract void removeCustomAudienceOverridesByPackageName(@NonNull String appPackageName); 735 736 /** 737 * Fetch all active Custom Audience including null user_bidding_signals 738 * 739 * @param currentTime to compare against CA time values and find an active CA 740 * @return All the Custom Audience that represent 741 */ 742 @Query( 743 "SELECT * FROM custom_audience WHERE activation_time <= (:currentTime) AND" 744 + " (:currentTime) < expiration_time AND" 745 + " (last_ads_and_bidding_data_updated_time + (:activeWindowTimeMs)) >=" 746 + " (:currentTime) AND trusted_bidding_data_uri IS NOT NULL AND ads IS" 747 + " NOT NULL ") 748 @Nullable getAllActiveCustomAudienceForServerSideAuction( Instant currentTime, long activeWindowTimeMs)749 public abstract List<DBCustomAudience> getAllActiveCustomAudienceForServerSideAuction( 750 Instant currentTime, long activeWindowTimeMs); 751 752 /** 753 * Fetch all the Custom Audience corresponding to the buyers 754 * 755 * @param buyers associated with the Custom Audience 756 * @param currentTime to compare against CA time values and find an active CA 757 * @return All the Custom Audience that represent given buyers 758 */ 759 @Query( 760 "SELECT * FROM custom_audience WHERE buyer in (:buyers) AND activation_time <=" 761 + " (:currentTime) AND (:currentTime) < expiration_time AND" 762 + " (last_ads_and_bidding_data_updated_time + (:activeWindowTimeMs)) >=" 763 + " (:currentTime) AND user_bidding_signals IS NOT NULL AND" 764 + " trusted_bidding_data_uri IS NOT NULL AND ads IS NOT NULL ") 765 @Nullable getActiveCustomAudienceByBuyers( List<AdTechIdentifier> buyers, Instant currentTime, long activeWindowTimeMs)766 public abstract List<DBCustomAudience> getActiveCustomAudienceByBuyers( 767 List<AdTechIdentifier> buyers, Instant currentTime, long activeWindowTimeMs); 768 769 /** 770 * Gets up to {@code maxRowsReturned} rows of {@link DBCustomAudienceBackgroundFetchData} which 771 * correspond to custom audiences that are active, not expired, and eligible for update. 772 */ 773 @Query( 774 "SELECT bgf.* FROM custom_audience_background_fetch_data AS bgf " 775 + "INNER JOIN custom_audience AS ca " 776 + "ON bgf.buyer = ca.buyer AND bgf.owner = ca.owner AND bgf.name = ca.name " 777 + "WHERE bgf.eligible_update_time <= :currentTime " 778 + "AND ca.activation_time <= :currentTime " 779 + "AND :currentTime < ca.expiration_time " 780 + "ORDER BY ca.last_ads_and_bidding_data_updated_time ASC " 781 + "LIMIT :maxRowsReturned") 782 @NonNull 783 public abstract List<DBCustomAudienceBackgroundFetchData> getActiveEligibleCustomAudienceBackgroundFetchData( @onNull Instant currentTime, long maxRowsReturned)784 getActiveEligibleCustomAudienceBackgroundFetchData( 785 @NonNull Instant currentTime, long maxRowsReturned); 786 787 /** 788 * Gets the number of all {@link DBCustomAudienceBackgroundFetchData} for custom audiences that 789 * are active, not expired, and eligible for update. 790 */ 791 @Query( 792 "SELECT COUNT(DISTINCT bgf.ROWID) FROM custom_audience_background_fetch_data AS bgf " 793 + "INNER JOIN custom_audience AS ca " 794 + "ON bgf.buyer = ca.buyer AND bgf.owner = ca.owner AND bgf.name = ca.name " 795 + "WHERE bgf.eligible_update_time <= :currentTime " 796 + "AND ca.activation_time <= :currentTime " 797 + "AND :currentTime < ca.expiration_time") getNumActiveEligibleCustomAudienceBackgroundFetchData( @onNull Instant currentTime)798 public abstract int getNumActiveEligibleCustomAudienceBackgroundFetchData( 799 @NonNull Instant currentTime); 800 801 /** 802 * Persists a delayed Custom Audience Update along with the overrides 803 * 804 * @param update delayed update 805 * @param partialCustomAudienceList overrides for incoming custom audiences 806 */ 807 // TODO(b/324478492) Refactor Update queries in a separate Dao 808 @Transaction insertScheduledUpdateAndPartialCustomAudienceList( @onNull DBScheduledCustomAudienceUpdate update, @NonNull List<PartialCustomAudience> partialCustomAudienceList)809 public void insertScheduledUpdateAndPartialCustomAudienceList( 810 @NonNull DBScheduledCustomAudienceUpdate update, 811 @NonNull List<PartialCustomAudience> partialCustomAudienceList) { 812 long updateId = insertScheduledCustomAudienceUpdate(update); 813 814 List<DBPartialCustomAudience> dbPartialCustomAudienceList = 815 partialCustomAudienceList.stream() 816 .map( 817 partialCa -> 818 DBPartialCustomAudience.builder() 819 .setUpdateId(updateId) 820 .setName(partialCa.getName()) 821 .setActivationTime(partialCa.getActivationTime()) 822 .setExpirationTime(partialCa.getExpirationTime()) 823 .setUserBiddingSignals( 824 partialCa.getUserBiddingSignals()) 825 .build()) 826 .collect(Collectors.toList()); 827 insertPartialCustomAudiencesForUpdate(dbPartialCustomAudienceList); 828 } 829 830 /** Gets updates schedule before a given time along with its corresponding overrides */ 831 @Transaction 832 public List<Pair<DBScheduledCustomAudienceUpdate, List<DBPartialCustomAudience>>> getScheduledUpdatesAndOverridesBeforeTime(@onNull Instant timestamp)833 getScheduledUpdatesAndOverridesBeforeTime(@NonNull Instant timestamp) { 834 835 List<DBScheduledCustomAudienceUpdate> scheduledUpdates = 836 getCustomAudienceUpdatesScheduledBeforeTime(timestamp); 837 838 List<Pair<DBScheduledCustomAudienceUpdate, List<DBPartialCustomAudience>>> updatesList = 839 new ArrayList<>(); 840 scheduledUpdates.forEach( 841 update -> 842 updatesList.add( 843 new Pair( 844 update, 845 getPartialAudienceListForUpdateId(update.getUpdateId())))); 846 return updatesList; 847 } 848 849 /** Persists a delayed Custom Audience Update and generated a unique update_id */ 850 @Insert(onConflict = OnConflictStrategy.REPLACE) insertScheduledCustomAudienceUpdate( @onNull DBScheduledCustomAudienceUpdate update)851 public abstract long insertScheduledCustomAudienceUpdate( 852 @NonNull DBScheduledCustomAudienceUpdate update); 853 854 /** Persists Custom Audience Overrides associated with a delayed update */ 855 @Insert(onConflict = OnConflictStrategy.REPLACE) insertPartialCustomAudiencesForUpdate( @onNull List<DBPartialCustomAudience> partialCustomAudienceList)856 public abstract void insertPartialCustomAudiencesForUpdate( 857 @NonNull List<DBPartialCustomAudience> partialCustomAudienceList); 858 859 /** Gets Custom Audience Overrides associated with a delayed update */ 860 @Query("SELECT * FROM partial_custom_audience WHERE update_id = :updateId") getPartialAudienceListForUpdateId(Long updateId)861 public abstract List<DBPartialCustomAudience> getPartialAudienceListForUpdateId(Long updateId); 862 863 /** Gets list of delayed Custom Audience Updates scheduled before the given time */ 864 @Query("SELECT * FROM scheduled_custom_audience_update WHERE scheduled_time <= :timestamp") 865 public abstract List<DBScheduledCustomAudienceUpdate> getCustomAudienceUpdatesScheduledBeforeTime(Instant timestamp)866 getCustomAudienceUpdatesScheduledBeforeTime(Instant timestamp); 867 868 /** Gets list of delayed Custom Audience Updates scheduled by owner */ 869 @Query("SELECT * FROM scheduled_custom_audience_update WHERE owner = :owner") getCustomAudienceUpdatesScheduledByOwner( String owner)870 public abstract List<DBScheduledCustomAudienceUpdate> getCustomAudienceUpdatesScheduledByOwner( 871 String owner); 872 873 /** Gets list of delayed Custom Audience Updates created before the given time */ 874 @Query("DELETE FROM scheduled_custom_audience_update WHERE creation_time <= :timestamp") deleteScheduledCustomAudienceUpdatesCreatedBeforeTime(Instant timestamp)875 public abstract void deleteScheduledCustomAudienceUpdatesCreatedBeforeTime(Instant timestamp); 876 877 /** Removes all the Custom Audience Update with the given owner */ 878 @Query("DELETE FROM scheduled_custom_audience_update WHERE owner = :owner") deleteScheduledCustomAudienceUpdatesByOwner(String owner)879 protected abstract void deleteScheduledCustomAudienceUpdatesByOwner(String owner); 880 881 /** 882 * Deletes all the Custom Audience Updates data 883 * 884 * <p>This method is not intended to be called on its own. Please use {@link 885 * #deleteAllCustomAudienceData(boolean)} instead. 886 */ 887 @Query("DELETE FROM scheduled_custom_audience_update") deleteAllScheduledCustomAudienceUpdates()888 protected abstract void deleteAllScheduledCustomAudienceUpdates(); 889 890 /** 891 * Removes a Custom Audience Update from storage and cascades the deletion to associated Partial 892 * Custom Audiences for overrides 893 */ 894 @Delete deleteScheduledCustomAudienceUpdate( @onNull DBScheduledCustomAudienceUpdate update)895 public abstract void deleteScheduledCustomAudienceUpdate( 896 @NonNull DBScheduledCustomAudienceUpdate update); 897 898 @VisibleForTesting 899 static class BiddingLogicJsWithVersion { 900 @ColumnInfo(name = "bidding_logic_js") 901 @NonNull 902 String mBiddingLogicJs; 903 904 @ColumnInfo(name = "buyer_bidding_logic_version") 905 @Nullable 906 Long mBuyerBiddingLogicVersion; 907 BiddingLogicJsWithVersion( @onNull String biddingLogicJs, @Nullable Long buyerBiddingLogicVersion)908 BiddingLogicJsWithVersion( 909 @NonNull String biddingLogicJs, @Nullable Long buyerBiddingLogicVersion) { 910 this.mBiddingLogicJs = biddingLogicJs; 911 this.mBuyerBiddingLogicVersion = buyerBiddingLogicVersion; 912 } 913 914 @NonNull getBiddingLogicJs()915 public String getBiddingLogicJs() { 916 return mBiddingLogicJs; 917 } 918 919 @Nullable getBuyerBiddingLogicVersion()920 public Long getBuyerBiddingLogicVersion() { 921 return mBuyerBiddingLogicVersion; 922 } 923 } 924 } 925