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