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