blob: 46dfe6ce9b4a67a0de5696cb5eeee4ac71246bf4 [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.room.ColumnInfo;
import androidx.room.Entity;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.customaudience.CustomAudienceUpdatableData;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.google.auto.value.AutoValue;
import java.time.Instant;
import java.util.Objects;
/** This POJO represents the schema for the background fetch data for custom audiences. */
@AutoValue
@AutoValue.CopyAnnotations
@Entity(
tableName = DBCustomAudienceBackgroundFetchData.TABLE_NAME,
primaryKeys = {"owner", "buyer", "name"},
inheritSuperIndices = true)
public abstract class DBCustomAudienceBackgroundFetchData {
public static final String TABLE_NAME = "custom_audience_background_fetch_data";
/** @return the owner package name of the custom audience */
@AutoValue.CopyAnnotations
@ColumnInfo(name = "owner", index = true)
@NonNull
public abstract String getOwner();
/** @return the buyer for the custom audience */
@AutoValue.CopyAnnotations
@ColumnInfo(name = "buyer", index = true)
@NonNull
public abstract AdTechIdentifier getBuyer();
/** @return the name of the custom audience */
@AutoValue.CopyAnnotations
@ColumnInfo(name = "name", index = true)
@NonNull
public abstract String getName();
/** @return the daily update URI for the custom audience */
@AutoValue.CopyAnnotations
@ColumnInfo(name = "daily_update_uri")
@NonNull
public abstract Uri getDailyUpdateUri();
/** @return the time after which the specified custom audience is eligible to be updated */
@AutoValue.CopyAnnotations
@ColumnInfo(name = "eligible_update_time", index = true)
@NonNull
public abstract Instant getEligibleUpdateTime();
/**
* @return the number of failures since the last successful update caused by response validation
*/
@AutoValue.CopyAnnotations
@ColumnInfo(name = "num_validation_failures")
public abstract long getNumValidationFailures();
/** @return the number of failures since the last successful update caused by fetch timeouts */
@AutoValue.CopyAnnotations
@ColumnInfo(name = "num_timeout_failures")
public abstract long getNumTimeoutFailures();
/** @return an AutoValue builder for a {@link DBCustomAudienceBackgroundFetchData} object */
@NonNull
public static DBCustomAudienceBackgroundFetchData.Builder builder() {
return new AutoValue_DBCustomAudienceBackgroundFetchData.Builder()
.setNumValidationFailures(0)
.setNumTimeoutFailures(0);
}
/**
* Creates a {@link DBCustomAudienceBackgroundFetchData} object using the builder.
*
* <p>Required for Room SQLite integration.
*/
@NonNull
public static DBCustomAudienceBackgroundFetchData create(
@NonNull String owner,
@NonNull AdTechIdentifier buyer,
@NonNull String name,
@NonNull Uri dailyUpdateUri,
@NonNull Instant eligibleUpdateTime,
long numValidationFailures,
long numTimeoutFailures) {
return builder()
.setOwner(owner)
.setBuyer(buyer)
.setName(name)
.setDailyUpdateUri(dailyUpdateUri)
.setEligibleUpdateTime(eligibleUpdateTime)
.setNumValidationFailures(numValidationFailures)
.setNumTimeoutFailures(numTimeoutFailures)
.build();
}
/**
* Computes the next eligible update time, given the most recent successful update time and
* flags.
*
* <p>This method is split out for testing because testing with P/H flags in a static method
* requires non-trivial permissions in test applications.
*/
@VisibleForTesting
@NonNull
public static Instant computeNextEligibleUpdateTimeAfterSuccessfulUpdate(
@NonNull Instant successfulUpdateTime, @NonNull Flags flags) {
Objects.requireNonNull(successfulUpdateTime);
Objects.requireNonNull(flags);
// Successful updates are next eligible in base interval (one day) + jitter
// TODO(b/221861706): Implement jitter
return successfulUpdateTime.plusSeconds(
flags.getFledgeBackgroundFetchEligibleUpdateBaseIntervalS());
}
/** Computes the next eligible update time, given the most recent successful update time. */
@NonNull
public static Instant computeNextEligibleUpdateTimeAfterSuccessfulUpdate(
@NonNull Instant successfulUpdateTime) {
return computeNextEligibleUpdateTimeAfterSuccessfulUpdate(
successfulUpdateTime, FlagsFactory.getFlags());
}
/**
* Creates a copy of the current object with an updated failure count based on the input failure
* type.
*/
@NonNull
public final DBCustomAudienceBackgroundFetchData copyWithUpdatableData(
@NonNull CustomAudienceUpdatableData updatableData) {
// Create a builder with a full copy of the current object
DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder =
builder()
.setOwner(getOwner())
.setBuyer(getBuyer())
.setName(getName())
.setDailyUpdateUri(getDailyUpdateUri())
.setEligibleUpdateTime(getEligibleUpdateTime())
.setNumValidationFailures(getNumValidationFailures())
.setNumTimeoutFailures(getNumTimeoutFailures());
if (updatableData.getContainsSuccessfulUpdate()) {
fetchDataBuilder.setEligibleUpdateTime(
computeNextEligibleUpdateTimeAfterSuccessfulUpdate(
updatableData.getAttemptedUpdateTime()));
// Reset all failure counts on any successful update
fetchDataBuilder.setNumValidationFailures(0).setNumTimeoutFailures(0);
} else {
switch (updatableData.getInitialUpdateResult()) {
case SUCCESS:
// This success result is only the result set prior to syntax validation of the
// response, so an error must have occurred during data validation
// INTENTIONAL FALLTHROUGH
case RESPONSE_VALIDATION_FAILURE:
fetchDataBuilder.setNumValidationFailures(getNumValidationFailures() + 1);
break;
case NETWORK_FAILURE:
// TODO(b/221861706): Consider differentiating timeout failures for fairness
// TODO(b/237342352): Consolidate timeout failures if they don't need to be
// distinguished
// INTENTIONAL FALLTHROUGH
case NETWORK_READ_TIMEOUT_FAILURE:
fetchDataBuilder.setNumTimeoutFailures(getNumTimeoutFailures() + 1);
break;
case K_ANON_FAILURE:
// TODO(b/234884352): Implement k-anon check
// INTENTIONAL FALLTHROUGH
case UNKNOWN:
// Treat this as a benign failure, so we can just try again
break;
}
// TODO(b/221861706): Decide whether this custom audience is delinquent and set its next
// eligible update time
// TODO(b/221861706): Implement jitter for delinquent updates
// Non-delinquent failed updates are immediately eligible to be updated in the next job
fetchDataBuilder.setEligibleUpdateTime(getEligibleUpdateTime());
}
return fetchDataBuilder.build();
}
/** Builder class for a {@link DBCustomAudienceBackgroundFetchData} object. */
@AutoValue.Builder
public abstract static class Builder {
/** Sets the owner package name for the custom audience. */
@NonNull
public abstract Builder setOwner(@NonNull String value);
/** Sets the buyer for the custom audience. */
@NonNull
public abstract Builder setBuyer(@NonNull AdTechIdentifier value);
/** Sets the name for the custom audience. */
@NonNull
public abstract Builder setName(@NonNull String value);
/** Sets the daily update URI for the custom audience. */
@NonNull
public abstract Builder setDailyUpdateUri(@NonNull Uri value);
/** Sets the time after which the custom audience will be eligible for update. */
@NonNull
public abstract Builder setEligibleUpdateTime(@NonNull Instant value);
/**
* Sets the number of failures due to response validation for the custom audience since the
* last successful update.
*/
@NonNull
public abstract Builder setNumValidationFailures(long value);
/**
* Sets the number of failures due to fetch timeout for the custom audience since the last
* successful update.
*/
@NonNull
public abstract Builder setNumTimeoutFailures(long value);
/**
* Builds the {@link DBCustomAudienceBackgroundFetchData} object and returns it.
*
* <p>Note that AutoValue doesn't by itself do any validation, so splitting the builder with
* a manual verification is recommended. See go/autovalue/builders-howto#validate for more
* information.
*/
@NonNull
protected abstract DBCustomAudienceBackgroundFetchData autoValueBuild();
/**
* Builds, validates, and returns the {@link DBCustomAudienceBackgroundFetchData} object.
*/
@NonNull
public final DBCustomAudienceBackgroundFetchData build() {
DBCustomAudienceBackgroundFetchData fetchData = autoValueBuild();
// Fields marked @NonNull are already validated by AutoValue
Preconditions.checkArgument(
fetchData.getNumValidationFailures() >= 0
&& fetchData.getNumTimeoutFailures() >= 0,
"Update failure count must be non-negative");
return fetchData;
}
}
}