• 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.service.adselection;
18 
19 import static android.adservices.adselection.AdSelectionFromOutcomesConfigFixture.SAMPLE_SELLER;
20 import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
21 import static android.adservices.common.AdServicesStatusUtils.STATUS_TIMEOUT;
22 import static android.adservices.common.AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
23 
24 import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_AD_SELECTION_FROM_OUTCOMES_OVERALL_TIMEOUT_MS;
25 import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_AD_SELECTION_SELECTING_OUTCOME_TIMEOUT_MS;
26 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN;
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertFalse;
35 import static org.junit.Assert.assertNotNull;
36 import static org.junit.Assert.assertNull;
37 import static org.junit.Assert.assertTrue;
38 import static org.mockito.ArgumentMatchers.any;
39 import static org.mockito.ArgumentMatchers.anyInt;
40 import static org.mockito.ArgumentMatchers.argThat;
41 import static org.mockito.Mockito.never;
42 
43 import android.adservices.adselection.AdSelectionCallback;
44 import android.adservices.adselection.AdSelectionFromOutcomesConfig;
45 import android.adservices.adselection.AdSelectionFromOutcomesConfigFixture;
46 import android.adservices.adselection.AdSelectionFromOutcomesInput;
47 import android.adservices.adselection.AdSelectionResponse;
48 import android.adservices.adselection.CustomAudienceSignalsFixture;
49 import android.adservices.common.CommonFixture;
50 import android.adservices.common.FledgeErrorResponse;
51 import android.annotation.NonNull;
52 import android.content.Context;
53 import android.net.Uri;
54 import android.os.Process;
55 import android.os.RemoteException;
56 
57 import androidx.room.Room;
58 import androidx.test.core.app.ApplicationProvider;
59 
60 import com.android.adservices.concurrency.AdServicesExecutors;
61 import com.android.adservices.data.adselection.AdSelectionDatabase;
62 import com.android.adservices.data.adselection.AdSelectionEntryDao;
63 import com.android.adservices.data.adselection.CustomAudienceSignals;
64 import com.android.adservices.data.adselection.DBAdSelection;
65 import com.android.adservices.service.Flags;
66 import com.android.adservices.service.common.AdSelectionServiceFilter;
67 import com.android.adservices.service.common.Throttler;
68 import com.android.adservices.service.consent.ConsentManager;
69 import com.android.adservices.service.exception.FilterException;
70 import com.android.adservices.service.stats.AdServicesLogger;
71 import com.android.adservices.service.stats.AdServicesLoggerImpl;
72 import com.android.adservices.service.stats.AdServicesStatsLog;
73 import com.android.dx.mockito.inline.extended.ExtendedMockito;
74 
75 import com.google.common.util.concurrent.ListenableFuture;
76 import com.google.common.util.concurrent.ListeningExecutorService;
77 
78 import org.junit.After;
79 import org.junit.Before;
80 import org.junit.Test;
81 import org.mockito.ArgumentMatcher;
82 import org.mockito.Mock;
83 import org.mockito.Mockito;
84 import org.mockito.MockitoSession;
85 import org.mockito.quality.Strictness;
86 
87 import java.time.Instant;
88 import java.util.HashSet;
89 import java.util.List;
90 import java.util.concurrent.CountDownLatch;
91 import java.util.stream.Collectors;
92 
93 public class OutcomeSelectionRunnerTest {
94     private static final int CALLER_UID = Process.myUid();
95     private static final String MY_APP_PACKAGE_NAME = CommonFixture.TEST_PACKAGE_NAME;
96     private static final String ANOTHER_CALLER_PACKAGE_NAME = "another.caller.package";
97     private static final Uri RENDER_URI_1 = Uri.parse("https://www.domain.com/advert1/");
98     private static final Uri RENDER_URI_2 = Uri.parse("https://www.domain.com/advert2/");
99     private static final Uri RENDER_URI_3 = Uri.parse("https://www.domain.com/advert3/");
100     private static final long AD_SELECTION_ID_1 = 1;
101     private static final long AD_SELECTION_ID_2 = 2;
102     private static final long AD_SELECTION_ID_3 = 3;
103     private static final double BID_1 = 10.0;
104     private static final double BID_2 = 20.0;
105     private static final double BID_3 = 30.0;
106     private static final AdSelectionIdWithBidAndRenderUri AD_SELECTION_WITH_BID_1 =
107             AdSelectionIdWithBidAndRenderUri.builder()
108                     .setAdSelectionId(AD_SELECTION_ID_1)
109                     .setBid(BID_1)
110                     .setRenderUri(RENDER_URI_1)
111                     .build();
112     private static final AdSelectionIdWithBidAndRenderUri AD_SELECTION_WITH_BID_2 =
113             AdSelectionIdWithBidAndRenderUri.builder()
114                     .setAdSelectionId(AD_SELECTION_ID_2)
115                     .setBid(BID_2)
116                     .setRenderUri(RENDER_URI_2)
117                     .build();
118     private static final AdSelectionIdWithBidAndRenderUri AD_SELECTION_WITH_BID_3 =
119             AdSelectionIdWithBidAndRenderUri.builder()
120                     .setAdSelectionId(AD_SELECTION_ID_3)
121                     .setBid(BID_3)
122                     .setRenderUri(RENDER_URI_3)
123                     .build();
124 
125     private final Context mContext = ApplicationProvider.getApplicationContext();
126 
127     private AdSelectionEntryDao mAdSelectionEntryDao;
128     @Mock private AdOutcomeSelector mAdOutcomeSelectorMock;
129     private OutcomeSelectionRunner mOutcomeSelectionRunner;
130     private Flags mFlags = new OutcomeSelectionRunnerTestFlags();
131     private final AdServicesLogger mAdServicesLoggerMock =
132             ExtendedMockito.mock(AdServicesLoggerImpl.class);
133     private MockitoSession mStaticMockSession = null;
134     private ListeningExecutorService mBlockingExecutorService;
135 
136     @Mock private AdSelectionServiceFilter mAdSelectionServiceFilter;
137 
138     @Before
setup()139     public void setup() {
140         mBlockingExecutorService = AdServicesExecutors.getBlockingExecutor();
141         mStaticMockSession =
142                 ExtendedMockito.mockitoSession()
143                         //                        .spyStatic(JSScriptEngine.class)
144                         // mAdServicesLoggerMock is not referenced in many tests
145                         .strictness(Strictness.LENIENT)
146                         .initMocks(this)
147                         .startMocking();
148 
149         mAdSelectionEntryDao =
150                 Room.inMemoryDatabaseBuilder(
151                                 ApplicationProvider.getApplicationContext(),
152                                 AdSelectionDatabase.class)
153                         .build()
154                         .adSelectionEntryDao();
155 
156         mOutcomeSelectionRunner =
157                 new OutcomeSelectionRunner(
158                         CALLER_UID,
159                         mAdOutcomeSelectorMock,
160                         mAdSelectionEntryDao,
161                         mBlockingExecutorService,
162                         AdServicesExecutors.getLightWeightExecutor(),
163                         AdServicesExecutors.getScheduler(),
164                         mAdServicesLoggerMock,
165                         mContext,
166                         mFlags,
167                         mAdSelectionServiceFilter);
168 
169         doNothing()
170                 .when(mAdSelectionServiceFilter)
171                 .filterRequest(
172                         SAMPLE_SELLER,
173                         MY_APP_PACKAGE_NAME,
174                         true,
175                         true,
176                         CALLER_UID,
177                         AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN,
178                         Throttler.ApiKey.FLEDGE_API_SELECT_ADS);
179     }
180 
181     @After
tearDown()182     public void tearDown() {
183         if (mStaticMockSession != null) {
184             mStaticMockSession.finishMocking();
185         }
186     }
187 
188     @Test
testRunOutcomeSelectionInvalidAdSelectionConfigFromOutcomes()189     public void testRunOutcomeSelectionInvalidAdSelectionConfigFromOutcomes() {
190         List<AdSelectionIdWithBidAndRenderUri> AdSelectionIdWithBidAndRenderUris =
191                 List.of(AD_SELECTION_WITH_BID_1, AD_SELECTION_WITH_BID_2, AD_SELECTION_WITH_BID_3);
192         persistAdSelectionEntry(AdSelectionIdWithBidAndRenderUris.get(0), MY_APP_PACKAGE_NAME);
193         // Not persisting index 1
194         // Persisting index 2 with a different package name
195         persistAdSelectionEntry(
196                 AdSelectionIdWithBidAndRenderUris.get(2), ANOTHER_CALLER_PACKAGE_NAME);
197 
198         List<Long> adOutcomesConfigParam =
199                 AdSelectionIdWithBidAndRenderUris.stream()
200                         .map(AdSelectionIdWithBidAndRenderUri::getAdSelectionId)
201                         .collect(Collectors.toList());
202 
203         AdSelectionFromOutcomesConfig config =
204                 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig(
205                         adOutcomesConfigParam);
206 
207         AdSelectionTestCallback resultsCallback =
208                 invokeRunAdSelectionFromOutcomes(
209                         mOutcomeSelectionRunner, config, MY_APP_PACKAGE_NAME);
210 
211         verify(mAdOutcomeSelectorMock, never()).runAdOutcomeSelector(any(), any());
212         assertFalse(resultsCallback.mIsSuccess);
213         assertEquals(STATUS_INVALID_ARGUMENT, resultsCallback.mFledgeErrorResponse.getStatusCode());
214         verify(mAdServicesLoggerMock)
215                 .logFledgeApiCallStats(
216                         eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
217                         eq(STATUS_INVALID_ARGUMENT),
218                         anyInt());
219     }
220 
221     @Test
testRunOutcomeSelectionRevokedUserConsentEmptyResult()222     public void testRunOutcomeSelectionRevokedUserConsentEmptyResult() {
223         doThrow(new FilterException(new ConsentManager.RevokedConsentException()))
224                 .when(mAdSelectionServiceFilter)
225                 .filterRequest(
226                         SAMPLE_SELLER,
227                         MY_APP_PACKAGE_NAME,
228                         true,
229                         true,
230                         CALLER_UID,
231                         AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN,
232                         Throttler.ApiKey.FLEDGE_API_SELECT_ADS);
233 
234         List<AdSelectionIdWithBidAndRenderUri> adSelectionIdWithBidAndRenderUris =
235                 List.of(AD_SELECTION_WITH_BID_1, AD_SELECTION_WITH_BID_2, AD_SELECTION_WITH_BID_3);
236         for (AdSelectionIdWithBidAndRenderUri idWithBid : adSelectionIdWithBidAndRenderUris) {
237             persistAdSelectionEntry(idWithBid, MY_APP_PACKAGE_NAME);
238         }
239 
240         List<Long> adOutcomesConfigParam =
241                 adSelectionIdWithBidAndRenderUris.stream()
242                         .map(AdSelectionIdWithBidAndRenderUri::getAdSelectionId)
243                         .collect(Collectors.toList());
244 
245         AdSelectionFromOutcomesConfig config =
246                 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig(
247                         adOutcomesConfigParam);
248 
249         AdSelectionTestCallback resultsCallback =
250                 invokeRunAdSelectionFromOutcomes(
251                         mOutcomeSelectionRunner, config, MY_APP_PACKAGE_NAME);
252 
253         verify(mAdOutcomeSelectorMock, never()).runAdOutcomeSelector(any(), any());
254         assertTrue(resultsCallback.mIsSuccess);
255         assertNull(resultsCallback.mAdSelectionResponse);
256 
257         // Confirm a duplicate log entry does not exist.
258         // AdSelectionServiceFilter ensures the failing assertion is logged internally.
259         verify(mAdServicesLoggerMock, never())
260                 .logFledgeApiCallStats(
261                         eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
262                         eq(STATUS_USER_CONSENT_REVOKED),
263                         anyInt());
264     }
265 
266     @Test
testRunOutcomeSelectionOrchestrationTimeoutFailure()267     public void testRunOutcomeSelectionOrchestrationTimeoutFailure() {
268         mFlags =
269                 new Flags() {
270                     @Override
271                     public long getAdSelectionSelectingOutcomeTimeoutMs() {
272                         return 300;
273                     }
274 
275                     @Override
276                     public boolean getDisableFledgeEnrollmentCheck() {
277                         return true;
278                     }
279 
280                     @Override
281                     public long getAdSelectionFromOutcomesOverallTimeoutMs() {
282                         return 100;
283                     }
284                 };
285 
286         List<AdSelectionIdWithBidAndRenderUri> adSelectionIdWithBidAndRenderUris =
287                 List.of(AD_SELECTION_WITH_BID_1, AD_SELECTION_WITH_BID_2, AD_SELECTION_WITH_BID_3);
288         for (AdSelectionIdWithBidAndRenderUri idWithBid : adSelectionIdWithBidAndRenderUris) {
289             persistAdSelectionEntry(idWithBid, MY_APP_PACKAGE_NAME);
290         }
291 
292         List<Long> adOutcomesConfigParam =
293                 adSelectionIdWithBidAndRenderUris.stream()
294                         .map(AdSelectionIdWithBidAndRenderUri::getAdSelectionId)
295                         .collect(Collectors.toList());
296 
297         AdSelectionFromOutcomesConfig config =
298                 AdSelectionFromOutcomesConfigFixture.anAdSelectionFromOutcomesConfig(
299                         adOutcomesConfigParam);
300 
301         GenericListMatcher matcher = new GenericListMatcher(adSelectionIdWithBidAndRenderUris);
302         doAnswer((ignored) -> getSelectedOutcomeWithDelay(AD_SELECTION_ID_1, mFlags))
303                 .when(mAdOutcomeSelectorMock)
304                 .runAdOutcomeSelector(argThat(matcher), eq(config));
305 
306         OutcomeSelectionRunner outcomeSelectionRunner =
307                 new OutcomeSelectionRunner(
308                         CALLER_UID,
309                         mAdOutcomeSelectorMock,
310                         mAdSelectionEntryDao,
311                         mBlockingExecutorService,
312                         AdServicesExecutors.getLightWeightExecutor(),
313                         AdServicesExecutors.getScheduler(),
314                         mAdServicesLoggerMock,
315                         mContext,
316                         mFlags,
317                         mAdSelectionServiceFilter);
318 
319         AdSelectionTestCallback resultsCallback =
320                 invokeRunAdSelectionFromOutcomes(
321                         outcomeSelectionRunner, config, MY_APP_PACKAGE_NAME);
322 
323         verify(mAdOutcomeSelectorMock, Mockito.times(1)).runAdOutcomeSelector(any(), any());
324         assertFalse(resultsCallback.mIsSuccess);
325         assertNotNull(resultsCallback.mFledgeErrorResponse);
326         assertEquals(STATUS_TIMEOUT, resultsCallback.mFledgeErrorResponse.getStatusCode());
327         verify(mAdServicesLoggerMock)
328                 .logFledgeApiCallStats(
329                         eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
330                         eq(STATUS_TIMEOUT),
331                         anyInt());
332     }
333 
persistAdSelectionEntry( AdSelectionIdWithBidAndRenderUri idWithBidAndRenderUri, String callerPackageName)334     private void persistAdSelectionEntry(
335             AdSelectionIdWithBidAndRenderUri idWithBidAndRenderUri, String callerPackageName) {
336         final Uri biddingLogicUri1 = Uri.parse("https://www.domain.com/logic/1");
337         final Instant activationTime = Instant.now();
338         final String contextualSignals = "contextual_signals";
339         final CustomAudienceSignals customAudienceSignals =
340                 CustomAudienceSignalsFixture.aCustomAudienceSignals();
341 
342         final DBAdSelection dbAdSelectionEntry =
343                 new DBAdSelection.Builder()
344                         .setAdSelectionId(idWithBidAndRenderUri.getAdSelectionId())
345                         .setCustomAudienceSignals(customAudienceSignals)
346                         .setContextualSignals(contextualSignals)
347                         .setBiddingLogicUri(biddingLogicUri1)
348                         .setWinningAdRenderUri(idWithBidAndRenderUri.getRenderUri())
349                         .setWinningAdBid(idWithBidAndRenderUri.getBid())
350                         .setCreationTimestamp(activationTime)
351                         .setCallerPackageName(callerPackageName)
352                         .build();
353         mAdSelectionEntryDao.persistAdSelection(dbAdSelectionEntry);
354     }
355 
invokeRunAdSelectionFromOutcomes( OutcomeSelectionRunner outcomeSelectionRunner, AdSelectionFromOutcomesConfig config, String callerPackageName)356     private OutcomeSelectionRunnerTest.AdSelectionTestCallback invokeRunAdSelectionFromOutcomes(
357             OutcomeSelectionRunner outcomeSelectionRunner,
358             AdSelectionFromOutcomesConfig config,
359             String callerPackageName) {
360 
361         // Counted down in the callback
362         CountDownLatch countDownLatch = new CountDownLatch(1);
363         OutcomeSelectionRunnerTest.AdSelectionTestCallback adSelectionTestCallback =
364                 new OutcomeSelectionRunnerTest.AdSelectionTestCallback(countDownLatch);
365 
366         AdSelectionFromOutcomesInput input =
367                 new AdSelectionFromOutcomesInput.Builder()
368                         .setAdSelectionFromOutcomesConfig(config)
369                         .setCallerPackageName(callerPackageName)
370                         .build();
371 
372         outcomeSelectionRunner.runOutcomeSelection(input, adSelectionTestCallback);
373         try {
374             adSelectionTestCallback.mCountDownLatch.await();
375         } catch (InterruptedException e) {
376             e.printStackTrace();
377         }
378         return adSelectionTestCallback;
379     }
380 
getSelectedOutcomeWithDelay( Long outcomeId, @NonNull Flags flags)381     private ListenableFuture<Long> getSelectedOutcomeWithDelay(
382             Long outcomeId, @NonNull Flags flags) {
383         return mBlockingExecutorService.submit(
384                 () -> {
385                     Thread.sleep(2 * flags.getAdSelectionFromOutcomesOverallTimeoutMs());
386                     return outcomeId;
387                 });
388     }
389 
390     static class AdSelectionTestCallback extends AdSelectionCallback.Stub {
391 
392         final CountDownLatch mCountDownLatch;
393         boolean mIsSuccess = false;
394         AdSelectionResponse mAdSelectionResponse;
395         FledgeErrorResponse mFledgeErrorResponse;
396 
AdSelectionTestCallback(CountDownLatch countDownLatch)397         AdSelectionTestCallback(CountDownLatch countDownLatch) {
398             mCountDownLatch = countDownLatch;
399             mAdSelectionResponse = null;
400             mFledgeErrorResponse = null;
401         }
402 
403         @Override
onSuccess(AdSelectionResponse adSelectionResponse)404         public void onSuccess(AdSelectionResponse adSelectionResponse) throws RemoteException {
405             mIsSuccess = true;
406             mAdSelectionResponse = adSelectionResponse;
407             mCountDownLatch.countDown();
408         }
409 
410         @Override
onFailure(FledgeErrorResponse fledgeErrorResponse)411         public void onFailure(FledgeErrorResponse fledgeErrorResponse) throws RemoteException {
412             mIsSuccess = false;
413             mFledgeErrorResponse = fledgeErrorResponse;
414             mCountDownLatch.countDown();
415         }
416     }
417 
418     static class GenericListMatcher
419             implements ArgumentMatcher<List<AdSelectionIdWithBidAndRenderUri>> {
420         private final List<AdSelectionIdWithBidAndRenderUri> mTruth;
421 
GenericListMatcher(List<AdSelectionIdWithBidAndRenderUri> truth)422         GenericListMatcher(List<AdSelectionIdWithBidAndRenderUri> truth) {
423             this.mTruth = truth;
424         }
425 
426         @Override
matches(List<AdSelectionIdWithBidAndRenderUri> argument)427         public boolean matches(List<AdSelectionIdWithBidAndRenderUri> argument) {
428             return mTruth.size() == argument.size()
429                     && new HashSet<>(mTruth).equals(new HashSet<>(argument));
430         }
431     }
432 
433     private static class OutcomeSelectionRunnerTestFlags implements Flags {
434         @Override
getAdSelectionSelectingOutcomeTimeoutMs()435         public long getAdSelectionSelectingOutcomeTimeoutMs() {
436             return EXTENDED_FLEDGE_AD_SELECTION_SELECTING_OUTCOME_TIMEOUT_MS;
437         }
438 
439         @Override
getAdSelectionFromOutcomesOverallTimeoutMs()440         public long getAdSelectionFromOutcomesOverallTimeoutMs() {
441             return EXTENDED_FLEDGE_AD_SELECTION_FROM_OUTCOMES_OVERALL_TIMEOUT_MS;
442         }
443     }
444 }
445