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 android.adservices.utils;
18 
19 import static android.adservices.adselection.ReportEventRequest.FLAG_REPORTING_DESTINATION_BUYER;
20 import static android.adservices.adselection.ReportEventRequest.FLAG_REPORTING_DESTINATION_SELLER;
21 
22 import static com.android.adservices.service.FlagsConstants.KEY_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_CA_MS;
23 import static com.android.adservices.service.FlagsConstants.KEY_FLEDGE_AD_SELECTION_OVERALL_TIMEOUT_MS;
24 import static com.android.adservices.service.FlagsConstants.KEY_FLEDGE_AD_SELECTION_SCORING_TIMEOUT_MS;
25 
26 import android.Manifest;
27 import android.adservices.adselection.AdSelectionConfig;
28 import android.adservices.adselection.AdSelectionOutcome;
29 import android.adservices.adselection.ReportEventRequest;
30 import android.adservices.adselection.ReportImpressionRequest;
31 import android.adservices.clients.adselection.AdSelectionClient;
32 import android.adservices.clients.customaudience.AdvertisingCustomAudienceClient;
33 import android.adservices.common.AdData;
34 import android.adservices.common.AdSelectionSignals;
35 import android.adservices.common.AdTechIdentifier;
36 import android.adservices.common.CommonFixture;
37 import android.adservices.customaudience.CustomAudience;
38 import android.adservices.customaudience.JoinCustomAudienceRequest;
39 import android.adservices.customaudience.ScheduleCustomAudienceUpdateRequest;
40 import android.adservices.customaudience.TrustedBiddingData;
41 import android.content.Context;
42 import android.net.Uri;
43 import android.util.Log;
44 
45 import androidx.test.core.app.ApplicationProvider;
46 import androidx.test.platform.app.InstrumentationRegistry;
47 
48 import com.android.adservices.common.AdServicesCtsTestCase;
49 import com.android.adservices.common.AdServicesDeviceSupportedRule;
50 import com.android.adservices.common.AdServicesFlagsSetterRule;
51 import com.android.adservices.common.AdservicesTestHelper;
52 import com.android.adservices.service.PhFlagsFixture;
53 import com.android.adservices.shared.testing.SdkLevelSupportRule;
54 import com.android.adservices.shared.testing.SupportedByConditionRule;
55 import com.android.compatibility.common.util.ShellUtils;
56 
57 import com.google.common.collect.ImmutableList;
58 import com.google.common.collect.ImmutableMap;
59 
60 import org.json.JSONException;
61 import org.json.JSONObject;
62 import org.junit.After;
63 import org.junit.Before;
64 import org.junit.Rule;
65 
66 import java.net.URL;
67 import java.time.Instant;
68 import java.time.temporal.ChronoUnit;
69 import java.util.Locale;
70 import java.util.concurrent.ExecutionException;
71 import java.util.concurrent.ExecutorService;
72 import java.util.concurrent.Executors;
73 import java.util.concurrent.TimeUnit;
74 import java.util.concurrent.TimeoutException;
75 
76 /** Abstract class for FLEDGE scenario tests using local servers. */
77 public abstract class FledgeScenarioTest extends AdServicesCtsTestCase {
78     protected static final Context sContext = ApplicationProvider.getApplicationContext();
79 
80     protected static final String TAG = FledgeScenarioTest.class.getSimpleName();
81     protected static final int TIMEOUT = 120;
82     protected static final String SHOES_CA = "shoes";
83     protected static final String SHIRTS_CA = "shirts";
84     private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
85     private static final int NUM_ADS_PER_AUDIENCE = 4;
86     private static final String PACKAGE_NAME = CommonFixture.TEST_PACKAGE_NAME;
87     private static final long AD_ID_FETCHER_TIMEOUT = 1000;
88     private static final long AD_ID_FETCHER_TIMEOUT_DEFAULT = 50;
89 
90     protected AdvertisingCustomAudienceClient mCustomAudienceClient;
91     protected AdSelectionClient mAdSelectionClient;
92 
93     private AdTechIdentifier mBuyer;
94     private AdTechIdentifier mSeller;
95     private String mServerBaseAddress;
96 
97 
98     @Rule(order = 0)
99     public final SdkLevelSupportRule sdkLevel = SdkLevelSupportRule.forAtLeastS();
100 
101     @Rule(order = 1)
102     public final SupportedByConditionRule devOptionsEnabled =
103             DevContextUtils.createDevOptionsAvailableRule(sContext, TAG);
104 
105     @Rule(order = 5)
106     public final AdServicesDeviceSupportedRule deviceSupported =
107             new AdServicesDeviceSupportedRule();
108 
109     @Rule(order = 3)
110     public final SupportedByConditionRule webViewSupportsJSSandbox =
111             CtsWebViewSupportUtil.createJSSandboxAvailableRule(CONTEXT);
112 
113     @Rule(order = 6)
114     public MockWebServerRule mMockWebServerRule =
115             MockWebServerRule.forHttps(
116                     CONTEXT, "adservices_untrusted_test_server.p12", "adservices_test");
117 
overrideBiddingLogicVersionToV3(boolean useVersion3)118     protected static void overrideBiddingLogicVersionToV3(boolean useVersion3) {
119         ShellUtils.runShellCommand(
120                 "device_config put adservices fledge_ad_selection_bidding_logic_js_version %s",
121                 useVersion3 ? "3" : "2");
122     }
123 
makeAdSelectionSignals()124     protected static AdSelectionSignals makeAdSelectionSignals() {
125         return AdSelectionSignals.fromString(
126                 String.format("{\"valid\": true, \"publisher\": \"%s\"}", PACKAGE_NAME));
127     }
128 
129     @Override
getAdServicesFlagsSetterRule()130     protected AdServicesFlagsSetterRule getAdServicesFlagsSetterRule() {
131         return AdServicesFlagsSetterRule.forAllApisEnabledTests()
132                 .setCompatModeFlags()
133                 .setPpapiAppAllowList(sContext.getPackageName())
134                 .setFlag(KEY_FLEDGE_AD_SELECTION_BIDDING_TIMEOUT_PER_CA_MS, 5_000)
135                 .setFlag(KEY_FLEDGE_AD_SELECTION_SCORING_TIMEOUT_MS, 5_000)
136                 .setFlag(KEY_FLEDGE_AD_SELECTION_OVERALL_TIMEOUT_MS, 10_000);
137     }
138 
139     @Before
setUp()140     public void setUp() throws Exception {
141         InstrumentationRegistry.getInstrumentation()
142                 .getUiAutomation()
143                 .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG);
144 
145         AdservicesTestHelper.killAdservicesProcess(sContext);
146         ExecutorService executor = Executors.newCachedThreadPool();
147         mCustomAudienceClient =
148                 new AdvertisingCustomAudienceClient.Builder()
149                         .setContext(CONTEXT)
150                         .setExecutor(executor)
151                         .build();
152         mAdSelectionClient =
153                 new AdSelectionClient.Builder().setContext(CONTEXT).setExecutor(executor).build();
154     }
155 
156     @After
tearDown()157     public final void tearDown() throws Exception {
158         try {
159             leaveCustomAudience(SHOES_CA);
160             leaveCustomAudience(SHIRTS_CA);
161         } catch (Exception e) {
162             // No-op catch here, these are only for cleaning up
163             Log.w(TAG, "Failed while cleaning up custom audiences", e);
164         }
165     }
166 
doSelectAds(AdSelectionConfig adSelectionConfig)167     protected AdSelectionOutcome doSelectAds(AdSelectionConfig adSelectionConfig)
168             throws ExecutionException, InterruptedException, TimeoutException {
169         AdSelectionOutcome result =
170                 mAdSelectionClient.selectAds(adSelectionConfig).get(TIMEOUT, TimeUnit.SECONDS);
171         Log.d(TAG, "Ran ad selection.");
172         return result;
173     }
174 
doReportEvent(long adSelectionId, String eventName)175     protected void doReportEvent(long adSelectionId, String eventName)
176             throws JSONException, ExecutionException, InterruptedException, TimeoutException {
177         mAdSelectionClient
178                 .reportEvent(
179                         new ReportEventRequest.Builder(
180                                         adSelectionId,
181                                         eventName,
182                                         new JSONObject().put("key", "value").toString(),
183                                         FLAG_REPORTING_DESTINATION_SELLER
184                                                 | FLAG_REPORTING_DESTINATION_BUYER)
185                                 .build())
186                 .get(TIMEOUT, TimeUnit.SECONDS);
187         Log.d(TAG, "Ran report ad click for ad selection id: " + adSelectionId);
188     }
189 
doReportImpression(long adSelectionId, AdSelectionConfig adSelectionConfig)190     protected void doReportImpression(long adSelectionId, AdSelectionConfig adSelectionConfig)
191             throws ExecutionException, InterruptedException, TimeoutException {
192         mAdSelectionClient
193                 .reportImpression(new ReportImpressionRequest(adSelectionId, adSelectionConfig))
194                 .get(TIMEOUT, TimeUnit.SECONDS);
195         Log.d(TAG, "Ran report impression for ad selection id: " + adSelectionId);
196     }
197 
joinCustomAudience(String customAudienceName)198     protected void joinCustomAudience(String customAudienceName)
199             throws ExecutionException, InterruptedException, TimeoutException {
200         JoinCustomAudienceRequest joinCustomAudienceRequest =
201                 makeJoinCustomAudienceRequest(customAudienceName);
202         mCustomAudienceClient
203                 .joinCustomAudience(joinCustomAudienceRequest.getCustomAudience())
204                 .get(5, TimeUnit.SECONDS);
205         Log.d(TAG, "Joined Custom Audience: " + customAudienceName);
206     }
207 
joinCustomAudience(CustomAudience customAudience)208     protected void joinCustomAudience(CustomAudience customAudience)
209             throws ExecutionException, InterruptedException, TimeoutException {
210         mCustomAudienceClient.joinCustomAudience(customAudience).get(5, TimeUnit.SECONDS);
211         Log.d(TAG, "Joined Custom Audience: " + customAudience.getName());
212     }
213 
leaveCustomAudience(String customAudienceName)214     protected void leaveCustomAudience(String customAudienceName)
215             throws ExecutionException, InterruptedException, TimeoutException {
216         CustomAudience customAudience = makeCustomAudience(customAudienceName).build();
217         mCustomAudienceClient
218                 .leaveCustomAudience(customAudience.getBuyer(), customAudience.getName())
219                 .get(TIMEOUT, TimeUnit.SECONDS);
220         Log.d(TAG, "Left Custom Audience: " + customAudienceName);
221     }
222 
doScheduleCustomAudienceUpdate(ScheduleCustomAudienceUpdateRequest request)223     protected void doScheduleCustomAudienceUpdate(ScheduleCustomAudienceUpdateRequest request)
224             throws ExecutionException, InterruptedException, TimeoutException {
225         mCustomAudienceClient.scheduleCustomAudienceUpdate(request).get(TIMEOUT, TimeUnit.SECONDS);
226         Log.d(TAG, "Scheduled Custom Audience Update: " + request);
227     }
228 
getServerBaseAddress()229     protected String getServerBaseAddress() {
230         return mServerBaseAddress;
231     }
232 
overrideCpcBillingEnabled(boolean enabled)233     protected void overrideCpcBillingEnabled(boolean enabled) {
234         ShellUtils.runShellCommand(
235                 String.format(
236                         "device_config put adservices fledge_cpc_billing_enabled %s",
237                         enabled ? "true" : "false"));
238     }
239 
overrideRegisterAdBeaconEnabled(boolean enabled)240     protected void overrideRegisterAdBeaconEnabled(boolean enabled) {
241         ShellUtils.runShellCommand(
242                 String.format(
243                         "device_config put adservices fledge_register_ad_beacon_enabled %s",
244                         enabled ? "true" : "false"));
245     }
246 
overrideShouldUseUnifiedTable(boolean shouldUse)247     protected void overrideShouldUseUnifiedTable(boolean shouldUse) {
248         ShellUtils.runShellCommand(
249                 String.format(
250                         "device_config put adservices"
251                                 + " fledge_on_device_auction_should_use_unified_tables %s",
252                         shouldUse ? "true" : "false"));
253     }
254 
setDebugReportingEnabledForTesting(boolean enabled)255     protected void setDebugReportingEnabledForTesting(boolean enabled) {
256         FledgeScenarioTest.overrideBiddingLogicVersionToV3(enabled);
257         PhFlagsFixture.overrideAdIdFetcherTimeoutMs(
258                 enabled ? AD_ID_FETCHER_TIMEOUT : AD_ID_FETCHER_TIMEOUT_DEFAULT);
259         ShellUtils.runShellCommand(
260                 String.format(
261                         "device_config put adservices fledge_event_level_debug_reporting_enabled"
262                                 + " %s",
263                         enabled ? "true" : "false"));
264         ShellUtils.runShellCommand(
265                 String.format(
266                         "device_config put adservices"
267                                 + " fledge_event_level_debug_report_send_immediately %s",
268                         enabled ? "true" : "false"));
269     }
270 
makeAdSelectionConfig(URL serverBaseAddressWithPrefix)271     protected AdSelectionConfig makeAdSelectionConfig(URL serverBaseAddressWithPrefix) {
272         AdSelectionSignals signals = FledgeScenarioTest.makeAdSelectionSignals();
273         Log.d(TAG, "Ad tech buyer: " + mBuyer);
274         Log.d(TAG, "Ad tech seller: " + mSeller);
275         return new AdSelectionConfig.Builder()
276                 .setSeller(mSeller)
277                 .setPerBuyerSignals(ImmutableMap.of(mBuyer, signals))
278                 .setCustomAudienceBuyers(ImmutableList.of(mBuyer))
279                 .setAdSelectionSignals(signals)
280                 .setSellerSignals(signals)
281                 .setDecisionLogicUri(
282                         Uri.parse(serverBaseAddressWithPrefix + Scenarios.SCORING_LOGIC_PATH))
283                 .setTrustedScoringSignalsUri(
284                         Uri.parse(serverBaseAddressWithPrefix + Scenarios.SCORING_SIGNALS_PATH))
285                 .build();
286     }
287 
setupDispatcher( ScenarioDispatcherFactory scenarioDispatcherFactory)288     protected ScenarioDispatcher setupDispatcher(
289             ScenarioDispatcherFactory scenarioDispatcherFactory) throws Exception {
290         ScenarioDispatcher scenarioDispatcher =
291                 mMockWebServerRule.startMockWebServer(scenarioDispatcherFactory);
292         mServerBaseAddress = scenarioDispatcher.getBaseAddressWithPrefix().toString();
293         mBuyer =
294                 AdTechIdentifier.fromString(
295                         scenarioDispatcher.getBaseAddressWithPrefix().getHost());
296         mSeller =
297                 AdTechIdentifier.fromString(
298                         scenarioDispatcher.getBaseAddressWithPrefix().getHost());
299         Log.d(TAG, "Started default MockWebServer.");
300         return scenarioDispatcher;
301     }
302 
makeJoinCustomAudienceRequest(String customAudienceName)303     private JoinCustomAudienceRequest makeJoinCustomAudienceRequest(String customAudienceName) {
304         return new JoinCustomAudienceRequest.Builder()
305                 .setCustomAudience(makeCustomAudience(customAudienceName).build())
306                 .build();
307     }
308 
makeCustomAudience(String customAudienceName)309     protected CustomAudience.Builder makeCustomAudience(String customAudienceName) {
310         Uri trustedBiddingUri = Uri.parse(mServerBaseAddress + Scenarios.BIDDING_SIGNALS_PATH);
311         Uri dailyUpdateUri =
312                 Uri.parse(mServerBaseAddress + Scenarios.getDailyUpdatePath(customAudienceName));
313         return new CustomAudience.Builder()
314                 .setName(customAudienceName)
315                 .setDailyUpdateUri(dailyUpdateUri)
316                 .setTrustedBiddingData(
317                         new TrustedBiddingData.Builder()
318                                 .setTrustedBiddingKeys(ImmutableList.of())
319                                 .setTrustedBiddingUri(trustedBiddingUri)
320                                 .build())
321                 .setUserBiddingSignals(AdSelectionSignals.fromString("{}"))
322                 .setAds(makeAds(customAudienceName))
323                 .setBiddingLogicUri(
324                         Uri.parse(String.format(mServerBaseAddress + Scenarios.BIDDING_LOGIC_PATH)))
325                 .setBuyer(mBuyer)
326                 .setActivationTime(Instant.now())
327                 .setExpirationTime(Instant.now().plus(5, ChronoUnit.DAYS));
328     }
329 
makeAds(String customAudienceName)330     private ImmutableList<AdData> makeAds(String customAudienceName) {
331         ImmutableList.Builder<AdData> ads = new ImmutableList.Builder<>();
332         for (int i = 0; i < NUM_ADS_PER_AUDIENCE; i++) {
333             ads.add(makeAd(/* adNumber= */ i, customAudienceName));
334         }
335         return ads.build();
336     }
337 
makeAd(int adNumber, String customAudienceName)338     private AdData makeAd(int adNumber, String customAudienceName) {
339         return new AdData.Builder()
340                 .setMetadata(
341                         String.format(
342                                 Locale.ENGLISH,
343                                 "{\"bid\": 5, \"ad_number\": %d, \"target\": \"%s\"}",
344                                 adNumber,
345                                 PACKAGE_NAME))
346                 .setRenderUri(
347                         Uri.parse(
348                                 String.format(
349                                         "%s/render/%s/%s",
350                                         mServerBaseAddress, customAudienceName, adNumber)))
351                 .build();
352     }
353 }
354