blob: 76386da5fd2ec759fc46545d41747fbb052bfe5b [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.adservices.data.customaudience;
import android.adservices.common.AdTechIdentifier;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Transaction;
import com.android.adservices.service.customaudience.CustomAudienceUpdatableData;
import com.android.internal.annotations.VisibleForTesting;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
/**
* DAO abstract class used to access Custom Audience persistent storage.
*
* <p>Annotations will generate Room-based SQLite Dao impl.
*/
@Dao
public abstract class CustomAudienceDao {
/**
* Add user to a new custom audience. As designed, will override existing one.
*
* <p>This method is not meant to be used on its own, since custom audiences must be persisted
* alongside matching background fetch data. Use {@link
* #insertOrOverwriteCustomAudience(DBCustomAudience, Uri)} instead.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract void persistCustomAudience(@NonNull DBCustomAudience customAudience);
/**
* Adds or updates background fetch data for a custom audience.
*
* <p>This method does not update the corresponding custom audience. Use {@link
* #updateCustomAudienceAndBackgroundFetchData(DBCustomAudienceBackgroundFetchData,
* CustomAudienceUpdatableData)} to do so safely.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract void persistCustomAudienceBackgroundFetchData(
@NonNull DBCustomAudienceBackgroundFetchData fetchData);
/**
* Adds or updates a given custom audience and background fetch data in a single transaction.
*
* <p>This transaction is separate in order to minimize the critical region while locking the
* database. It is not meant to be exposed or used by itself; use {@link
* #insertOrOverwriteCustomAudience(DBCustomAudience, Uri)} instead.
*/
@Transaction
protected void insertOrOverwriteCustomAudienceAndBackgroundFetchData(
@NonNull DBCustomAudience customAudience,
@NonNull DBCustomAudienceBackgroundFetchData fetchData) {
persistCustomAudience(customAudience);
persistCustomAudienceBackgroundFetchData(fetchData);
}
/**
* Adds the user to the given custom audience.
*
* <p>If a custom audience already exists, it is overwritten completely.
*
* <p>Background fetch data is also created based on the given {@code customAudience} and {@code
* dailyUpdateUri} and overwrites any existing background fetch data. This method assumes the
* input parameters have already been validated and are correct.
*/
public void insertOrOverwriteCustomAudience(
@NonNull DBCustomAudience customAudience, @NonNull Uri dailyUpdateUri) {
Objects.requireNonNull(customAudience);
Objects.requireNonNull(dailyUpdateUri);
Instant eligibleUpdateTime;
if (customAudience.getUserBiddingSignals() == null
|| customAudience.getTrustedBiddingData() == null
|| customAudience.getAds() == null
|| customAudience.getAds().isEmpty()) {
eligibleUpdateTime = Instant.EPOCH;
} else {
eligibleUpdateTime =
DBCustomAudienceBackgroundFetchData
.computeNextEligibleUpdateTimeAfterSuccessfulUpdate(
customAudience.getCreationTime());
}
DBCustomAudienceBackgroundFetchData fetchData =
DBCustomAudienceBackgroundFetchData.builder()
.setOwner(customAudience.getOwner())
.setBuyer(customAudience.getBuyer())
.setName(customAudience.getName())
.setDailyUpdateUri(dailyUpdateUri)
.setEligibleUpdateTime(eligibleUpdateTime)
.build();
insertOrOverwriteCustomAudienceAndBackgroundFetchData(customAudience, fetchData);
}
/**
* Updates a custom audience and its background fetch data based on the given {@link
* CustomAudienceUpdatableData} in a single transaction.
*
* <p>If no custom audience is found corresponding to the given {@link
* DBCustomAudienceBackgroundFetchData}, no action is taken.
*/
@Transaction
public void updateCustomAudienceAndBackgroundFetchData(
@NonNull DBCustomAudienceBackgroundFetchData fetchData,
@NonNull CustomAudienceUpdatableData updatableData) {
Objects.requireNonNull(fetchData);
Objects.requireNonNull(updatableData);
DBCustomAudience customAudience =
getCustomAudienceByPrimaryKey(
fetchData.getOwner(), fetchData.getBuyer(), fetchData.getName());
if (customAudience == null) {
// This custom audience could have been cleaned up while it was being updated
return;
}
customAudience = customAudience.copyWithUpdatableData(updatableData);
persistCustomAudience(customAudience);
persistCustomAudienceBackgroundFetchData(fetchData);
}
/** Get count of custom audience. */
@Query("SELECT COUNT(*) FROM custom_audience")
public abstract long getCustomAudienceCount();
/** Get count of custom audience of a given owner. */
@Query("SELECT COUNT(*) FROM custom_audience WHERE owner=:owner")
public abstract long getCustomAudienceCountForOwner(String owner);
/** Get the total number of distinct custom audience owner. */
@Query("SELECT COUNT(DISTINCT owner) FROM custom_audience")
public abstract long getCustomAudienceOwnerCount();
/**
* Get the count of total custom audience, the count for the given owner and the count of
* distinct owner in one transaction.
*
* @param owner the owner we need check the count against.
* @return the aggregated data of custom audience count
*/
@Transaction
@NonNull
public CustomAudienceStats getCustomAudienceStats(@NonNull String owner) {
Objects.requireNonNull(owner);
long customAudienceCount = getCustomAudienceCount();
long customAudienceCountPerOwner = getCustomAudienceCountForOwner(owner);
long ownerCount = getCustomAudienceOwnerCount();
return new CustomAudienceStats(
owner, customAudienceCount, customAudienceCountPerOwner, ownerCount);
}
/**
* Add a custom audience override into the table custom_audience_overrides
*
* @param customAudienceOverride is the CustomAudienceOverride to add to table
* custom_audience_overrides. If a {@link DBCustomAudienceOverride} object with the primary
* key already exists, this will replace the existing object.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract void persistCustomAudienceOverride(
DBCustomAudienceOverride customAudienceOverride);
/**
* Checks if there is a row in the custom audience override data with the unique key combination
* of owner, buyer, and name
*
* @return true if row exists, false otherwise
*/
@Query(
"SELECT EXISTS(SELECT 1 FROM custom_audience_overrides WHERE owner = :owner "
+ "AND buyer = :buyer AND name = :name LIMIT 1)")
public abstract boolean doesCustomAudienceOverrideExist(
@NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name);
/**
* Get custom audience by its unique key.
*
* @return custom audience result if exists.
*/
@Query("SELECT * FROM custom_audience WHERE owner = :owner AND buyer = :buyer AND name = :name")
@Nullable
@VisibleForTesting
public abstract DBCustomAudience getCustomAudienceByPrimaryKey(
@NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name);
/**
* Get custom audience background fetch data by its unique key.
*
* @return custom audience background fetch data if it exists
*/
@Query(
"SELECT * FROM custom_audience_background_fetch_data "
+ "WHERE owner = :owner AND buyer = :buyer AND name = :name")
@Nullable
@VisibleForTesting
public abstract DBCustomAudienceBackgroundFetchData
getCustomAudienceBackgroundFetchDataByPrimaryKey(
@NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name);
/**
* Get custom audience JS override by its unique key.
*
* @return custom audience override result if exists.
*/
@Query(
"SELECT bidding_logic FROM custom_audience_overrides WHERE owner = :owner "
+ "AND buyer = :buyer AND name = :name AND app_package_name= :appPackageName")
@Nullable
public abstract String getBiddingLogicUriOverride(
@NonNull String owner,
@NonNull AdTechIdentifier buyer,
@NonNull String name,
@NonNull String appPackageName);
/**
* Get trusted bidding data override by its unique key.
*
* @return custom audience override result if exists.
*/
@Query(
"SELECT trusted_bidding_data FROM custom_audience_overrides WHERE owner = :owner "
+ "AND buyer = :buyer AND name = :name AND app_package_name= :appPackageName")
@Nullable
public abstract String getTrustedBiddingDataOverride(
@NonNull String owner,
@NonNull AdTechIdentifier buyer,
@NonNull String name,
@NonNull String appPackageName);
/** Delete the custom audience given owner, buyer, and name. */
@Query("DELETE FROM custom_audience WHERE owner = :owner AND buyer = :buyer AND name = :name")
protected abstract void deleteCustomAudienceByPrimaryKey(
@NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name);
/** Delete background fetch data for the custom audience given owner, buyer, and name. */
@Query(
"DELETE FROM custom_audience_background_fetch_data WHERE owner = :owner "
+ "AND buyer = :buyer AND name = :name")
protected abstract void deleteCustomAudienceBackgroundFetchDataByPrimaryKey(
@NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name);
/**
* Delete all custom audience data corresponding to the given {@code owner}, {@code buyer}, and
* {@code name} in a single transaction.
*/
@Transaction
public void deleteAllCustomAudienceDataByPrimaryKey(
@NonNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name) {
deleteCustomAudienceByPrimaryKey(owner, buyer, name);
deleteCustomAudienceBackgroundFetchDataByPrimaryKey(owner, buyer, name);
}
/**
* Deletes all custom audiences which are expired, where the custom audiences' expiration times
* match or precede the given {@code expiryTime}.
*
* <p>This method is not intended to be called on its own. Please use {@link
* #deleteAllExpiredCustomAudienceData(Instant)} instead.
*
* @return the number of deleted custom audiences
*/
@Query("DELETE FROM custom_audience WHERE expiration_time <= :expiryTime")
protected abstract int deleteAllExpiredCustomAudiences(@NonNull Instant expiryTime);
/**
* Deletes background fetch data for all custom audiences which are expired, where the custom
* audiences' expiration times match or precede the given {@code expiryTime}.
*
* <p>This method is not intended to be called on its own. Please use {@link
* #deleteAllExpiredCustomAudienceData(Instant)} instead.
*/
@Query(
"DELETE FROM custom_audience_background_fetch_data WHERE ROWID IN "
+ "(SELECT bgf.ROWID FROM custom_audience_background_fetch_data AS bgf "
+ "INNER JOIN custom_audience AS ca "
+ "ON bgf.buyer = ca.buyer AND bgf.owner = ca.owner AND bgf.name = ca.name "
+ "WHERE expiration_time <= :expiryTime)")
protected abstract void deleteAllExpiredCustomAudienceBackgroundFetchData(
@NonNull Instant expiryTime);
/**
* Deletes all expired custom audience data in a single transaction, where the custom audiences'
* expiration times match or precede the given {@code expiryTime}.
*
* @return the number of deleted custom audiences
*/
@Transaction
public int deleteAllExpiredCustomAudienceData(@NonNull Instant expiryTime) {
deleteAllExpiredCustomAudienceBackgroundFetchData(expiryTime);
return deleteAllExpiredCustomAudiences(expiryTime);
}
/**
* Deletes ALL custom audiences from the table.
*
* <p>This method is not intended to be called on its own. Please use {@link
* #deleteAllCustomAudienceData()} instead.
*/
@Query("DELETE FROM custom_audience")
protected abstract void deleteAllCustomAudiences();
/**
* Deletes ALL custom audience background fetch data from the table.
*
* <p>This method is not intended to be called on its own. Please use {@link
* #deleteAllCustomAudienceData()} instead.
*/
@Query("DELETE FROM custom_audience_background_fetch_data")
protected abstract void deleteAllCustomAudienceBackgroundFetchData();
/**
* Deletes ALL custom audience overrides from the table.
*
* <p>This method is not intended to be called on its own. Please use {@link
* #deleteAllCustomAudienceData()} instead.
*/
@Query("DELETE FROM custom_audience_overrides")
protected abstract void deleteAllCustomAudienceOverrides();
/** Deletes ALL custom audience data from the database in a single transaction. */
@Transaction
public void deleteAllCustomAudienceData() {
deleteAllCustomAudiences();
deleteAllCustomAudienceBackgroundFetchData();
deleteAllCustomAudienceOverrides();
}
/**
* Deletes all custom audiences belonging to the {@code owner} application from the table.
*
* <p>This method is not intended to be called on its own. Please use {@link
* #deleteCustomAudienceDataByOwner(String)} instead.
*/
@Query("DELETE FROM custom_audience WHERE owner = :owner")
protected abstract void deleteCustomAudiencesByOwner(@NonNull String owner);
/**
* Deletes all custom audience background fetch data belonging to the {@code owner} application
* from the table.
*
* <p>This method is not intended to be called on its own. Please use {@link
* #deleteCustomAudienceDataByOwner(String)} instead.
*/
@Query("DELETE FROM custom_audience_background_fetch_data WHERE owner = :owner")
protected abstract void deleteCustomAudienceBackgroundFetchDataByOwner(@NonNull String owner);
/**
* Deletes all custom audience overrides belonging to the {@code owner} application from the
* table.
*
* <p>This method is not intended to be called on its own. Please use {@link
* #deleteCustomAudienceDataByOwner(String)} instead.
*/
@Query("DELETE FROM custom_audience_overrides WHERE owner = :owner")
protected abstract void deleteCustomAudienceOverridesByOwner(@NonNull String owner);
/**
* Deletes all custom audience data belonging to the {@code owner} application from the database
* in a single transaction.
*/
@Transaction
public void deleteCustomAudienceDataByOwner(@NonNull String owner) {
deleteCustomAudiencesByOwner(owner);
deleteCustomAudienceBackgroundFetchDataByOwner(owner);
deleteCustomAudienceOverridesByOwner(owner);
}
/** Clean up selected custom audience override data by its primary key */
@Query(
"DELETE FROM custom_audience_overrides WHERE owner = :owner AND buyer = :buyer "
+ "AND name = :name AND app_package_name = :appPackageName")
public abstract void removeCustomAudienceOverrideByPrimaryKeyAndPackageName(
@NonNull String owner,
@NonNull AdTechIdentifier buyer,
@NonNull String name,
@NonNull String appPackageName);
/** Clean up all custom audience override data for the given package name. */
@Query("DELETE FROM custom_audience_overrides WHERE app_package_name = :appPackageName")
public abstract void removeCustomAudienceOverridesByPackageName(@NonNull String appPackageName);
/**
* Fetch all the Custom Audience corresponding to the buyers
*
* @param buyers associated with the Custom Audience
* @param currentTime to compare against CA time values and find an active CA
* @return All the Custom Audience that represent given buyers
*/
@Query(
"SELECT * FROM custom_audience WHERE buyer in (:buyers) AND activation_time <="
+ " (:currentTime) AND (:currentTime) < expiration_time AND"
+ " (last_ads_and_bidding_data_updated_time + (:activeWindowTimeMs)) >="
+ " (:currentTime) AND user_bidding_signals IS NOT NULL AND"
+ " trusted_bidding_data_uri IS NOT NULL AND ads IS NOT NULL ")
@Nullable
public abstract List<DBCustomAudience> getActiveCustomAudienceByBuyers(
List<AdTechIdentifier> buyers, Instant currentTime, long activeWindowTimeMs);
/**
* Gets up to {@code maxRowsReturned} rows of {@link DBCustomAudienceBackgroundFetchData} which
* correspond to custom audiences that are active, not expired, and eligible for update.
*/
@Query(
"SELECT bgf.* FROM custom_audience_background_fetch_data AS bgf "
+ "INNER JOIN custom_audience AS ca "
+ "ON bgf.buyer = ca.buyer AND bgf.owner = ca.owner AND bgf.name = ca.name "
+ "WHERE bgf.eligible_update_time <= :currentTime "
+ "AND ca.activation_time <= :currentTime "
+ "AND :currentTime < ca.expiration_time "
+ "ORDER BY ca.last_ads_and_bidding_data_updated_time ASC "
+ "LIMIT :maxRowsReturned")
@NonNull
public abstract List<DBCustomAudienceBackgroundFetchData>
getActiveEligibleCustomAudienceBackgroundFetchData(
@NonNull Instant currentTime, long maxRowsReturned);
/**
* Gets the number of all {@link DBCustomAudienceBackgroundFetchData} for custom audiences that
* are active, not expired, and eligible for update.
*/
@Query(
"SELECT COUNT(DISTINCT bgf.ROWID) FROM custom_audience_background_fetch_data AS bgf "
+ "INNER JOIN custom_audience AS ca "
+ "ON bgf.buyer = ca.buyer AND bgf.owner = ca.owner AND bgf.name = ca.name "
+ "WHERE bgf.eligible_update_time <= :currentTime "
+ "AND ca.activation_time <= :currentTime "
+ "AND :currentTime < ca.expiration_time")
public abstract int getNumActiveEligibleCustomAudienceBackgroundFetchData(
@NonNull Instant currentTime);
/** Class represents custom audience stats query result. */
public static class CustomAudienceStats {
private final String mOwner;
private final long mTotalCount;
private final long mPerOwnerCount;
private final long mOwnerCount;
public CustomAudienceStats(
String owner, long totalCount, long perOwnerCount, long ownerCount) {
mOwner = owner;
mTotalCount = totalCount;
mPerOwnerCount = perOwnerCount;
mOwnerCount = ownerCount;
}
public String getOwner() {
return mOwner;
}
public long getTotalCount() {
return mTotalCount;
}
public long getPerOwnerCount() {
return mPerOwnerCount;
}
public long getOwnerCount() {
return mOwnerCount;
}
}
}