1 /*
2  * Copyright (C) 2023 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.service.customaudience;
18 
19 import static android.adservices.common.AdServicesStatusUtils.ILLEGAL_STATE_BACKGROUND_CALLER_ERROR_MESSAGE;
20 import static android.adservices.common.AdServicesStatusUtils.RATE_LIMIT_REACHED_ERROR_MESSAGE;
21 import static android.adservices.common.AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE;
22 import static android.adservices.common.AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE;
23 import static android.adservices.common.AdServicesStatusUtils.STATUS_BACKGROUND_CALLER;
24 import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLBACK_SHUTDOWN;
25 import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
26 import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
27 import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
28 import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_OBJECT;
29 import static android.adservices.common.AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
30 import static android.adservices.common.AdServicesStatusUtils.STATUS_SERVER_RATE_LIMIT_REACHED;
31 import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
32 import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
33 import static android.adservices.common.AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
34 import static android.adservices.common.CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI;
35 import static android.adservices.common.CommonFixture.TEST_PACKAGE_NAME;
36 import static android.adservices.common.CommonFixture.VALID_BUYER_1;
37 import static android.adservices.common.CommonFixture.VALID_BUYER_2;
38 import static android.adservices.customaudience.CustomAudience.FLAG_AUCTION_SERVER_REQUEST_OMIT_ADS;
39 import static android.adservices.customaudience.CustomAudienceFixture.CUSTOM_AUDIENCE_MAX_ACTIVATION_DELAY_IN;
40 import static android.adservices.customaudience.CustomAudienceFixture.INVALID_BEYOND_MAX_EXPIRATION_TIME;
41 import static android.adservices.customaudience.CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME;
42 import static android.adservices.customaudience.CustomAudienceFixture.VALID_EXPIRATION_TIME;
43 import static android.adservices.customaudience.CustomAudienceFixture.VALID_NAME;
44 import static android.adservices.customaudience.CustomAudienceFixture.VALID_OWNER;
45 import static android.adservices.customaudience.CustomAudienceFixture.VALID_USER_BIDDING_SIGNALS;
46 import static android.adservices.customaudience.CustomAudienceFixture.getValidDailyUpdateUriByBuyer;
47 import static android.adservices.exceptions.RetryableAdServicesNetworkException.DEFAULT_RETRY_AFTER_VALUE;
48 
49 import static com.android.adservices.service.common.AdTechUriValidator.IDENTIFIER_AND_URI_ARE_INCONSISTENT;
50 import static com.android.adservices.service.common.Validator.EXCEPTION_MESSAGE_FORMAT;
51 import static com.android.adservices.service.common.ValidatorUtil.AD_TECH_ROLE_BUYER;
52 import static com.android.adservices.service.customaudience.CustomAudienceBlob.BUYER_KEY;
53 import static com.android.adservices.service.customaudience.CustomAudienceBlob.OWNER_KEY;
54 import static com.android.adservices.service.customaudience.CustomAudienceBlobFixture.addDailyUpdateUri;
55 import static com.android.adservices.service.customaudience.CustomAudienceBlobFixture.addName;
56 import static com.android.adservices.service.customaudience.CustomAudienceBlobValidator.CLASS_NAME;
57 import static com.android.adservices.service.customaudience.CustomAudienceExpirationTimeValidatorTest.CUSTOM_AUDIENCE_MAX_EXPIRE_IN;
58 import static com.android.adservices.service.customaudience.CustomAudienceFieldSizeValidator.VIOLATION_NAME_TOO_LONG;
59 import static com.android.adservices.service.customaudience.CustomAudienceFieldSizeValidator.VIOLATION_USER_BIDDING_SIGNAL_TOO_BIG;
60 import static com.android.adservices.service.customaudience.CustomAudienceQuantityChecker.CUSTOM_AUDIENCE_QUANTITY_CHECK_FAILED;
61 import static com.android.adservices.service.customaudience.CustomAudienceQuantityChecker.THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_DEVICE_HAD_REACHED;
62 import static com.android.adservices.service.customaudience.CustomAudienceQuantityChecker.THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_OWNER_HAD_REACHED;
63 import static com.android.adservices.service.customaudience.CustomAudienceTimestampValidator.VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE;
64 import static com.android.adservices.service.customaudience.CustomAudienceTimestampValidator.VIOLATION_EXPIRE_AFTER_MAX_EXPIRE_TIME;
65 import static com.android.adservices.service.customaudience.CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_ACTIVATION;
66 import static com.android.adservices.service.customaudience.CustomAudienceUpdatableDataReader.ADS_KEY;
67 import static com.android.adservices.service.customaudience.CustomAudienceUpdatableDataReader.USER_BIDDING_SIGNALS_KEY;
68 import static com.android.adservices.service.customaudience.CustomAudienceValidator.DAILY_UPDATE_URI_FIELD_NAME;
69 import static com.android.adservices.service.customaudience.FetchCustomAudienceFixture.getFullJsonResponseStringWithInvalidAdRenderId;
70 import static com.android.adservices.service.customaudience.FetchCustomAudienceFixture.getFullSuccessfulJsonResponse;
71 import static com.android.adservices.service.customaudience.FetchCustomAudienceFixture.getFullSuccessfulJsonResponseString;
72 import static com.android.adservices.service.customaudience.FetchCustomAudienceFixture.getFullSuccessfulJsonResponseStringWithAdRenderId;
73 import static com.android.adservices.service.customaudience.FetchCustomAudienceImpl.FUSED_CUSTOM_AUDIENCE_EXCEEDS_SIZE_LIMIT_MESSAGE;
74 import static com.android.adservices.service.customaudience.FetchCustomAudienceImpl.FUSED_CUSTOM_AUDIENCE_INCOMPLETE_MESSAGE;
75 import static com.android.adservices.service.customaudience.FetchCustomAudienceImpl.REQUEST_CUSTOM_HEADER_EXCEEDS_SIZE_LIMIT_MESSAGE;
76 import static com.android.adservices.service.customaudience.FetchCustomAudienceReader.ACTIVATION_TIME_KEY;
77 import static com.android.adservices.service.customaudience.FetchCustomAudienceReader.DAILY_UPDATE_URI_KEY;
78 import static com.android.adservices.service.customaudience.FetchCustomAudienceReader.EXPIRATION_TIME_KEY;
79 import static com.android.adservices.service.customaudience.FetchCustomAudienceReader.NAME_KEY;
80 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__FETCH_AND_JOIN_CUSTOM_AUDIENCE;
81 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
82 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
83 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
84 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
85 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
86 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
87 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
88 
89 import static org.junit.Assert.assertEquals;
90 import static org.junit.Assert.assertFalse;
91 import static org.junit.Assert.assertTrue;
92 import static org.mockito.ArgumentMatchers.any;
93 import static org.mockito.ArgumentMatchers.anyBoolean;
94 import static org.mockito.ArgumentMatchers.anyLong;
95 import static org.mockito.Mockito.times;
96 
97 import android.adservices.common.AdTechIdentifier;
98 import android.adservices.common.CallingAppUidSupplierProcessImpl;
99 import android.adservices.common.CommonFixture;
100 import android.adservices.common.FledgeErrorResponse;
101 import android.adservices.customaudience.CustomAudienceFixture;
102 import android.adservices.customaudience.FetchAndJoinCustomAudienceCallback;
103 import android.adservices.customaudience.FetchAndJoinCustomAudienceInput;
104 import android.adservices.http.MockWebServerRule;
105 import android.net.Uri;
106 import android.os.LimitExceededException;
107 import android.os.Process;
108 import android.os.RemoteException;
109 
110 import com.android.adservices.LoggerFactory;
111 import com.android.adservices.MockWebServerRuleFactory;
112 import com.android.adservices.concurrency.AdServicesExecutors;
113 import com.android.adservices.data.adselection.AppInstallDao;
114 import com.android.adservices.data.adselection.FrequencyCapDao;
115 import com.android.adservices.data.customaudience.AdDataConversionStrategy;
116 import com.android.adservices.data.customaudience.AdDataConversionStrategyFactory;
117 import com.android.adservices.data.customaudience.CustomAudienceDao;
118 import com.android.adservices.data.customaudience.CustomAudienceStats;
119 import com.android.adservices.data.customaudience.DBCustomAudience;
120 import com.android.adservices.data.customaudience.DBCustomAudienceQuarantine;
121 import com.android.adservices.service.Flags;
122 import com.android.adservices.service.adselection.AdFilteringFeatureFactory;
123 import com.android.adservices.service.common.AdRenderIdValidator;
124 import com.android.adservices.service.common.AppImportanceFilter;
125 import com.android.adservices.service.common.CustomAudienceServiceFilter;
126 import com.android.adservices.service.common.FledgeAllowListsFilter;
127 import com.android.adservices.service.common.FledgeAuthorizationFilter;
128 import com.android.adservices.service.common.Throttler;
129 import com.android.adservices.service.common.cache.CacheProviderFactory;
130 import com.android.adservices.service.common.httpclient.AdServicesHttpsClient;
131 import com.android.adservices.service.consent.ConsentManager;
132 import com.android.adservices.service.devapi.DevContext;
133 import com.android.adservices.service.stats.AdServicesLogger;
134 import com.android.adservices.service.stats.AdServicesLoggerImpl;
135 import com.android.adservices.shared.testing.SdkLevelSupportRule;
136 import com.android.dx.mockito.inline.extended.ExtendedMockito;
137 
138 import com.google.common.collect.ImmutableList;
139 import com.google.common.util.concurrent.MoreExecutors;
140 import com.google.mockwebserver.Dispatcher;
141 import com.google.mockwebserver.MockResponse;
142 import com.google.mockwebserver.MockWebServer;
143 import com.google.mockwebserver.RecordedRequest;
144 
145 import org.json.JSONObject;
146 import org.junit.After;
147 import org.junit.Assert;
148 import org.junit.Before;
149 import org.junit.Rule;
150 import org.junit.Test;
151 import org.junit.runner.RunWith;
152 import org.mockito.ArgumentCaptor;
153 import org.mockito.Mock;
154 import org.mockito.MockitoSession;
155 import org.mockito.Spy;
156 import org.mockito.junit.MockitoJUnitRunner;
157 import org.mockito.quality.Strictness;
158 import org.mockito.stubbing.Answer;
159 
160 import java.nio.charset.StandardCharsets;
161 import java.time.Clock;
162 import java.util.List;
163 import java.util.Locale;
164 import java.util.concurrent.CountDownLatch;
165 import java.util.concurrent.ExecutorService;
166 import java.util.concurrent.atomic.AtomicInteger;
167 
168 @RunWith(MockitoJUnitRunner.class)
169 public class FetchCustomAudienceImplTest {
170     private static final int API_NAME =
171             AD_SERVICES_API_CALLED__API_NAME__FETCH_AND_JOIN_CUSTOM_AUDIENCE;
172     private static final ExecutorService DIRECT_EXECUTOR = MoreExecutors.newDirectExecutorService();
173     private static final AdDataConversionStrategy AD_DATA_CONVERSION_STRATEGY =
174             AdDataConversionStrategyFactory.getAdDataConversionStrategy(true, true, true);
175     private static final Clock CLOCK = CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI;
176     private final AdServicesLogger mAdServicesLoggerMock =
177             ExtendedMockito.mock(AdServicesLoggerImpl.class);
178     @Mock private CustomAudienceServiceFilter mCustomAudienceServiceFilterMock;
179     @Mock private CustomAudienceDao mCustomAudienceDaoMock;
180     @Mock private AppInstallDao mAppInstallDaoMock;
181     @Mock private FrequencyCapDao mFrequencyCapDaoMock;
182     private final AdRenderIdValidator mAdRenderIdValidator =
183             AdRenderIdValidator.createEnabledInstance(100);
184     private AdFilteringFeatureFactory mAdFilteringFeatureFactory;
185 
186     @Spy
187     private final AdServicesHttpsClient mHttpClientSpy =
188             new AdServicesHttpsClient(
189                     AdServicesExecutors.getBlockingExecutor(),
190                     CacheProviderFactory.createNoOpCache());
191 
192     @Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
193     private static final AdTechIdentifier BUYER = AdTechIdentifier.fromString("localhost");
194     private int mCallingAppUid;
195     private Uri mFetchUri;
196     private FetchCustomAudienceImpl mFetchCustomAudienceImpl;
197     private FetchAndJoinCustomAudienceInput.Builder mInputBuilder;
198     private MockitoSession mStaticMockSession = null;
199 
200     @Rule(order = 0)
201     public final SdkLevelSupportRule sdkLevel = SdkLevelSupportRule.forAtLeastS();
202 
203     @Before
setup()204     public void setup() {
205         mStaticMockSession =
206                 ExtendedMockito.mockitoSession()
207                         .strictness(Strictness.LENIENT)
208                         .initMocks(this)
209                         .mockStatic(ConsentManager.class)
210                         .startMocking();
211 
212         mCallingAppUid = CallingAppUidSupplierProcessImpl.create().getCallingAppUid();
213 
214         mFetchUri = mMockWebServerRule.uriForPath("/fetch");
215 
216         mInputBuilder =
217                 new FetchAndJoinCustomAudienceInput.Builder(mFetchUri, VALID_OWNER)
218                         .setName(CustomAudienceFixture.VALID_NAME)
219                         .setActivationTime(CustomAudienceFixture.VALID_ACTIVATION_TIME)
220                         .setExpirationTime(VALID_EXPIRATION_TIME)
221                         .setUserBiddingSignals(CustomAudienceFixture.VALID_USER_BIDDING_SIGNALS);
222 
223         Flags flags = new FetchCustomAudienceFlags();
224 
225         mAdFilteringFeatureFactory =
226                 new AdFilteringFeatureFactory(mAppInstallDaoMock, mFrequencyCapDaoMock, flags);
227 
228         mFetchCustomAudienceImpl = getImplWithFlags(flags);
229 
230         doReturn(BUYER)
231                 .when(mCustomAudienceServiceFilterMock)
232                 .filterRequestAndExtractIdentifier(
233                         mFetchUri,
234                         VALID_OWNER,
235                         false,
236                         true,
237                         true,
238                         Process.myUid(),
239                         API_NAME,
240                         Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
241                         DevContext.createForDevOptionsDisabled());
242         doReturn(BUYER)
243                 .when(mCustomAudienceServiceFilterMock)
244                 .filterRequestAndExtractIdentifier(
245                         mFetchUri,
246                         VALID_OWNER,
247                         false,
248                         true,
249                         true,
250                         Process.myUid(),
251                         API_NAME,
252                         Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
253                         DevContext.builder().setDevOptionsEnabled(true).build());
254         doReturn(
255                         CustomAudienceStats.builder()
256                                 .setTotalCustomAudienceCount(1)
257                                 .setBuyer(BUYER)
258                                 .setOwner(VALID_OWNER)
259                                 .setPerOwnerCustomAudienceCount(1)
260                                 .setPerBuyerCustomAudienceCount(1)
261                                 .setTotalBuyerCount(1)
262                                 .setTotalOwnerCount(1)
263                                 .build())
264                 .when(mCustomAudienceDaoMock)
265                 .getCustomAudienceStats(eq(VALID_OWNER));
266 
267         doReturn(false)
268                 .when(mCustomAudienceDaoMock)
269                 .doesCustomAudienceQuarantineExist(VALID_OWNER, BUYER);
270     }
271 
272     @After
tearDown()273     public void tearDown() {
274         if (mStaticMockSession != null) {
275             mStaticMockSession.finishMocking();
276         }
277     }
278 
279     @Test
testImpl_disabled()280     public void testImpl_disabled() throws Exception {
281         // Use flag value to disable the API.
282         mFetchCustomAudienceImpl =
283                 getImplWithFlags(
284                         new FetchCustomAudienceFlags() {
285                             @Override
286                             public boolean getFledgeFetchCustomAudienceEnabled() {
287                                 return false;
288                             }
289                         });
290 
291         MockWebServer mockWebServer =
292                 mMockWebServerRule.startMockWebServer(
293                         List.of(
294                                 new MockResponse()
295                                         .setBody(
296                                                 getFullSuccessfulJsonResponseString(
297                                                         VALID_BUYER_1))));
298 
299         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
300 
301         assertEquals(0, mockWebServer.getRequestCount());
302         assertFalse(callback.mIsSuccess);
303         verify(mAdServicesLoggerMock)
304                 .logFledgeApiCallStats(
305                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INTERNAL_ERROR), anyInt());
306     }
307 
308     @Test
testImpl_invalidPackageName_throws()309     public void testImpl_invalidPackageName_throws() throws Exception {
310         String otherPackageName = VALID_OWNER + "incorrectPackage";
311 
312         doThrow(new FledgeAuthorizationFilter.CallerMismatchException())
313                 .when(mCustomAudienceServiceFilterMock)
314                 .filterRequestAndExtractIdentifier(
315                         mFetchUri,
316                         otherPackageName,
317                         false,
318                         true,
319                         true,
320                         Process.myUid(),
321                         API_NAME,
322                         Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
323                         DevContext.createForDevOptionsDisabled());
324 
325         FetchCustomAudienceTestCallback callback =
326                 callFetchCustomAudience(
327                         mInputBuilder.setCallerPackageName(otherPackageName).build());
328 
329         assertFalse(callback.mIsSuccess);
330         assertEquals(STATUS_UNAUTHORIZED, callback.mFledgeErrorResponse.getStatusCode());
331         assertEquals(
332                 SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE,
333                 callback.mFledgeErrorResponse.getErrorMessage());
334 
335         // Confirm a duplicate log entry does not exist.
336         // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
337         verify(mAdServicesLoggerMock, never())
338                 .logFledgeApiCallStats(
339                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_UNAUTHORIZED), anyInt());
340     }
341 
342     @Test
testImpl_throttled_throws()343     public void testImpl_throttled_throws() throws Exception {
344         doThrow(new LimitExceededException(RATE_LIMIT_REACHED_ERROR_MESSAGE))
345                 .when(mCustomAudienceServiceFilterMock)
346                 .filterRequestAndExtractIdentifier(
347                         mFetchUri,
348                         VALID_OWNER,
349                         false,
350                         true,
351                         true,
352                         Process.myUid(),
353                         API_NAME,
354                         Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
355                         DevContext.createForDevOptionsDisabled());
356 
357         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
358 
359         assertFalse(callback.mIsSuccess);
360         assertEquals(STATUS_RATE_LIMIT_REACHED, callback.mFledgeErrorResponse.getStatusCode());
361         assertEquals(
362                 RATE_LIMIT_REACHED_ERROR_MESSAGE, callback.mFledgeErrorResponse.getErrorMessage());
363 
364         // Confirm a duplicate log entry does not exist.
365         // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
366         verify(mAdServicesLoggerMock, never())
367                 .logFledgeApiCallStats(
368                         eq(API_NAME),
369                         eq(TEST_PACKAGE_NAME),
370                         eq(STATUS_RATE_LIMIT_REACHED),
371                         anyInt());
372     }
373 
374     @Test
testImpl_failedForegroundCheck_throws()375     public void testImpl_failedForegroundCheck_throws() throws Exception {
376         doThrow(new AppImportanceFilter.WrongCallingApplicationStateException())
377                 .when(mCustomAudienceServiceFilterMock)
378                 .filterRequestAndExtractIdentifier(
379                         mFetchUri,
380                         VALID_OWNER,
381                         false,
382                         true,
383                         true,
384                         Process.myUid(),
385                         API_NAME,
386                         Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
387                         DevContext.createForDevOptionsDisabled());
388 
389         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
390 
391         assertFalse(callback.mIsSuccess);
392         assertEquals(STATUS_BACKGROUND_CALLER, callback.mFledgeErrorResponse.getStatusCode());
393         assertEquals(
394                 ILLEGAL_STATE_BACKGROUND_CALLER_ERROR_MESSAGE,
395                 callback.mFledgeErrorResponse.getErrorMessage());
396 
397         // Confirm a duplicate log entry does not exist.
398         // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
399         verify(mAdServicesLoggerMock, never())
400                 .logFledgeApiCallStats(
401                         eq(API_NAME),
402                         eq(TEST_PACKAGE_NAME),
403                         eq(STATUS_BACKGROUND_CALLER),
404                         anyInt());
405     }
406 
407     @Test
testImpl_failedEnrollmentCheck_throws()408     public void testImpl_failedEnrollmentCheck_throws() throws Exception {
409         doThrow(new FledgeAuthorizationFilter.AdTechNotAllowedException())
410                 .when(mCustomAudienceServiceFilterMock)
411                 .filterRequestAndExtractIdentifier(
412                         mFetchUri,
413                         VALID_OWNER,
414                         false,
415                         true,
416                         true,
417                         Process.myUid(),
418                         API_NAME,
419                         Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
420                         DevContext.createForDevOptionsDisabled());
421 
422         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
423 
424         assertFalse(callback.mIsSuccess);
425         assertEquals(STATUS_CALLER_NOT_ALLOWED, callback.mFledgeErrorResponse.getStatusCode());
426         assertEquals(
427                 SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE,
428                 callback.mFledgeErrorResponse.getErrorMessage());
429 
430         // Confirm a duplicate log entry does not exist.
431         // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
432         verify(mAdServicesLoggerMock, never())
433                 .logFledgeApiCallStats(
434                         eq(API_NAME),
435                         eq(TEST_PACKAGE_NAME),
436                         eq(STATUS_CALLER_NOT_ALLOWED),
437                         anyInt());
438     }
439 
440     @Test
testImpl_appCannotUsePPAPI_throws()441     public void testImpl_appCannotUsePPAPI_throws() throws Exception {
442         doThrow(new FledgeAllowListsFilter.AppNotAllowedException())
443                 .when(mCustomAudienceServiceFilterMock)
444                 .filterRequestAndExtractIdentifier(
445                         mFetchUri,
446                         VALID_OWNER,
447                         false,
448                         true,
449                         true,
450                         Process.myUid(),
451                         API_NAME,
452                         Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
453                         DevContext.createForDevOptionsDisabled());
454 
455         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
456 
457         assertFalse(callback.mIsSuccess);
458         assertEquals(STATUS_CALLER_NOT_ALLOWED, callback.mFledgeErrorResponse.getStatusCode());
459         assertEquals(
460                 SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE,
461                 callback.mFledgeErrorResponse.getErrorMessage());
462 
463         // Confirm a duplicate log entry does not exist.
464         // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
465         verify(mAdServicesLoggerMock, never())
466                 .logFledgeApiCallStats(
467                         eq(API_NAME),
468                         eq(TEST_PACKAGE_NAME),
469                         eq(STATUS_CALLER_NOT_ALLOWED),
470                         anyInt());
471     }
472 
473     @Test
testImpl_revokedConsent_failsSilently()474     public void testImpl_revokedConsent_failsSilently() throws Exception {
475         doThrow(new ConsentManager.RevokedConsentException())
476                 .when(mCustomAudienceServiceFilterMock)
477                 .filterRequestAndExtractIdentifier(
478                         mFetchUri,
479                         VALID_OWNER,
480                         false,
481                         true,
482                         true,
483                         Process.myUid(),
484                         API_NAME,
485                         Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
486                         DevContext.createForDevOptionsDisabled());
487 
488         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
489 
490         assertTrue(callback.mIsSuccess);
491 
492         // Confirm a duplicate log entry does not exist.
493         // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
494         verify(mAdServicesLoggerMock, never())
495                 .logFledgeApiCallStats(
496                         eq(API_NAME),
497                         eq(TEST_PACKAGE_NAME),
498                         eq(STATUS_USER_CONSENT_REVOKED),
499                         anyInt());
500     }
501 
502     @Test
testImpl_invalidRequest_quotaExhausted_throws()503     public void testImpl_invalidRequest_quotaExhausted_throws() throws Exception {
504         // Use flag values with a clearly small quota limits.
505         mFetchCustomAudienceImpl =
506                 getImplWithFlags(
507                         new FetchCustomAudienceFlags() {
508                             @Override
509                             public long getFledgeCustomAudienceMaxOwnerCount() {
510                                 return 0;
511                             }
512 
513                             @Override
514                             public long getFledgeCustomAudienceMaxCount() {
515                                 return 0;
516                             }
517 
518                             @Override
519                             public long getFledgeCustomAudiencePerAppMaxCount() {
520                                 return 0;
521                             }
522                         });
523 
524         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
525 
526         assertFalse(callback.mIsSuccess);
527         assertEquals(STATUS_INVALID_ARGUMENT, callback.mFledgeErrorResponse.getStatusCode());
528         assertEquals(
529                 String.format(
530                         Locale.ENGLISH,
531                         CUSTOM_AUDIENCE_QUANTITY_CHECK_FAILED,
532                         ImmutableList.of(
533                                 THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_DEVICE_HAD_REACHED,
534                                 THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_OWNER_HAD_REACHED)),
535                 callback.mFledgeErrorResponse.getErrorMessage());
536 
537         // Assert failure due to the invalid argument is logged.
538         verify(mAdServicesLoggerMock)
539                 .logFledgeApiCallStats(
540                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_ARGUMENT), anyInt());
541     }
542 
543     @Test
testImpl_invalidRequest_invalidName_throws()544     public void testImpl_invalidRequest_invalidName_throws() throws Exception {
545         // Use flag value with a clearly small size limit.
546         mFetchCustomAudienceImpl =
547                 getImplWithFlags(
548                         new FetchCustomAudienceFlags() {
549                             @Override
550                             public int getFledgeCustomAudienceMaxNameSizeB() {
551                                 return 1;
552                             }
553                         });
554 
555         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
556 
557         assertFalse(callback.mIsSuccess);
558         assertEquals(STATUS_INVALID_ARGUMENT, callback.mFledgeErrorResponse.getStatusCode());
559         assertEquals(
560                 String.format(
561                         Locale.ENGLISH,
562                         EXCEPTION_MESSAGE_FORMAT,
563                         CLASS_NAME,
564                         ImmutableList.of(
565                                 String.format(
566                                         Locale.ENGLISH,
567                                         VIOLATION_NAME_TOO_LONG,
568                                         1,
569                                         VALID_NAME.getBytes(StandardCharsets.UTF_8).length))),
570                 callback.mFledgeErrorResponse.getErrorMessage());
571 
572         // Assert failure due to the invalid argument is logged.
573         verify(mAdServicesLoggerMock)
574                 .logFledgeApiCallStats(
575                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_ARGUMENT), anyInt());
576     }
577 
578     @Test
testImpl_invalidRequest_invalidActivationTime_throws()579     public void testImpl_invalidRequest_invalidActivationTime_throws() throws Exception {
580         mInputBuilder.setActivationTime(INVALID_DELAYED_ACTIVATION_TIME);
581 
582         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
583 
584         assertFalse(callback.mIsSuccess);
585         assertEquals(STATUS_INVALID_ARGUMENT, callback.mFledgeErrorResponse.getStatusCode());
586         assertEquals(
587                 String.format(
588                         Locale.ENGLISH,
589                         EXCEPTION_MESSAGE_FORMAT,
590                         CLASS_NAME,
591                         ImmutableList.of(
592                                 String.format(
593                                         Locale.ENGLISH,
594                                         VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE,
595                                         CUSTOM_AUDIENCE_MAX_ACTIVATION_DELAY_IN,
596                                         FIXED_NOW_TRUNCATED_TO_MILLI,
597                                         INVALID_DELAYED_ACTIVATION_TIME),
598                                 String.format(
599                                         VIOLATION_EXPIRE_BEFORE_ACTIVATION,
600                                         INVALID_DELAYED_ACTIVATION_TIME,
601                                         VALID_EXPIRATION_TIME))),
602                 callback.mFledgeErrorResponse.getErrorMessage());
603 
604         // Assert failure due to the invalid argument is logged.
605         verify(mAdServicesLoggerMock)
606                 .logFledgeApiCallStats(
607                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_ARGUMENT), anyInt());
608     }
609 
610     @Test
testImpl_invalidRequest_invalidExpirationTime_throws()611     public void testImpl_invalidRequest_invalidExpirationTime_throws() throws Exception {
612         mInputBuilder.setExpirationTime(INVALID_BEYOND_MAX_EXPIRATION_TIME);
613 
614         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
615 
616         assertFalse(callback.mIsSuccess);
617         assertEquals(STATUS_INVALID_ARGUMENT, callback.mFledgeErrorResponse.getStatusCode());
618         assertEquals(
619                 String.format(
620                         Locale.ENGLISH,
621                         EXCEPTION_MESSAGE_FORMAT,
622                         CLASS_NAME,
623                         ImmutableList.of(
624                                 String.format(
625                                         Locale.ENGLISH,
626                                         VIOLATION_EXPIRE_AFTER_MAX_EXPIRE_TIME,
627                                         CUSTOM_AUDIENCE_MAX_EXPIRE_IN,
628                                         FIXED_NOW_TRUNCATED_TO_MILLI,
629                                         FIXED_NOW_TRUNCATED_TO_MILLI,
630                                         INVALID_BEYOND_MAX_EXPIRATION_TIME))),
631                 callback.mFledgeErrorResponse.getErrorMessage());
632 
633         // Assert failure due to the invalid argument is logged.
634         verify(mAdServicesLoggerMock)
635                 .logFledgeApiCallStats(
636                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_ARGUMENT), anyInt());
637     }
638 
639     @Test
testImpl_invalidRequest_invalidUserBiddingSignals_throws()640     public void testImpl_invalidRequest_invalidUserBiddingSignals_throws() throws Exception {
641         // Use flag value with a clearly small size limit.
642         mFetchCustomAudienceImpl =
643                 getImplWithFlags(
644                         new FetchCustomAudienceFlags() {
645                             @Override
646                             public int getFledgeFetchCustomAudienceMaxUserBiddingSignalsSizeB() {
647                                 return 1;
648                             }
649                         });
650 
651         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
652 
653         assertFalse(callback.mIsSuccess);
654         assertEquals(STATUS_INVALID_ARGUMENT, callback.mFledgeErrorResponse.getStatusCode());
655         assertEquals(
656                 String.format(
657                         Locale.ENGLISH,
658                         EXCEPTION_MESSAGE_FORMAT,
659                         CLASS_NAME,
660                         ImmutableList.of(
661                                 String.format(
662                                         Locale.ENGLISH,
663                                         VIOLATION_USER_BIDDING_SIGNAL_TOO_BIG,
664                                         1,
665                                         VALID_USER_BIDDING_SIGNALS.getSizeInBytes()))),
666                 callback.mFledgeErrorResponse.getErrorMessage());
667 
668         // Assert failure due to the invalid argument is logged.
669         verify(mAdServicesLoggerMock)
670                 .logFledgeApiCallStats(
671                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_ARGUMENT), anyInt());
672     }
673 
674     @Test
testImpl_customHeaderExceedsLimit_throws()675     public void testImpl_customHeaderExceedsLimit_throws() throws Exception {
676         // Use flag value with a clearly small size limit.
677         mFetchCustomAudienceImpl =
678                 getImplWithFlags(
679                         new FetchCustomAudienceFlags() {
680                             @Override
681                             public int getFledgeFetchCustomAudienceMaxRequestCustomHeaderSizeB() {
682                                 return 1;
683                             }
684                         });
685 
686         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
687 
688         assertFalse(callback.mIsSuccess);
689         assertEquals(STATUS_INVALID_ARGUMENT, callback.mFledgeErrorResponse.getStatusCode());
690         assertEquals(
691                 REQUEST_CUSTOM_HEADER_EXCEEDS_SIZE_LIMIT_MESSAGE,
692                 callback.mFledgeErrorResponse.getErrorMessage());
693 
694         // Assert failure due to the invalid argument is logged.
695         verify(mAdServicesLoggerMock)
696                 .logFledgeApiCallStats(
697                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_ARGUMENT), anyInt());
698     }
699 
700     @Test
testImpl_invalidResponse_invalidJSONObject()701     public void testImpl_invalidResponse_invalidJSONObject() throws Exception {
702         String jsonString = "Not[A]VALID[JSON]";
703         MockWebServer mockWebServer =
704                 mMockWebServerRule.startMockWebServer(
705                         List.of(new MockResponse().setBody("Not[A]VALID[JSON]")));
706 
707         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
708 
709         assertEquals(1, mockWebServer.getRequestCount());
710         assertFalse(callback.mIsSuccess);
711         assertEquals(STATUS_INVALID_OBJECT, callback.mFledgeErrorResponse.getStatusCode());
712         assertEquals(
713                 "Value Not of type "
714                         + jsonString.getClass().getName()
715                         + " cannot be converted to JSONObject",
716                 callback.mFledgeErrorResponse.getErrorMessage());
717 
718         // Assert failure due to the invalid response is logged.
719         verify(mAdServicesLoggerMock)
720                 .logFledgeApiCallStats(
721                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_OBJECT), anyInt());
722     }
723 
724     @Test
testImpl_invalidFused_missingField()725     public void testImpl_invalidFused_missingField() throws Exception {
726         // Remove ads from the response, resulting in an incomplete fused custom audience.
727         JSONObject validResponse =
728                 getFullSuccessfulJsonResponse(BUYER, /* auctionServerRequestFlagsEnabled= */ false);
729         validResponse.remove(ADS_KEY);
730 
731         MockWebServer mockWebServer =
732                 mMockWebServerRule.startMockWebServer(
733                         List.of(new MockResponse().setBody(validResponse.toString())));
734 
735         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
736 
737         assertEquals(1, mockWebServer.getRequestCount());
738         assertFalse(callback.mIsSuccess);
739         assertEquals(STATUS_INVALID_OBJECT, callback.mFledgeErrorResponse.getStatusCode());
740         assertEquals(
741                 FUSED_CUSTOM_AUDIENCE_INCOMPLETE_MESSAGE,
742                 callback.mFledgeErrorResponse.getErrorMessage());
743 
744         // Assert failure due to the invalid response is logged.
745         verify(mAdServicesLoggerMock)
746                 .logFledgeApiCallStats(
747                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_OBJECT), anyInt());
748     }
749 
750     @Test
testImpl_invalidFused_missingFieldAuctionServerFlagsEnabled()751     public void testImpl_invalidFused_missingFieldAuctionServerFlagsEnabled() throws Exception {
752         enableAuctionServerRequestFlags();
753 
754         // Remove adsKey from the response, resulting in an incomplete fused custom audience.
755         JSONObject validResponse =
756                 getFullSuccessfulJsonResponse(BUYER, /* auctionServerRequestFlagsEnabled= */ true);
757         validResponse.remove(ADS_KEY);
758 
759         MockWebServer mockWebServer =
760                 mMockWebServerRule.startMockWebServer(
761                         List.of(new MockResponse().setBody(validResponse.toString())));
762 
763         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
764 
765         assertEquals(1, mockWebServer.getRequestCount());
766         assertFalse(callback.mIsSuccess);
767         assertEquals(STATUS_INVALID_OBJECT, callback.mFledgeErrorResponse.getStatusCode());
768         assertEquals(
769                 FUSED_CUSTOM_AUDIENCE_INCOMPLETE_MESSAGE,
770                 callback.mFledgeErrorResponse.getErrorMessage());
771 
772         // Assert failure due to the invalid response is logged.
773         verify(mAdServicesLoggerMock)
774                 .logFledgeApiCallStats(
775                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_OBJECT), anyInt());
776     }
777 
778     @Test
testImpl_invalidFused_invalidField()779     public void testImpl_invalidFused_invalidField() throws Exception {
780         // Replace buyer to cause mismatch.
781         JSONObject validResponse = getFullSuccessfulJsonResponse(BUYER, false);
782         validResponse.remove(DAILY_UPDATE_URI_KEY);
783         JSONObject invalidResponse =
784                 addDailyUpdateUri(
785                         validResponse, getValidDailyUpdateUriByBuyer(VALID_BUYER_2), false);
786 
787         MockWebServer mockWebServer =
788                 mMockWebServerRule.startMockWebServer(
789                         List.of(new MockResponse().setBody(invalidResponse.toString())));
790 
791         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
792 
793         assertEquals(1, mockWebServer.getRequestCount());
794         assertFalse(callback.mIsSuccess);
795         assertEquals(STATUS_INVALID_ARGUMENT, callback.mFledgeErrorResponse.getStatusCode());
796         assertEquals(
797                 String.format(
798                         Locale.ENGLISH,
799                         EXCEPTION_MESSAGE_FORMAT,
800                         CLASS_NAME,
801                         ImmutableList.of(
802                                 String.format(
803                                         Locale.ENGLISH,
804                                         IDENTIFIER_AND_URI_ARE_INCONSISTENT,
805                                         AD_TECH_ROLE_BUYER,
806                                         BUYER,
807                                         AD_TECH_ROLE_BUYER,
808                                         DAILY_UPDATE_URI_FIELD_NAME,
809                                         VALID_BUYER_2))),
810                 callback.mFledgeErrorResponse.getErrorMessage());
811 
812         // Assert failure due to the invalid response is logged.
813         verify(mAdServicesLoggerMock)
814                 .logFledgeApiCallStats(
815                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_ARGUMENT), anyInt());
816     }
817 
818     @Test
testImpl_invalidFused_exceedsSizeLimit()819     public void testImpl_invalidFused_exceedsSizeLimit() throws Exception {
820         // Use flag value with a clearly small size limit.
821         mFetchCustomAudienceImpl =
822                 getImplWithFlags(
823                         new FetchCustomAudienceFlags() {
824                             @Override
825                             public int getFledgeFetchCustomAudienceMaxCustomAudienceSizeB() {
826                                 return 1;
827                             }
828                         });
829 
830         MockWebServer mockWebServer =
831                 mMockWebServerRule.startMockWebServer(
832                         List.of(
833                                 new MockResponse()
834                                         .setBody(getFullSuccessfulJsonResponseString(BUYER))));
835 
836         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
837 
838         assertEquals(1, mockWebServer.getRequestCount());
839         assertFalse(callback.mIsSuccess);
840         assertEquals(STATUS_INVALID_OBJECT, callback.mFledgeErrorResponse.getStatusCode());
841         assertEquals(
842                 FUSED_CUSTOM_AUDIENCE_EXCEEDS_SIZE_LIMIT_MESSAGE,
843                 callback.mFledgeErrorResponse.getErrorMessage());
844 
845         // Assert failure due to the invalid response is logged.
846         verify(mAdServicesLoggerMock)
847                 .logFledgeApiCallStats(
848                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_OBJECT), anyInt());
849     }
850 
851     @Test
testImpl_runNormally_partialResponse()852     public void testImpl_runNormally_partialResponse() throws Exception {
853         // Remove all fields from the response that the request itself has.
854         JSONObject partialResponse = getFullSuccessfulJsonResponse(BUYER, false);
855         partialResponse.remove(OWNER_KEY);
856         partialResponse.remove(BUYER_KEY);
857         partialResponse.remove(NAME_KEY);
858         partialResponse.remove(ACTIVATION_TIME_KEY);
859         partialResponse.remove(EXPIRATION_TIME_KEY);
860         partialResponse.remove(USER_BIDDING_SIGNALS_KEY);
861 
862         MockWebServer mockWebServer =
863                 mMockWebServerRule.startMockWebServer(
864                         List.of(new MockResponse().setBody(partialResponse.toString())));
865 
866         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
867 
868         assertEquals(1, mockWebServer.getRequestCount());
869         assertTrue(callback.mIsSuccess);
870         verify(mCustomAudienceDaoMock)
871                 .insertOrOverwriteCustomAudience(
872                         FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
873                         getValidDailyUpdateUriByBuyer(BUYER),
874                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
875         verify(mAdServicesLoggerMock)
876                 .logFledgeApiCallStats(
877                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
878     }
879 
880     @Test
testImpl_runNormally_discardedResponseValues()881     public void testImpl_runNormally_discardedResponseValues() throws Exception {
882         // Replace response name with a valid but different name from the original request.
883         JSONObject validResponse = getFullSuccessfulJsonResponse(BUYER, false);
884         validResponse.remove(NAME_KEY);
885         String validNameFromTheServer = VALID_NAME + "FromTheServer";
886         validResponse = addName(validResponse, validNameFromTheServer, false);
887 
888         MockWebServer mockWebServer =
889                 mMockWebServerRule.startMockWebServer(
890                         List.of(new MockResponse().setBody(validResponse.toString())));
891 
892         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
893 
894         assertEquals(1, mockWebServer.getRequestCount());
895         assertTrue(callback.mIsSuccess);
896         // Assert the response value is in fact discarded in favor of the request value.
897         verify(mCustomAudienceDaoMock)
898                 .insertOrOverwriteCustomAudience(
899                         FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
900                         getValidDailyUpdateUriByBuyer(BUYER),
901                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
902         verify(mAdServicesLoggerMock)
903                 .logFledgeApiCallStats(
904                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
905     }
906 
907     @Test
testImpl_runNormally_completeResponse()908     public void testImpl_runNormally_completeResponse() throws Exception {
909         // Respond with a complete custom audience including the request values as is.
910         MockWebServer mockWebServer =
911                 mMockWebServerRule.startMockWebServer(
912                         List.of(
913                                 new MockResponse()
914                                         .setBody(getFullSuccessfulJsonResponseString(BUYER))));
915 
916         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
917 
918         assertEquals(1, mockWebServer.getRequestCount());
919         assertTrue(callback.mIsSuccess);
920         verify(mCustomAudienceDaoMock)
921                 .insertOrOverwriteCustomAudience(
922                         FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
923                         getValidDailyUpdateUriByBuyer(BUYER),
924                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
925         verify(mAdServicesLoggerMock)
926                 .logFledgeApiCallStats(
927                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
928     }
929 
930     @Test
testImpl_runNormally_CallbackThrowsException()931     public void testImpl_runNormally_CallbackThrowsException() throws Exception {
932         // Respond with a complete custom audience including the request values as is.
933         MockWebServer mockWebServer =
934                 mMockWebServerRule.startMockWebServer(
935                         List.of(
936                                 new MockResponse()
937                                         .setBody(getFullSuccessfulJsonResponseString(BUYER))));
938 
939         FetchCustomAudienceTestCallback callback =
940                 callFetchCustomAudienceWithErrorCallback(mInputBuilder.build(), 3);
941 
942         assertEquals(1, mockWebServer.getRequestCount());
943         assertTrue(callback.mIsSuccess);
944         verify(mAdServicesLoggerMock)
945                 .logFledgeApiCallStats(
946                         eq(API_NAME),
947                         eq(TEST_PACKAGE_NAME),
948                         eq(STATUS_CALLBACK_SHUTDOWN),
949                         anyInt());
950     }
951 
952     @Test
testImpl_runWithFailure_CallbackThrowsException()953     public void testImpl_runWithFailure_CallbackThrowsException() throws Exception {
954 
955         // Just want the service to throw an exception so we trigger the failure callback
956         doThrow(new FledgeAuthorizationFilter.AdTechNotAllowedException())
957                 .when(mCustomAudienceServiceFilterMock)
958                 .filterRequestAndExtractIdentifier(
959                         mFetchUri,
960                         VALID_OWNER,
961                         false,
962                         true,
963                         true,
964                         Process.myUid(),
965                         API_NAME,
966                         Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
967                         DevContext.createForDevOptionsDisabled());
968 
969         FetchCustomAudienceTestCallback callback =
970                 callFetchCustomAudienceWithErrorCallback(mInputBuilder.build(), 1);
971 
972         assertFalse(callback.mIsSuccess);
973         verify(mAdServicesLoggerMock)
974                 .logFledgeApiCallStats(
975                         eq(API_NAME),
976                         eq(TEST_PACKAGE_NAME),
977                         eq(STATUS_CALLBACK_SHUTDOWN),
978                         anyInt());
979     }
980 
981     @Test
testImpl_runNormally_completeResponseWithAuctionServerFlagsEnabled()982     public void testImpl_runNormally_completeResponseWithAuctionServerFlagsEnabled()
983             throws Exception {
984         enableAuctionServerRequestFlags();
985 
986         // Respond with a complete custom audience including the request values as is and auction
987         // request flags
988         MockWebServer mockWebServer =
989                 mMockWebServerRule.startMockWebServer(
990                         List.of(
991                                 new MockResponse()
992                                         .setBody(
993                                                 getFullSuccessfulJsonResponse(BUYER, true)
994                                                         .toString())));
995 
996         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
997 
998         assertEquals(1, mockWebServer.getRequestCount());
999         assertTrue(callback.mIsSuccess);
1000         verify(mCustomAudienceDaoMock)
1001                 .insertOrOverwriteCustomAudience(
1002                         FetchCustomAudienceFixture
1003                                 .getFullSuccessfulDBCustomAudienceWithAuctionServerRequestFlags(
1004                                         FLAG_AUCTION_SERVER_REQUEST_OMIT_ADS),
1005                         getValidDailyUpdateUriByBuyer(BUYER),
1006                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
1007         verify(mAdServicesLoggerMock)
1008                 .logFledgeApiCallStats(
1009                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
1010     }
1011 
1012     @Test
testImpl_runNormally_completeResponseWithAuctionServerFlagsDisabled()1013     public void testImpl_runNormally_completeResponseWithAuctionServerFlagsDisabled()
1014             throws Exception {
1015         // Respond with a complete custom audience including the request values as is and auction
1016         // request flags
1017         MockWebServer mockWebServer =
1018                 mMockWebServerRule.startMockWebServer(
1019                         List.of(
1020                                 new MockResponse()
1021                                         .setBody(
1022                                                 getFullSuccessfulJsonResponse(BUYER, true)
1023                                                         .toString())));
1024 
1025         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
1026 
1027         assertEquals(1, mockWebServer.getRequestCount());
1028         assertTrue(callback.mIsSuccess);
1029         // Expect a CA without auction server request flags
1030         verify(mCustomAudienceDaoMock)
1031                 .insertOrOverwriteCustomAudience(
1032                         FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
1033                         getValidDailyUpdateUriByBuyer(BUYER),
1034                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
1035         verify(mAdServicesLoggerMock)
1036                 .logFledgeApiCallStats(
1037                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
1038     }
1039 
1040     @Test
1041     public void
testImpl_runNormally_completeResponseWithAuctionServerFlagsEnabledButNoFlagsInResponse()1042             testImpl_runNormally_completeResponseWithAuctionServerFlagsEnabledButNoFlagsInResponse()
1043                     throws Exception {
1044         enableAuctionServerRequestFlags();
1045 
1046         // Respond with a complete custom audience including the request values as is
1047         MockWebServer mockWebServer =
1048                 mMockWebServerRule.startMockWebServer(
1049                         List.of(
1050                                 new MockResponse()
1051                                         .setBody(
1052                                                 getFullSuccessfulJsonResponse(BUYER, false)
1053                                                         .toString())));
1054 
1055         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
1056 
1057         assertEquals(1, mockWebServer.getRequestCount());
1058         assertTrue(callback.mIsSuccess);
1059         // Expect a CA without auction server request flags
1060         verify(mCustomAudienceDaoMock)
1061                 .insertOrOverwriteCustomAudience(
1062                         FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
1063                         getValidDailyUpdateUriByBuyer(BUYER),
1064                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
1065         verify(mAdServicesLoggerMock)
1066                 .logFledgeApiCallStats(
1067                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
1068     }
1069 
1070     @Test
testImpl_runNormally_withDevOptionsEnabled()1071     public void testImpl_runNormally_withDevOptionsEnabled() throws Exception {
1072         // Respond with a complete custom audience including the request values as is.
1073         MockWebServer mockWebServer =
1074                 mMockWebServerRule.startMockWebServer(
1075                         List.of(
1076                                 new MockResponse()
1077                                         .setBody(getFullSuccessfulJsonResponseString(BUYER))));
1078 
1079         CountDownLatch resultLatch = new CountDownLatch(1);
1080         FetchCustomAudienceTestCallback callback = new FetchCustomAudienceTestCallback(resultLatch);
1081         mFetchCustomAudienceImpl.doFetchCustomAudience(
1082                 mInputBuilder.build(),
1083                 callback,
1084                 DevContext.builder().setDevOptionsEnabled(true).build());
1085         resultLatch.await();
1086 
1087         assertEquals(1, mockWebServer.getRequestCount());
1088         assertTrue(callback.mIsSuccess);
1089         verify(mCustomAudienceDaoMock)
1090                 .insertOrOverwriteCustomAudience(
1091                         FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
1092                         getValidDailyUpdateUriByBuyer(BUYER),
1093                         /*debuggable=*/ true);
1094         verify(mAdServicesLoggerMock)
1095                 .logFledgeApiCallStats(
1096                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
1097     }
1098 
1099     @Test
testImpl_runNormally_withAdRenderId()1100     public void testImpl_runNormally_withAdRenderId() throws Exception {
1101         // Enable server auction Ad Render Ids
1102         mFetchCustomAudienceImpl =
1103                 getImplWithFlags(
1104                         new FetchCustomAudienceFlags() {
1105                             @Override
1106                             public boolean getFledgeAuctionServerAdRenderIdEnabled() {
1107                                 return true;
1108                             }
1109                         });
1110 
1111         // Respond with a complete custom audience with ad render ids including the request values
1112         // as is.
1113         MockWebServer mockWebServer =
1114                 mMockWebServerRule.startMockWebServer(
1115                         List.of(
1116                                 new MockResponse()
1117                                         .setBody(
1118                                                 getFullSuccessfulJsonResponseStringWithAdRenderId(
1119                                                         BUYER))));
1120 
1121         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
1122 
1123         assertEquals(1, mockWebServer.getRequestCount());
1124         assertTrue(callback.mIsSuccess);
1125         verify(mCustomAudienceDaoMock)
1126                 .insertOrOverwriteCustomAudience(
1127                         FetchCustomAudienceFixture
1128                                 .getFullSuccessfulDBCustomAudienceWithAdRenderId(),
1129                         getValidDailyUpdateUriByBuyer(BUYER),
1130                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
1131         verify(mAdServicesLoggerMock)
1132                 .logFledgeApiCallStats(
1133                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
1134     }
1135 
1136     @Test
testImpl_withAdRenderId_onlyInvalidAdRenderIds_fail()1137     public void testImpl_withAdRenderId_onlyInvalidAdRenderIds_fail() throws Exception {
1138         // Enable server auction Ad Render Ids
1139         mFetchCustomAudienceImpl =
1140                 getImplWithFlags(
1141                         new FetchCustomAudienceFlags() {
1142                             @Override
1143                             public boolean getFledgeAuctionServerAdRenderIdEnabled() {
1144                                 return true;
1145                             }
1146 
1147                             @Override
1148                             public long getFledgeAuctionServerAdRenderIdMaxLength() {
1149                                 return 1;
1150                             }
1151                         });
1152 
1153         // Respond with a complete custom audience with ad render ids including the request values
1154         // as is.
1155         MockWebServer mockWebServer =
1156                 mMockWebServerRule.startMockWebServer(
1157                         List.of(
1158                                 new MockResponse()
1159                                         .setBody(
1160                                                 getFullJsonResponseStringWithInvalidAdRenderId(
1161                                                         BUYER))));
1162 
1163         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
1164 
1165         assertEquals(1, mockWebServer.getRequestCount());
1166         assertFalse(callback.mIsSuccess);
1167         assertEquals(STATUS_INVALID_ARGUMENT, callback.mFledgeErrorResponse.getStatusCode());
1168 
1169         // Assert failure due to the invalid response is logged.
1170         verify(mAdServicesLoggerMock)
1171                 .logFledgeApiCallStats(
1172                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INVALID_ARGUMENT), anyInt());
1173     }
1174 
1175     @Test
testImpl_runNormally_withAdRenderId_AdRenderIdFlagDisabled()1176     public void testImpl_runNormally_withAdRenderId_AdRenderIdFlagDisabled() throws Exception {
1177         // Disable server auction Ad Render Ids
1178         mFetchCustomAudienceImpl =
1179                 getImplWithFlags(
1180                         new FetchCustomAudienceFlags() {
1181                             @Override
1182                             public boolean getFledgeAuctionServerAdRenderIdEnabled() {
1183                                 return false;
1184                             }
1185                         });
1186 
1187         // Respond with a complete custom audience with ad render ids including the request values
1188         // as is, but expect that ad render ids to not be read
1189         MockWebServer mockWebServer =
1190                 mMockWebServerRule.startMockWebServer(
1191                         List.of(
1192                                 new MockResponse()
1193                                         .setBody(
1194                                                 getFullSuccessfulJsonResponseStringWithAdRenderId(
1195                                                         BUYER))));
1196 
1197         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
1198 
1199         ArgumentCaptor<DBCustomAudience> argumentDBCustomAudience =
1200                 ArgumentCaptor.forClass(DBCustomAudience.class);
1201 
1202         assertEquals(1, mockWebServer.getRequestCount());
1203         assertTrue(callback.mIsSuccess);
1204         verify(mCustomAudienceDaoMock)
1205                 .insertOrOverwriteCustomAudience(
1206                         argumentDBCustomAudience.capture(),
1207                         eq(getValidDailyUpdateUriByBuyer(BUYER)),
1208                         eq(DevContext.createForDevOptionsDisabled().getDevOptionsEnabled()));
1209         DBCustomAudience dbCustomAudience = argumentDBCustomAudience.getValue();
1210         Assert.assertNotEquals(
1211                 FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudienceWithAdRenderId(),
1212                 dbCustomAudience);
1213         assertTrue(dbCustomAudience.getAds().stream().allMatch(ad -> ad.getAdRenderId() == null));
1214         verify(mAdServicesLoggerMock)
1215                 .logFledgeApiCallStats(
1216                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
1217     }
1218 
1219     @Test
testImpl_runNormally_differentResponsesToSameFetchUri()1220     public void testImpl_runNormally_differentResponsesToSameFetchUri() throws Exception {
1221         // Respond with a complete custom audience including the request values as is.
1222         JSONObject response1 = getFullSuccessfulJsonResponse(BUYER, false);
1223         Uri differentDailyUpdateUri = CommonFixture.getUri(BUYER, "/differentUpdate");
1224         JSONObject response2 =
1225                 getFullSuccessfulJsonResponse(BUYER, false)
1226                         .put(DAILY_UPDATE_URI_KEY, differentDailyUpdateUri.toString());
1227 
1228         MockWebServer mockWebServer =
1229                 mMockWebServerRule.startMockWebServer(
1230                         new Dispatcher() {
1231                             AtomicInteger mNumCalls = new AtomicInteger(0);
1232 
1233                             @Override
1234                             public MockResponse dispatch(RecordedRequest request) {
1235                                 if (mNumCalls.get() == 0) {
1236                                     mNumCalls.addAndGet(1);
1237                                     return new MockResponse().setBody(response1.toString());
1238                                 } else if (mNumCalls.get() == 1) {
1239                                     mNumCalls.addAndGet(1);
1240                                     return new MockResponse().setBody(response2.toString());
1241                                 } else {
1242                                     throw new IllegalStateException("Expected only 2 calls!");
1243                                 }
1244                             }
1245                         });
1246 
1247         FetchCustomAudienceTestCallback callback1 = callFetchCustomAudience(mInputBuilder.build());
1248         assertTrue(callback1.mIsSuccess);
1249         verify(mCustomAudienceDaoMock)
1250                 .insertOrOverwriteCustomAudience(
1251                         FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
1252                         getValidDailyUpdateUriByBuyer(BUYER),
1253                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
1254 
1255         FetchCustomAudienceTestCallback callback2 = callFetchCustomAudience(mInputBuilder.build());
1256         assertTrue(callback2.mIsSuccess);
1257         verify(mCustomAudienceDaoMock)
1258                 .insertOrOverwriteCustomAudience(
1259                         FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
1260                         differentDailyUpdateUri,
1261                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
1262 
1263         verify(mAdServicesLoggerMock, times(2))
1264                 .logFledgeApiCallStats(
1265                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
1266     }
1267 
1268     @Test
testImpl_requestRejectedByServer()1269     public void testImpl_requestRejectedByServer() throws Exception {
1270         // Respond with a 403
1271         MockWebServer mockWebServer =
1272                 mMockWebServerRule.startMockWebServer(
1273                         List.of(new MockResponse().setResponseCode(403)));
1274 
1275         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
1276         assertEquals(1, mockWebServer.getRequestCount());
1277         assertFalse(callback.mIsSuccess);
1278         assertEquals(STATUS_INTERNAL_ERROR, callback.mFledgeErrorResponse.getStatusCode());
1279         verify(mCustomAudienceDaoMock, times(/* wantedNumberOfInvocations= */ 0))
1280                 .insertOrOverwriteCustomAudience(any(), any(), anyBoolean());
1281         verify(mAdServicesLoggerMock)
1282                 .logFledgeApiCallStats(
1283                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_INTERNAL_ERROR), anyInt());
1284     }
1285 
1286     @Test
testImpl_AddsToQuarantineTableWhenServerReturns429()1287     public void testImpl_AddsToQuarantineTableWhenServerReturns429() throws Exception {
1288         // Respond with a 429
1289         MockWebServer mockWebServer =
1290                 mMockWebServerRule.startMockWebServer(
1291                         List.of(new MockResponse().setResponseCode(429)));
1292 
1293         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
1294         assertEquals(1, mockWebServer.getRequestCount());
1295         assertFalse(callback.mIsSuccess);
1296         assertEquals(
1297                 STATUS_SERVER_RATE_LIMIT_REACHED, callback.mFledgeErrorResponse.getStatusCode());
1298         verify(mCustomAudienceDaoMock)
1299                 .safelyInsertCustomAudienceQuarantine(
1300                         any(DBCustomAudienceQuarantine.class), anyLong());
1301         verify(mAdServicesLoggerMock)
1302                 .logFledgeApiCallStats(
1303                         eq(API_NAME),
1304                         eq(TEST_PACKAGE_NAME),
1305                         eq(STATUS_SERVER_RATE_LIMIT_REACHED),
1306                         anyInt());
1307     }
1308 
1309     @Test
testImpl_ReturnsServerRateLimitReachedWhenEntryIsInQuarantineTable()1310     public void testImpl_ReturnsServerRateLimitReachedWhenEntryIsInQuarantineTable()
1311             throws Exception {
1312         doReturn(true)
1313                 .when(mCustomAudienceDaoMock)
1314                 .doesCustomAudienceQuarantineExist(VALID_OWNER, BUYER);
1315         // Return a time in the future so request gets filtered
1316         doReturn(CLOCK.instant().plusMillis(2 * DEFAULT_RETRY_AFTER_VALUE.toMillis()))
1317                 .when(mCustomAudienceDaoMock)
1318                 .getCustomAudienceQuarantineExpiration(VALID_OWNER, BUYER);
1319 
1320         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
1321         assertFalse(callback.mIsSuccess);
1322         assertEquals(
1323                 STATUS_SERVER_RATE_LIMIT_REACHED, callback.mFledgeErrorResponse.getStatusCode());
1324         verify(mCustomAudienceDaoMock).doesCustomAudienceQuarantineExist(VALID_OWNER, BUYER);
1325         verify(mCustomAudienceDaoMock).getCustomAudienceQuarantineExpiration(VALID_OWNER, BUYER);
1326         verify(mAdServicesLoggerMock)
1327                 .logFledgeApiCallStats(
1328                         eq(API_NAME),
1329                         eq(TEST_PACKAGE_NAME),
1330                         eq(STATUS_SERVER_RATE_LIMIT_REACHED),
1331                         anyInt());
1332     }
1333 
1334     @Test
testImpl_SucceedsAndRemovesEntryFromQuarantineTable()1335     public void testImpl_SucceedsAndRemovesEntryFromQuarantineTable() throws Exception {
1336         doReturn(true)
1337                 .when(mCustomAudienceDaoMock)
1338                 .doesCustomAudienceQuarantineExist(VALID_OWNER, BUYER);
1339         // Return a time in the past so request is not filtered
1340         doReturn(CLOCK.instant().minusMillis(2 * DEFAULT_RETRY_AFTER_VALUE.toMillis()))
1341                 .when(mCustomAudienceDaoMock)
1342                 .getCustomAudienceQuarantineExpiration(VALID_OWNER, BUYER);
1343 
1344         // Respond with a complete custom audience including the request values as is.
1345         MockWebServer mockWebServer =
1346                 mMockWebServerRule.startMockWebServer(
1347                         List.of(
1348                                 new MockResponse()
1349                                         .setBody(getFullSuccessfulJsonResponseString(BUYER))));
1350 
1351         FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
1352         assertEquals(1, mockWebServer.getRequestCount());
1353         assertTrue(callback.mIsSuccess);
1354 
1355         verify(mCustomAudienceDaoMock).doesCustomAudienceQuarantineExist(VALID_OWNER, BUYER);
1356         verify(mCustomAudienceDaoMock).getCustomAudienceQuarantineExpiration(VALID_OWNER, BUYER);
1357         verify(mCustomAudienceDaoMock).deleteQuarantineEntry(VALID_OWNER, BUYER);
1358         verify(mCustomAudienceDaoMock)
1359                 .insertOrOverwriteCustomAudience(
1360                         FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
1361                         getValidDailyUpdateUriByBuyer(BUYER),
1362                         DevContext.createForDevOptionsDisabled().getDevOptionsEnabled());
1363 
1364         verify(mAdServicesLoggerMock)
1365                 .logFledgeApiCallStats(
1366                         eq(API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt());
1367     }
1368 
enableAuctionServerRequestFlags()1369     private void enableAuctionServerRequestFlags() {
1370         // Enable auction server request flags
1371         mFetchCustomAudienceImpl =
1372                 getImplWithFlags(
1373                         new FetchCustomAudienceFlags() {
1374                             @Override
1375                             public boolean getFledgeAuctionServerRequestFlagsEnabled() {
1376                                 return true;
1377                             }
1378                         });
1379     }
1380 
callFetchCustomAudience( FetchAndJoinCustomAudienceInput input)1381     private FetchCustomAudienceTestCallback callFetchCustomAudience(
1382             FetchAndJoinCustomAudienceInput input) throws Exception {
1383         CountDownLatch resultLatch = new CountDownLatch(1);
1384         FetchCustomAudienceTestCallback callback = new FetchCustomAudienceTestCallback(resultLatch);
1385         mFetchCustomAudienceImpl.doFetchCustomAudience(
1386                 input, callback, DevContext.createForDevOptionsDisabled());
1387         resultLatch.await();
1388         return callback;
1389     }
1390 
callFetchCustomAudienceWithErrorCallback( FetchAndJoinCustomAudienceInput input, int numCountDown)1391     private FetchCustomAudienceTestCallback callFetchCustomAudienceWithErrorCallback(
1392             FetchAndJoinCustomAudienceInput input, int numCountDown) throws Exception {
1393         CountDownLatch resultLatch = new CountDownLatch(numCountDown);
1394         Answer<Void> countDownAnswer =
1395                 unused -> {
1396                     resultLatch.countDown();
1397                     return null;
1398                 };
1399         doAnswer(countDownAnswer)
1400                 .when(mAdServicesLoggerMock)
1401                 .logFledgeApiCallStats(anyInt(), any(), anyInt(), anyInt());
1402         FetchCustomAudienceTestThrowingCallback callback =
1403                 new FetchCustomAudienceTestThrowingCallback(resultLatch);
1404         mFetchCustomAudienceImpl.doFetchCustomAudience(
1405                 input, callback, DevContext.createForDevOptionsDisabled());
1406         resultLatch.await();
1407         return callback;
1408     }
1409 
getImplWithFlags(Flags flags)1410     private FetchCustomAudienceImpl getImplWithFlags(Flags flags) {
1411         return new FetchCustomAudienceImpl(
1412                 flags,
1413                 CLOCK,
1414                 mAdServicesLoggerMock,
1415                 DIRECT_EXECUTOR,
1416                 mCustomAudienceDaoMock,
1417                 mCallingAppUid,
1418                 mCustomAudienceServiceFilterMock,
1419                 mHttpClientSpy,
1420                 mAdFilteringFeatureFactory.getFrequencyCapAdDataValidator(),
1421                 mAdRenderIdValidator,
1422                 AD_DATA_CONVERSION_STRATEGY);
1423     }
1424 
1425     public static class FetchCustomAudienceTestCallback
1426             extends FetchAndJoinCustomAudienceCallback.Stub {
1427         protected final CountDownLatch mCountDownLatch;
1428         boolean mIsSuccess = false;
1429         FledgeErrorResponse mFledgeErrorResponse;
1430 
FetchCustomAudienceTestCallback(CountDownLatch countDownLatch)1431         public FetchCustomAudienceTestCallback(CountDownLatch countDownLatch) {
1432             mCountDownLatch = countDownLatch;
1433         }
1434 
isSuccess()1435         public boolean isSuccess() {
1436             return mIsSuccess;
1437         }
1438 
1439         @Override
onSuccess()1440         public void onSuccess() throws RemoteException {
1441             LoggerFactory.getFledgeLogger()
1442                     .v("Reporting success to FetchCustomAudienceTestCallback.");
1443             mIsSuccess = true;
1444             mCountDownLatch.countDown();
1445         }
1446 
1447         @Override
onFailure(FledgeErrorResponse fledgeErrorResponse)1448         public void onFailure(FledgeErrorResponse fledgeErrorResponse) throws RemoteException {
1449             LoggerFactory.getFledgeLogger()
1450                     .v("Reporting failure to FetchCustomAudienceTestCallback.");
1451             mFledgeErrorResponse = fledgeErrorResponse;
1452             mCountDownLatch.countDown();
1453         }
1454     }
1455 
1456     public static class FetchCustomAudienceTestThrowingCallback
1457             extends FetchCustomAudienceTestCallback {
FetchCustomAudienceTestThrowingCallback(CountDownLatch countDownLatch)1458         public FetchCustomAudienceTestThrowingCallback(CountDownLatch countDownLatch) {
1459             super(countDownLatch);
1460         }
1461 
1462         @Override
onFailure(FledgeErrorResponse fledgeErrorResponse)1463         public void onFailure(FledgeErrorResponse fledgeErrorResponse) throws RemoteException {
1464             mFledgeErrorResponse = fledgeErrorResponse;
1465             mCountDownLatch.countDown();
1466             throw new RemoteException();
1467         }
1468 
1469         @Override
onSuccess()1470         public void onSuccess() throws RemoteException {
1471             mIsSuccess = true;
1472             mCountDownLatch.countDown();
1473             throw new RemoteException();
1474         }
1475     }
1476 
1477     private static class FetchCustomAudienceFlags implements Flags {
1478         @Override
getFledgeFetchCustomAudienceEnabled()1479         public boolean getFledgeFetchCustomAudienceEnabled() {
1480             return true;
1481         }
1482 
1483         @Override
getFledgeFrequencyCapFilteringEnabled()1484         public boolean getFledgeFrequencyCapFilteringEnabled() {
1485             return true;
1486         }
1487 
1488         @Override
getFledgeAppInstallFilteringEnabled()1489         public boolean getFledgeAppInstallFilteringEnabled() {
1490             return true;
1491         }
1492     }
1493 }
1494