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