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