• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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