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.net.Uri; 21 22 import androidx.annotation.NonNull; 23 import androidx.room.ColumnInfo; 24 import androidx.room.Entity; 25 26 import com.android.adservices.service.Flags; 27 import com.android.adservices.service.FlagsFactory; 28 import com.android.adservices.service.customaudience.CustomAudienceUpdatableData; 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.util.Preconditions; 31 32 import com.google.auto.value.AutoValue; 33 34 import java.time.Instant; 35 import java.util.Objects; 36 37 /** This POJO represents the schema for the background fetch data for custom audiences. */ 38 @AutoValue 39 @AutoValue.CopyAnnotations 40 @Entity( 41 tableName = DBCustomAudienceBackgroundFetchData.TABLE_NAME, 42 primaryKeys = {"owner", "buyer", "name"}, 43 inheritSuperIndices = true) 44 public abstract class DBCustomAudienceBackgroundFetchData { 45 public static final String TABLE_NAME = "custom_audience_background_fetch_data"; 46 47 /** @return the owner package name of the custom audience */ 48 @AutoValue.CopyAnnotations 49 @ColumnInfo(name = "owner", index = true) 50 @NonNull getOwner()51 public abstract String getOwner(); 52 53 /** @return the buyer for the custom audience */ 54 @AutoValue.CopyAnnotations 55 @ColumnInfo(name = "buyer", index = true) 56 @NonNull getBuyer()57 public abstract AdTechIdentifier getBuyer(); 58 59 /** @return the name of the custom audience */ 60 @AutoValue.CopyAnnotations 61 @ColumnInfo(name = "name", index = true) 62 @NonNull getName()63 public abstract String getName(); 64 65 /** @return the daily update URI for the custom audience */ 66 @AutoValue.CopyAnnotations 67 @ColumnInfo(name = "daily_update_uri") 68 @NonNull getDailyUpdateUri()69 public abstract Uri getDailyUpdateUri(); 70 71 /** @return the time after which the specified custom audience is eligible to be updated */ 72 @AutoValue.CopyAnnotations 73 @ColumnInfo(name = "eligible_update_time", index = true) 74 @NonNull getEligibleUpdateTime()75 public abstract Instant getEligibleUpdateTime(); 76 77 /** 78 * @return the number of failures since the last successful update caused by response validation 79 */ 80 @AutoValue.CopyAnnotations 81 @ColumnInfo(name = "num_validation_failures") getNumValidationFailures()82 public abstract long getNumValidationFailures(); 83 84 /** @return the number of failures since the last successful update caused by fetch timeouts */ 85 @AutoValue.CopyAnnotations 86 @ColumnInfo(name = "num_timeout_failures") getNumTimeoutFailures()87 public abstract long getNumTimeoutFailures(); 88 89 /** @return an AutoValue builder for a {@link DBCustomAudienceBackgroundFetchData} object */ 90 @NonNull builder()91 public static DBCustomAudienceBackgroundFetchData.Builder builder() { 92 return new AutoValue_DBCustomAudienceBackgroundFetchData.Builder() 93 .setNumValidationFailures(0) 94 .setNumTimeoutFailures(0); 95 } 96 97 /** 98 * Creates a {@link DBCustomAudienceBackgroundFetchData} object using the builder. 99 * 100 * <p>Required for Room SQLite integration. 101 */ 102 @NonNull create( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name, @NonNull Uri dailyUpdateUri, @NonNull Instant eligibleUpdateTime, long numValidationFailures, long numTimeoutFailures)103 public static DBCustomAudienceBackgroundFetchData create( 104 @NonNull String owner, 105 @NonNull AdTechIdentifier buyer, 106 @NonNull String name, 107 @NonNull Uri dailyUpdateUri, 108 @NonNull Instant eligibleUpdateTime, 109 long numValidationFailures, 110 long numTimeoutFailures) { 111 return builder() 112 .setOwner(owner) 113 .setBuyer(buyer) 114 .setName(name) 115 .setDailyUpdateUri(dailyUpdateUri) 116 .setEligibleUpdateTime(eligibleUpdateTime) 117 .setNumValidationFailures(numValidationFailures) 118 .setNumTimeoutFailures(numTimeoutFailures) 119 .build(); 120 } 121 122 /** 123 * Computes the next eligible update time, given the most recent successful update time and 124 * flags. 125 * 126 * <p>This method is split out for testing because testing with P/H flags in a static method 127 * requires non-trivial permissions in test applications. 128 */ 129 @VisibleForTesting 130 @NonNull computeNextEligibleUpdateTimeAfterSuccessfulUpdate( @onNull Instant successfulUpdateTime, @NonNull Flags flags)131 public static Instant computeNextEligibleUpdateTimeAfterSuccessfulUpdate( 132 @NonNull Instant successfulUpdateTime, @NonNull Flags flags) { 133 Objects.requireNonNull(successfulUpdateTime); 134 Objects.requireNonNull(flags); 135 136 // Successful updates are next eligible in base interval (one day) + jitter 137 // TODO(b/221861706): Implement jitter 138 return successfulUpdateTime.plusSeconds( 139 flags.getFledgeBackgroundFetchEligibleUpdateBaseIntervalS()); 140 } 141 142 /** Computes the next eligible update time, given the most recent successful update time. */ 143 @NonNull computeNextEligibleUpdateTimeAfterSuccessfulUpdate( @onNull Instant successfulUpdateTime)144 public static Instant computeNextEligibleUpdateTimeAfterSuccessfulUpdate( 145 @NonNull Instant successfulUpdateTime) { 146 return computeNextEligibleUpdateTimeAfterSuccessfulUpdate( 147 successfulUpdateTime, FlagsFactory.getFlags()); 148 } 149 150 /** 151 * Creates a copy of the current object with an updated failure count based on the input failure 152 * type. 153 */ 154 @NonNull copyWithUpdatableData( @onNull CustomAudienceUpdatableData updatableData)155 public final DBCustomAudienceBackgroundFetchData copyWithUpdatableData( 156 @NonNull CustomAudienceUpdatableData updatableData) { 157 // Create a builder with a full copy of the current object 158 DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder = 159 builder() 160 .setOwner(getOwner()) 161 .setBuyer(getBuyer()) 162 .setName(getName()) 163 .setDailyUpdateUri(getDailyUpdateUri()) 164 .setEligibleUpdateTime(getEligibleUpdateTime()) 165 .setNumValidationFailures(getNumValidationFailures()) 166 .setNumTimeoutFailures(getNumTimeoutFailures()); 167 168 if (updatableData.getContainsSuccessfulUpdate()) { 169 fetchDataBuilder.setEligibleUpdateTime( 170 computeNextEligibleUpdateTimeAfterSuccessfulUpdate( 171 updatableData.getAttemptedUpdateTime())); 172 173 // Reset all failure counts on any successful update 174 fetchDataBuilder.setNumValidationFailures(0).setNumTimeoutFailures(0); 175 } else { 176 switch (updatableData.getInitialUpdateResult()) { 177 case SUCCESS: 178 // This success result is only the result set prior to syntax validation of the 179 // response, so an error must have occurred during data validation 180 // INTENTIONAL FALLTHROUGH 181 case RESPONSE_VALIDATION_FAILURE: 182 fetchDataBuilder.setNumValidationFailures(getNumValidationFailures() + 1); 183 break; 184 case NETWORK_FAILURE: 185 // TODO(b/221861706): Consider differentiating timeout failures for fairness 186 // TODO(b/237342352): Consolidate timeout failures if they don't need to be 187 // distinguished 188 // INTENTIONAL FALLTHROUGH 189 case NETWORK_READ_TIMEOUT_FAILURE: 190 fetchDataBuilder.setNumTimeoutFailures(getNumTimeoutFailures() + 1); 191 break; 192 case K_ANON_FAILURE: 193 // TODO(b/234884352): Implement k-anon check 194 // INTENTIONAL FALLTHROUGH 195 case UNKNOWN: 196 // Treat this as a benign failure, so we can just try again 197 break; 198 } 199 200 // TODO(b/221861706): Decide whether this custom audience is delinquent and set its next 201 // eligible update time 202 // TODO(b/221861706): Implement jitter for delinquent updates 203 204 // Non-delinquent failed updates are immediately eligible to be updated in the next job 205 fetchDataBuilder.setEligibleUpdateTime(getEligibleUpdateTime()); 206 } 207 208 return fetchDataBuilder.build(); 209 } 210 211 /** Builder class for a {@link DBCustomAudienceBackgroundFetchData} object. */ 212 @AutoValue.Builder 213 public abstract static class Builder { 214 /** Sets the owner package name for the custom audience. */ 215 @NonNull setOwner(@onNull String value)216 public abstract Builder setOwner(@NonNull String value); 217 218 /** Sets the buyer for the custom audience. */ 219 @NonNull setBuyer(@onNull AdTechIdentifier value)220 public abstract Builder setBuyer(@NonNull AdTechIdentifier value); 221 222 /** Sets the name for the custom audience. */ 223 @NonNull setName(@onNull String value)224 public abstract Builder setName(@NonNull String value); 225 226 /** Sets the daily update URI for the custom audience. */ 227 @NonNull setDailyUpdateUri(@onNull Uri value)228 public abstract Builder setDailyUpdateUri(@NonNull Uri value); 229 230 /** Sets the time after which the custom audience will be eligible for update. */ 231 @NonNull setEligibleUpdateTime(@onNull Instant value)232 public abstract Builder setEligibleUpdateTime(@NonNull Instant value); 233 234 /** 235 * Sets the number of failures due to response validation for the custom audience since the 236 * last successful update. 237 */ 238 @NonNull setNumValidationFailures(long value)239 public abstract Builder setNumValidationFailures(long value); 240 241 /** 242 * Sets the number of failures due to fetch timeout for the custom audience since the last 243 * successful update. 244 */ 245 @NonNull setNumTimeoutFailures(long value)246 public abstract Builder setNumTimeoutFailures(long value); 247 248 /** 249 * Builds the {@link DBCustomAudienceBackgroundFetchData} object and returns it. 250 * 251 * <p>Note that AutoValue doesn't by itself do any validation, so splitting the builder with 252 * a manual verification is recommended. See go/autovalue/builders-howto#validate for more 253 * information. 254 */ 255 @NonNull autoValueBuild()256 protected abstract DBCustomAudienceBackgroundFetchData autoValueBuild(); 257 258 /** 259 * Builds, validates, and returns the {@link DBCustomAudienceBackgroundFetchData} object. 260 */ 261 @NonNull build()262 public final DBCustomAudienceBackgroundFetchData build() { 263 DBCustomAudienceBackgroundFetchData fetchData = autoValueBuild(); 264 265 // Fields marked @NonNull are already validated by AutoValue 266 Preconditions.checkArgument( 267 fetchData.getNumValidationFailures() >= 0 268 && fetchData.getNumTimeoutFailures() >= 0, 269 "Update failure count must be non-negative"); 270 271 return fetchData; 272 } 273 } 274 } 275