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 /** 90 * @return if the custom audience is considered debuggable (i.e. was created by an app where 91 * android:debuggable="true" while developer options are enabled. 92 */ 93 @AutoValue.CopyAnnotations 94 @ColumnInfo(name = "is_debuggable", defaultValue = "false") getIsDebuggable()95 public abstract boolean getIsDebuggable(); 96 97 /** @return an AutoValue builder for a {@link DBCustomAudienceBackgroundFetchData} object */ 98 @NonNull builder()99 public static DBCustomAudienceBackgroundFetchData.Builder builder() { 100 return new AutoValue_DBCustomAudienceBackgroundFetchData.Builder() 101 .setNumValidationFailures(0) 102 .setNumTimeoutFailures(0) 103 .setIsDebuggable(false); 104 } 105 106 /** 107 * Creates a {@link DBCustomAudienceBackgroundFetchData} object using the builder. 108 * 109 * <p>Required for Room SQLite integration. 110 */ 111 @NonNull create( @onNull String owner, @NonNull AdTechIdentifier buyer, @NonNull String name, @NonNull Uri dailyUpdateUri, @NonNull Instant eligibleUpdateTime, long numValidationFailures, long numTimeoutFailures, boolean debuggable)112 public static DBCustomAudienceBackgroundFetchData create( 113 @NonNull String owner, 114 @NonNull AdTechIdentifier buyer, 115 @NonNull String name, 116 @NonNull Uri dailyUpdateUri, 117 @NonNull Instant eligibleUpdateTime, 118 long numValidationFailures, 119 long numTimeoutFailures, 120 boolean debuggable) { 121 return builder() 122 .setOwner(owner) 123 .setBuyer(buyer) 124 .setName(name) 125 .setDailyUpdateUri(dailyUpdateUri) 126 .setEligibleUpdateTime(eligibleUpdateTime) 127 .setNumValidationFailures(numValidationFailures) 128 .setNumTimeoutFailures(numTimeoutFailures) 129 .setIsDebuggable(debuggable) 130 .build(); 131 } 132 133 /** 134 * Computes the next eligible update time, given the most recent successful update time and 135 * flags. 136 * 137 * <p>This method is split out for testing because testing with P/H flags in a static method 138 * requires non-trivial permissions in test applications. 139 */ 140 @VisibleForTesting 141 @NonNull computeNextEligibleUpdateTimeAfterSuccessfulUpdate( @onNull Instant successfulUpdateTime, @NonNull Flags flags)142 public static Instant computeNextEligibleUpdateTimeAfterSuccessfulUpdate( 143 @NonNull Instant successfulUpdateTime, @NonNull Flags flags) { 144 Objects.requireNonNull(successfulUpdateTime); 145 Objects.requireNonNull(flags); 146 147 // Successful updates are next eligible in base interval (one day) + jitter 148 // TODO(b/221861706): Implement jitter 149 return successfulUpdateTime.plusSeconds( 150 flags.getFledgeBackgroundFetchEligibleUpdateBaseIntervalS()); 151 } 152 153 /** Computes the next eligible update time, given the most recent successful update time. */ 154 @NonNull computeNextEligibleUpdateTimeAfterSuccessfulUpdate( @onNull Instant successfulUpdateTime)155 public static Instant computeNextEligibleUpdateTimeAfterSuccessfulUpdate( 156 @NonNull Instant successfulUpdateTime) { 157 return computeNextEligibleUpdateTimeAfterSuccessfulUpdate( 158 successfulUpdateTime, FlagsFactory.getFlags()); 159 } 160 161 /** 162 * Creates a copy of the current object with an updated failure count based on the input failure 163 * type. 164 */ 165 @NonNull copyWithUpdatableData( @onNull CustomAudienceUpdatableData updatableData)166 public final DBCustomAudienceBackgroundFetchData copyWithUpdatableData( 167 @NonNull CustomAudienceUpdatableData updatableData) { 168 // Create a builder with a full copy of the current object 169 DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder = 170 builder() 171 .setOwner(getOwner()) 172 .setBuyer(getBuyer()) 173 .setName(getName()) 174 .setDailyUpdateUri(getDailyUpdateUri()) 175 .setEligibleUpdateTime(getEligibleUpdateTime()) 176 .setNumValidationFailures(getNumValidationFailures()) 177 .setNumTimeoutFailures(getNumTimeoutFailures()) 178 .setIsDebuggable(getIsDebuggable()); 179 180 if (updatableData.getContainsSuccessfulUpdate()) { 181 fetchDataBuilder.setEligibleUpdateTime( 182 computeNextEligibleUpdateTimeAfterSuccessfulUpdate( 183 updatableData.getAttemptedUpdateTime())); 184 185 // Reset all failure counts on any successful update 186 fetchDataBuilder.setNumValidationFailures(0).setNumTimeoutFailures(0); 187 } else { 188 switch (updatableData.getInitialUpdateResult()) { 189 case SUCCESS: 190 // This success result is only the result set prior to syntax validation of the 191 // response, so an error must have occurred during data validation 192 // INTENTIONAL FALLTHROUGH 193 case RESPONSE_VALIDATION_FAILURE: 194 fetchDataBuilder.setNumValidationFailures(getNumValidationFailures() + 1); 195 break; 196 case NETWORK_FAILURE: 197 // TODO(b/221861706): Consider differentiating timeout failures for fairness 198 // TODO(b/237342352): Consolidate timeout failures if they don't need to be 199 // distinguished 200 // INTENTIONAL FALLTHROUGH 201 case NETWORK_READ_TIMEOUT_FAILURE: 202 fetchDataBuilder.setNumTimeoutFailures(getNumTimeoutFailures() + 1); 203 break; 204 case K_ANON_FAILURE: 205 // TODO(b/234884352): Implement k-anon check 206 // INTENTIONAL FALLTHROUGH 207 case UNKNOWN: 208 // Treat this as a benign failure, so we can just try again 209 break; 210 } 211 212 // TODO(b/221861706): Decide whether this custom audience is delinquent and set its next 213 // eligible update time 214 // TODO(b/221861706): Implement jitter for delinquent updates 215 216 // Non-delinquent failed updates are immediately eligible to be updated in the next job 217 fetchDataBuilder.setEligibleUpdateTime(getEligibleUpdateTime()); 218 } 219 220 return fetchDataBuilder.build(); 221 } 222 223 /** Builder class for a {@link DBCustomAudienceBackgroundFetchData} object. */ 224 @AutoValue.Builder 225 public abstract static class Builder { 226 /** Sets the owner package name for the custom audience. */ 227 @NonNull setOwner(@onNull String value)228 public abstract Builder setOwner(@NonNull String value); 229 230 /** Sets the buyer for the custom audience. */ 231 @NonNull setBuyer(@onNull AdTechIdentifier value)232 public abstract Builder setBuyer(@NonNull AdTechIdentifier value); 233 234 /** Sets the name for the custom audience. */ 235 @NonNull setName(@onNull String value)236 public abstract Builder setName(@NonNull String value); 237 238 /** Sets the daily update URI for the custom audience. */ 239 @NonNull setDailyUpdateUri(@onNull Uri value)240 public abstract Builder setDailyUpdateUri(@NonNull Uri value); 241 242 /** Sets the time after which the custom audience will be eligible for update. */ 243 @NonNull setEligibleUpdateTime(@onNull Instant value)244 public abstract Builder setEligibleUpdateTime(@NonNull Instant value); 245 246 /** 247 * Sets the number of failures due to response validation for the custom audience since the 248 * last successful update. 249 */ 250 @NonNull setNumValidationFailures(long value)251 public abstract Builder setNumValidationFailures(long value); 252 253 /** 254 * Sets the number of failures due to fetch timeout for the custom audience since the last 255 * successful update. 256 */ 257 @NonNull setNumTimeoutFailures(long value)258 public abstract Builder setNumTimeoutFailures(long value); 259 260 /** Sets if the custom audience was created in a debuggable context. */ 261 @NonNull setIsDebuggable(boolean value)262 public abstract Builder setIsDebuggable(boolean value); 263 264 /** 265 * Builds the {@link DBCustomAudienceBackgroundFetchData} object and returns it. 266 * 267 * <p>Note that AutoValue doesn't by itself do any validation, so splitting the builder with 268 * a manual verification is recommended. See go/autovalue/builders-howto#validate for more 269 * information. 270 */ 271 @NonNull autoValueBuild()272 protected abstract DBCustomAudienceBackgroundFetchData autoValueBuild(); 273 274 /** 275 * Builds, validates, and returns the {@link DBCustomAudienceBackgroundFetchData} object. 276 */ 277 @NonNull build()278 public final DBCustomAudienceBackgroundFetchData build() { 279 DBCustomAudienceBackgroundFetchData fetchData = autoValueBuild(); 280 281 // Fields marked @NonNull are already validated by AutoValue 282 Preconditions.checkArgument( 283 fetchData.getNumValidationFailures() >= 0 284 && fetchData.getNumTimeoutFailures() >= 0, 285 "Update failure count must be non-negative"); 286 287 return fetchData; 288 } 289 } 290 } 291