Merge new FLEDGE and appsetid tests to androidx-main

Bug: 266240773
Test: Ran all tests locally
Change-Id: I7c72185c03cd5afedbc308cd87843fe644f3c888
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/AndroidManifest.xml b/privacysandbox/ads/ads-adservices-java/src/androidTest/AndroidManifest.xml
index faff43e..90665d4 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/AndroidManifest.xml
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/AndroidManifest.xml
@@ -18,6 +18,7 @@
     <uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
     <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
     <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
+    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE" />
     <application>
         <property android:name="android.adservices.AD_SERVICES_CONFIG"
             android:resource="@xml/ad_services_config" />
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java
new file mode 100644
index 0000000..d1563e8
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java
@@ -0,0 +1,910 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.java.endtoend;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.RequiresApi;
+import androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig;
+import androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager;
+import androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome;
+import androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest;
+import androidx.privacysandbox.ads.adservices.common.AdData;
+import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals;
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier;
+import androidx.privacysandbox.ads.adservices.customaudience.CustomAudience;
+import androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest;
+import androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData;
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo;
+import androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures;
+import androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import kotlin.Unit;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@SdkSuppress(minSdkVersion = 26)
+public class FledgeCtsDebuggableTest {
+    protected static final Context sContext = ApplicationProvider.getApplicationContext();
+    private static final String TAG = "FledgeCtsDebuggableTest";
+
+    // Configuration constants
+    private static final int SDK_MAX_REQUEST_PERMITS_PER_SECOND = 1000;
+    private static final String DISABLE_MEASUREMENT_ENROLLMENT_CHECK = "1";
+
+    // Time allowed by current test setup for APIs to respond
+    private static final int API_RESPONSE_TIMEOUT_SECONDS = 5;
+
+    // This is used to check actual API timeout conditions; note that the default overall timeout
+    // for ad selection is 10 seconds
+    private static final int API_RESPONSE_LONGER_TIMEOUT_SECONDS = 12;
+
+    private static final AdTechIdentifier SELLER = new AdTechIdentifier("performance-fledge"
+            + "-static-5jyy5ulagq-uc.a.run.app");
+
+    private static final AdTechIdentifier BUYER_1 = new AdTechIdentifier("performance-fledge"
+            + "-static-5jyy5ulagq-uc.a.run.app");
+    private static final AdTechIdentifier BUYER_2 = new AdTechIdentifier("performance-fledge"
+            + "-static-2-5jyy5ulagq-uc.a.run.app");
+
+    private static final AdSelectionSignals AD_SELECTION_SIGNALS =
+            new AdSelectionSignals("{\"ad_selection_signals\":1}");
+
+    private static final AdSelectionSignals SELLER_SIGNALS =
+            new AdSelectionSignals("{\"test_seller_signals\":1}");
+
+    private static final Map<AdTechIdentifier, AdSelectionSignals> PER_BUYER_SIGNALS =
+            new HashMap<>();
+    static {
+        PER_BUYER_SIGNALS.put(BUYER_1,
+                new AdSelectionSignals("{\"buyer_signals\":1}"));
+        PER_BUYER_SIGNALS.put(BUYER_2,
+                new AdSelectionSignals("{\"buyer_signals\":2}"));
+    }
+
+    private static final String VALID_TRUSTED_BIDDING_URI_PATH = "/trusted/biddingsignals/simple";
+
+    private static final ImmutableList<String> VALID_TRUSTED_BIDDING_KEYS =
+            ImmutableList.of("key1", "key2");
+
+    private static final AdSelectionSignals VALID_USER_BIDDING_SIGNALS =
+            new AdSelectionSignals("{'valid': 'yep', 'opaque': 'definitely'}");
+
+    private static final long FLEDGE_CUSTOM_AUDIENCE_DEFAULT_EXPIRE_IN_MS =
+            60L * 24L * 60L * 60L * 1000L; // 60 days
+    private static final long FLEDGE_CUSTOM_AUDIENCE_MAX_ACTIVATION_DELAY_IN_MS =
+            60L * 24L * 60L * 60L * 1000L; // 60 days
+
+    private static final long DAY_IN_SECONDS = 60 * 60 * 24;
+
+    private static final String VALID_NAME = "testCustomAudienceName";
+
+    private static final String AD_URI_PREFIX = "/adverts/123/";
+
+    private static final String SELLER_DECISION_LOGIC_URI_PATH = "/seller/decision/simple_logic";
+    private static final String BUYER_BIDDING_LOGIC_URI_PATH = "/buyer/bidding/simple_logic";
+    private static final String SELLER_TRUSTED_SIGNAL_URI_PATH = "/trusted/scoringsignals/simple";
+    // To mock malformed logic, use paths that return an empty response
+    private static final String SELLER_MALFORMED_DECISION_LOGIC_URI_PATH = "/reporting/seller";
+    private static final String BUYER_MALFORMED_BIDDING_LOGIC_URI_PATH = "/reporting/buyer";
+
+    private static final AdSelectionConfig DEFAULT_AD_SELECTION_CONFIG = new AdSelectionConfig(
+            SELLER,
+            Uri.parse(
+                    String.format(
+                            "https://%s%s",
+                            SELLER,
+                            SELLER_DECISION_LOGIC_URI_PATH)),
+            Arrays.asList(BUYER_1, BUYER_2),
+            AD_SELECTION_SIGNALS,
+            SELLER_SIGNALS,
+            PER_BUYER_SIGNALS,
+            Uri.parse(
+                    String.format(
+                            "https://%s%s",
+                            SELLER,
+                            SELLER_TRUSTED_SIGNAL_URI_PATH)));
+
+    private AdSelectionClient mAdSelectionClient;
+    private CustomAudienceClient mCustomAudienceClient;
+
+    @BeforeClass
+    public static void configure() {
+        TestUtil testUtil = new TestUtil(InstrumentationRegistry.getInstrumentation(), TAG);
+
+        testUtil.overrideAdIdKillSwitch(true);
+        testUtil.overrideAppSetIdKillSwitch(true);
+        testUtil.overrideKillSwitches(true);
+        testUtil.overrideAllowlists(true);
+        testUtil.overrideConsentManagerDebugMode(true);
+        testUtil.overrideMeasurementKillSwitches(true);
+        testUtil.overrideDisableMeasurementEnrollmentCheck(DISABLE_MEASUREMENT_ENROLLMENT_CHECK);
+        testUtil.enableEnrollmentCheck(true);
+        testUtil.overrideFledgeSelectAdsKillSwitch(true);
+        testUtil.overrideFledgeCustomAudienceServiceKillSwitch(true);
+        testUtil.overrideSdkRequestPermitsPerSecond(SDK_MAX_REQUEST_PERMITS_PER_SECOND);
+        testUtil.disableDeviceConfigSyncForTests(true);
+        testUtil.disableFledgeEnrollmentCheck(true);
+        testUtil.enableAdServiceSystemService(true);
+        testUtil.enforceFledgeJsIsolateMaxHeapSize(false);
+    }
+
+    @AfterClass
+    public static void resetConfiguration() {
+        TestUtil testUtil = new TestUtil(InstrumentationRegistry.getInstrumentation(), TAG);
+
+        testUtil.overrideAdIdKillSwitch(false);
+        testUtil.overrideAppSetIdKillSwitch(false);
+        testUtil.overrideKillSwitches(false);
+        testUtil.overrideAllowlists(false);
+        testUtil.overrideConsentManagerDebugMode(false);
+        testUtil.overrideMeasurementKillSwitches(false);
+        testUtil.resetOverrideDisableMeasurementEnrollmentCheck();
+        testUtil.enableEnrollmentCheck(false);
+        testUtil.overrideFledgeSelectAdsKillSwitch(true);
+        testUtil.overrideFledgeCustomAudienceServiceKillSwitch(true);
+        testUtil.overrideSdkRequestPermitsPerSecond(1);
+        testUtil.disableDeviceConfigSyncForTests(false);
+        testUtil.disableFledgeEnrollmentCheck(false);
+        testUtil.enableAdServiceSystemService(false);
+        testUtil.enforceFledgeJsIsolateMaxHeapSize(true);
+    }
+
+    @Before
+    public void setup() throws IOException {
+        mAdSelectionClient =
+                new AdSelectionClient(sContext);
+        mCustomAudienceClient =
+                new CustomAudienceClient(sContext);
+    }
+
+    @Test
+    public void testFledgeAuctionSelectionFlow_overall_Success() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        CustomAudience customAudience2 = createCustomAudience(BUYER_2, bidsForBuyer2);
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Running ad selection and asserting that the outcome is returned in < 10 seconds
+        AdSelectionOutcome outcome =
+                mAdSelectionClient
+                        .selectAds(DEFAULT_AD_SELECTION_CONFIG)
+                        .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Assert that ad3 from BUYER_2 is rendered, since it had the highest bid and score
+        Assert.assertEquals(
+                getUri(BUYER_2, AD_URI_PREFIX + "/ad3"), outcome.getRenderUri());
+
+        ReportImpressionRequest reportImpressionRequest =
+                new ReportImpressionRequest(outcome.getAdSelectionId(),
+                        DEFAULT_AD_SELECTION_CONFIG);
+
+        // Performing reporting, and asserting that no exception is thrown
+        mAdSelectionClient
+                .reportImpression(reportImpressionRequest)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testAdSelection_etldViolation_failure() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        CustomAudience customAudience2 = createCustomAudience(BUYER_2, bidsForBuyer2);
+
+        AdSelectionConfig adSelectionConfigWithEtldViolations =
+                new AdSelectionConfig(
+                        SELLER,
+                        Uri.parse(
+                                String.format(
+                                        "https://%s%s",
+                                        SELLER + "etld_noise",
+                                        SELLER_DECISION_LOGIC_URI_PATH)),
+                        Arrays.asList(BUYER_1, BUYER_2),
+                        AD_SELECTION_SIGNALS,
+                        SELLER_SIGNALS,
+                        PER_BUYER_SIGNALS,
+                        Uri.parse(
+                                String.format(
+                                        "https://%s%s",
+                                        SELLER + "etld_noise",
+                                        SELLER_TRUSTED_SIGNAL_URI_PATH)));
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Running ad selection and asserting that exception is thrown when decision and signals
+        // URIs are not etld+1 compliant
+        Exception selectAdsException =
+                assertThrows(
+                        ExecutionException.class,
+                        () ->
+                                mAdSelectionClient
+                                        .selectAds(adSelectionConfigWithEtldViolations)
+                                        .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+        assertThat(selectAdsException.getCause()).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testReportImpression_etldViolation_failure() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        CustomAudience customAudience2 = createCustomAudience(BUYER_2, bidsForBuyer2);
+
+        AdSelectionConfig adSelectionConfigWithEtldViolations =
+                new AdSelectionConfig(
+                        SELLER,
+                        Uri.parse(
+                                String.format(
+                                        "https://%s%s",
+                                        SELLER + "etld_noise",
+                                        SELLER_DECISION_LOGIC_URI_PATH)),
+                        Arrays.asList(BUYER_1, BUYER_2),
+                        AD_SELECTION_SIGNALS,
+                        SELLER_SIGNALS,
+                        PER_BUYER_SIGNALS,
+                        Uri.parse(
+                                String.format(
+                                        "https://%s%s",
+                                        SELLER + "etld_noise",
+                                        SELLER_TRUSTED_SIGNAL_URI_PATH)));
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Running ad selection and asserting that the outcome is returned in < 10 seconds
+        AdSelectionOutcome outcome =
+                mAdSelectionClient
+                        .selectAds(DEFAULT_AD_SELECTION_CONFIG)
+                        .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Assert that the ad3 from buyer 2 is rendered, since it had the highest bid and score
+        Assert.assertEquals(
+                getUri(BUYER_2, AD_URI_PREFIX + "/ad3"), outcome.getRenderUri());
+
+        ReportImpressionRequest reportImpressionRequest =
+                new ReportImpressionRequest(
+                        outcome.getAdSelectionId(), adSelectionConfigWithEtldViolations);
+
+        // Running report Impression and asserting that exception is thrown when decision and
+        // signals URIs are not etld+1 compliant
+        Exception selectAdsException =
+                assertThrows(
+                        ExecutionException.class,
+                        () ->
+                                mAdSelectionClient
+                                        .reportImpression(reportImpressionRequest)
+                                        .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+        assertThat(selectAdsException.getCause()).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testAdSelection_skipAdsMalformedBiddingLogic_success() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        CustomAudience customAudience2 = createCustomAudience(
+                BUYER_2,
+                bidsForBuyer2,
+                getValidActivationTime(),
+                getValidExpirationTime(),
+                BUYER_MALFORMED_BIDDING_LOGIC_URI_PATH);
+
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Running ad selection and asserting that the outcome is returned in < 10 seconds
+        AdSelectionOutcome outcome =
+                mAdSelectionClient
+                        .selectAds(DEFAULT_AD_SELECTION_CONFIG)
+                        .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Assert that the ad3 from buyer 2 is skipped despite having the highest bid, since it has
+        // malformed bidding logic
+        // The winner should come from buyer1 with the highest bid i.e. ad2
+        Assert.assertEquals(
+                getUri(BUYER_1, AD_URI_PREFIX + "/ad2"), outcome.getRenderUri());
+
+        ReportImpressionRequest reportImpressionRequest =
+                new ReportImpressionRequest(outcome.getAdSelectionId(),
+                        DEFAULT_AD_SELECTION_CONFIG);
+
+        // Performing reporting, and asserting that no exception is thrown
+        mAdSelectionClient
+                .reportImpression(reportImpressionRequest)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testAdSelection_malformedScoringLogic_failure() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        CustomAudience customAudience2 = createCustomAudience(BUYER_2, bidsForBuyer2);
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Ad Selection will fail due to scoring logic malformed
+        AdSelectionConfig adSelectionConfig = new AdSelectionConfig(
+                SELLER,
+                Uri.parse(
+                        String.format(
+                                "https://%s%s",
+                                SELLER,
+                                SELLER_MALFORMED_DECISION_LOGIC_URI_PATH)),
+                Arrays.asList(BUYER_1, BUYER_2),
+                AD_SELECTION_SIGNALS,
+                SELLER_SIGNALS,
+                PER_BUYER_SIGNALS,
+                Uri.parse(
+                        String.format(
+                                "https://%s%s",
+                                SELLER,
+                                SELLER_TRUSTED_SIGNAL_URI_PATH)));
+
+        Exception selectAdsException =
+                assertThrows(
+                        ExecutionException.class,
+                        () ->
+                                mAdSelectionClient
+                                        .selectAds(adSelectionConfig)
+                                        .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+        assertThat(selectAdsException.getCause()).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    public void testAdSelection_skipAdsFailedGettingBiddingLogic_success() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        CustomAudience customAudience2 = createCustomAudience(
+                BUYER_2,
+                bidsForBuyer2,
+                getValidActivationTime(),
+                getValidExpirationTime(),
+                "/invalid/bidding/logic/uri");
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Running ad selection and asserting that the outcome is returned in < 10 seconds
+        AdSelectionOutcome outcome =
+                mAdSelectionClient
+                        .selectAds(DEFAULT_AD_SELECTION_CONFIG)
+                        .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Assert that the ad3 from buyer 2 is skipped despite having the highest bid, since it has
+        // missing bidding logic
+        // The winner should come from buyer1 with the highest bid i.e. ad2
+        Assert.assertEquals(getUri(BUYER_1, AD_URI_PREFIX + "/ad2"), outcome.getRenderUri());
+
+        ReportImpressionRequest reportImpressionRequest =
+                new ReportImpressionRequest(outcome.getAdSelectionId(),
+                        DEFAULT_AD_SELECTION_CONFIG);
+
+        // Performing reporting, and asserting that no exception is thrown
+        mAdSelectionClient
+                .reportImpression(reportImpressionRequest)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testAdSelection_errorGettingScoringLogic_failure() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        CustomAudience customAudience2 = createCustomAudience(BUYER_2, bidsForBuyer2);
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Ad Selection will fail due to scoring logic not found, because the URI that is used to
+        // fetch scoring logic does not exist
+        AdSelectionConfig adSelectionConfig = new AdSelectionConfig(
+                SELLER,
+                Uri.parse(
+                        String.format(
+                                "https://%s%s",
+                                SELLER,
+                                "/invalid/seller/decision/logic/uri")),
+                Arrays.asList(BUYER_1, BUYER_2),
+                AD_SELECTION_SIGNALS,
+                SELLER_SIGNALS,
+                PER_BUYER_SIGNALS,
+                Uri.parse(
+                        String.format(
+                                "https://%s%s",
+                                SELLER,
+                                SELLER_TRUSTED_SIGNAL_URI_PATH)));
+        Exception selectAdsException =
+                assertThrows(
+                        ExecutionException.class,
+                        () ->
+                                mAdSelectionClient
+                                        .selectAds(adSelectionConfig)
+                                        .get(API_RESPONSE_LONGER_TIMEOUT_SECONDS,
+                                                TimeUnit.SECONDS));
+        // Sometimes a 400 status code is returned (ISE) instead of the network fetch timing out
+        assertThat(
+                selectAdsException.getCause() instanceof TimeoutException
+                        || selectAdsException.getCause() instanceof IllegalStateException)
+                .isTrue();
+    }
+
+    @Test
+    public void testAdSelectionFlow_skipNonActivatedCA_Success() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        // CA 2 activated long in the future
+        CustomAudience customAudience2 =
+                createCustomAudience(
+                        BUYER_2,
+                        bidsForBuyer2,
+                        getValidDelayedActivationTime(),
+                        getValidDelayedExpirationTime(),
+                        BUYER_BIDDING_LOGIC_URI_PATH);
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Running ad selection and asserting that the outcome is returned in < 10 seconds
+        AdSelectionOutcome outcome =
+                mAdSelectionClient
+                        .selectAds(DEFAULT_AD_SELECTION_CONFIG)
+                        .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Assert that the ad3 from buyer 2 is skipped despite having the highest bid, since it is
+        // not activated yet
+        // The winner should come from buyer1 with the highest bid i.e. ad2
+        Assert.assertEquals(
+                getUri(BUYER_1, AD_URI_PREFIX + "/ad2"), outcome.getRenderUri());
+
+        ReportImpressionRequest reportImpressionRequest =
+                new ReportImpressionRequest(outcome.getAdSelectionId(),
+                        DEFAULT_AD_SELECTION_CONFIG);
+
+        // Performing reporting, and asserting that no exception is thrown
+        mAdSelectionClient
+                .reportImpression(reportImpressionRequest)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testAdSelectionFlow_skipExpiredCA_Success() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        int caTimeToExpireSeconds = 2;
+        // Since we cannot create CA which is already expired, we create one which expires in few
+        // seconds
+        // We will then wait till this CA expires before we run Ad Selection
+        CustomAudience customAudience2 =
+                createCustomAudience(
+                        BUYER_2,
+                        bidsForBuyer2,
+                        getValidActivationTime(),
+                        Instant.now().plusSeconds(caTimeToExpireSeconds),
+                        BUYER_BIDDING_LOGIC_URI_PATH);
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Wait to ensure that CA2 gets expired
+        Thread.sleep(caTimeToExpireSeconds * 2 * 1000);
+
+        // Running ad selection and asserting that the outcome is returned in < 10 seconds
+        AdSelectionOutcome outcome =
+                mAdSelectionClient
+                        .selectAds(DEFAULT_AD_SELECTION_CONFIG)
+                        .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Assert that the ad3 from buyer 2 is skipped despite having the highest bid, since it is
+        // expired
+        // The winner should come from buyer1 with the highest bid i.e. ad2
+        Assert.assertEquals(
+                getUri(BUYER_1, AD_URI_PREFIX + "/ad2"), outcome.getRenderUri());
+
+        ReportImpressionRequest reportImpressionRequest =
+                new ReportImpressionRequest(outcome.getAdSelectionId(),
+                        DEFAULT_AD_SELECTION_CONFIG);
+
+        // Performing reporting, and asserting that no exception is thrown
+        mAdSelectionClient
+                .reportImpression(reportImpressionRequest)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testAdSelectionFlow_skipCAsThatTimeoutDuringBidding_Success() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+        CustomAudience customAudience2 = createCustomAudience(
+                BUYER_2,
+                bidsForBuyer2,
+                getValidActivationTime(),
+                getValidExpirationTime(),
+                BUYER_BIDDING_LOGIC_URI_PATH + "?delay=" + 5000);
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception.
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Running ad selection and asserting that the outcome is returned in < 10 seconds
+        AdSelectionOutcome outcome =
+                mAdSelectionClient
+                        .selectAds(DEFAULT_AD_SELECTION_CONFIG)
+                        .get(API_RESPONSE_LONGER_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Assert that the ad3 from buyer 2 is skipped despite having the highest bid, since it
+        // timed out
+        // The winner should come from buyer1 with the highest bid i.e. ad2
+        Assert.assertEquals(
+                getUri(BUYER_1, AD_URI_PREFIX + "/ad2"), outcome.getRenderUri());
+
+        ReportImpressionRequest reportImpressionRequest =
+                new ReportImpressionRequest(outcome.getAdSelectionId(),
+                        DEFAULT_AD_SELECTION_CONFIG);
+
+        // Performing reporting, and asserting that no exception is thrown
+        mAdSelectionClient
+                .reportImpression(reportImpressionRequest)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testAdSelection_overallTimeout_Failure() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
+        List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
+
+        CustomAudience customAudience1 = createCustomAudience(BUYER_1, bidsForBuyer1);
+
+        CustomAudience customAudience2 = createCustomAudience(BUYER_2, bidsForBuyer2);
+
+        // Joining custom audiences, no result to do assertion on. Failures will generate an
+        // exception."
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience1)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        mCustomAudienceClient
+                .joinCustomAudience(customAudience2)
+                .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // Running ad selection and asserting that the outcome is returned in < 10 seconds
+        AdSelectionConfig adSelectionConfig = new AdSelectionConfig(
+                SELLER,
+                Uri.parse(
+                        String.format(
+                                "https://%s%s",
+                                SELLER,
+                                SELLER_DECISION_LOGIC_URI_PATH + "?delay=" + 10000)),
+                Arrays.asList(BUYER_1, BUYER_2),
+                AD_SELECTION_SIGNALS,
+                SELLER_SIGNALS,
+                PER_BUYER_SIGNALS,
+                Uri.parse(
+                        String.format(
+                                "https://%s%s",
+                                SELLER,
+                                SELLER_TRUSTED_SIGNAL_URI_PATH)));
+        Exception selectAdsException =
+                assertThrows(
+                        ExecutionException.class,
+                        () ->
+                                mAdSelectionClient
+                                        .selectAds(adSelectionConfig)
+                                        .get(API_RESPONSE_LONGER_TIMEOUT_SECONDS,
+                                                TimeUnit.SECONDS));
+        assertThat(selectAdsException.getCause()).isInstanceOf(TimeoutException.class);
+    }
+
+    private static Uri getUri(String authority, String path) {
+        return Uri.parse("https://" + authority + path);
+    }
+
+    private static Uri getUri(AdTechIdentifier authority, String path) {
+        return getUri(authority.toString(), path);
+    }
+
+    private static Uri getValidDailyUpdateUriByBuyer(AdTechIdentifier buyer) {
+        return getUri(buyer, "/update");
+    }
+
+    private static Uri getValidTrustedBiddingUriByBuyer(AdTechIdentifier buyer) {
+        return getUri(buyer, VALID_TRUSTED_BIDDING_URI_PATH);
+    }
+
+    private static List<String> getValidTrustedBiddingKeys() {
+        return new ArrayList<>(VALID_TRUSTED_BIDDING_KEYS);
+    }
+
+    private static TrustedBiddingData getValidTrustedBiddingDataByBuyer(AdTechIdentifier buyer) {
+        return new TrustedBiddingData(
+                getValidTrustedBiddingUriByBuyer(buyer),
+                getValidTrustedBiddingKeys());
+    }
+
+    @RequiresApi(26)
+    private Instant getValidDelayedActivationTime() {
+        Duration maxActivationDelayIn =
+                Duration.ofMillis(FLEDGE_CUSTOM_AUDIENCE_MAX_ACTIVATION_DELAY_IN_MS);
+
+        return Instant.now()
+                .truncatedTo(ChronoUnit.MILLIS)
+                .plus(maxActivationDelayIn.dividedBy(2));
+    }
+
+    @RequiresApi(26)
+    private Instant getValidDelayedExpirationTime() {
+        return getValidDelayedActivationTime().plusSeconds(DAY_IN_SECONDS);
+    }
+
+    @RequiresApi(26)
+    private Instant getValidActivationTime() {
+        return Instant.now()
+                .truncatedTo(ChronoUnit.MILLIS);
+    }
+
+    @RequiresApi(26)
+    private Instant getValidExpirationTime() {
+        return getValidActivationTime()
+                .plus(Duration.ofMillis(FLEDGE_CUSTOM_AUDIENCE_DEFAULT_EXPIRE_IN_MS));
+    }
+
+
+    /**
+     * @param buyer The name of the buyer for this Custom Audience
+     * @param bids these bids, are added to its metadata. Our JS logic then picks this value and
+     *     creates ad with the provided value as bid
+     * @return a real Custom Audience object that can be persisted and used in bidding and scoring
+     */
+    private CustomAudience createCustomAudience(final AdTechIdentifier buyer, List<Double> bids) {
+        return createCustomAudience(
+                buyer,
+                bids,
+                getValidActivationTime(),
+                getValidExpirationTime(),
+                BUYER_BIDDING_LOGIC_URI_PATH);
+    }
+
+    private CustomAudience createCustomAudience(
+            final AdTechIdentifier buyer,
+            List<Double> bids,
+            Instant activationTime,
+            Instant expirationTime,
+            String biddingLogicUri) {
+        // Generate ads for with bids provided
+        List<AdData> ads = new ArrayList<>();
+
+        // Create ads with the buyer name and bid number as the ad URI
+        // Add the bid value to the metadata
+        for (int i = 0; i < bids.size(); i++) {
+            ads.add(
+                    new AdData(getUri(buyer, AD_URI_PREFIX + "/ad" + (i + 1)),
+                            "{\"bid\":" + bids.get(i) + "}"));
+        }
+
+        return new CustomAudience.Builder(
+                buyer,
+                buyer + VALID_NAME,
+                getValidDailyUpdateUriByBuyer(buyer),
+                getUri(buyer, biddingLogicUri),
+                ads)
+                .setActivationTime(activationTime)
+                .setExpirationTime(expirationTime)
+                .setUserBiddingSignals(VALID_USER_BIDDING_SIGNALS)
+                .setTrustedBiddingData(
+                        getValidTrustedBiddingDataByBuyer(buyer))
+                .build();
+    }
+
+    private static class CustomAudienceClient {
+        private final CustomAudienceManagerFutures mCustomAudienceManager;
+
+        CustomAudienceClient(Context context) {
+            mCustomAudienceManager = CustomAudienceManagerFutures.from(context);
+        }
+
+        public ListenableFuture<Unit> joinCustomAudience(CustomAudience customAudience) {
+            JoinCustomAudienceRequest request = new JoinCustomAudienceRequest(customAudience);
+            return mCustomAudienceManager
+                    .joinCustomAudienceAsync(request);
+        }
+    }
+
+    private static class AdSelectionClient {
+
+        private final AdSelectionManagerFutures mAdSelectionManager;
+
+        AdSelectionClient(Context context) {
+            mAdSelectionManager = AdSelectionManagerFutures.from(context);
+        }
+
+        /**
+         *  Invokes the {@code selectAds} method of {@link AdSelectionManager} and
+         *  returns a future with {@link AdSelectionOutcome}
+         */
+        public ListenableFuture<AdSelectionOutcome> selectAds(AdSelectionConfig adSelectionConfig)
+                throws Exception {
+            return mAdSelectionManager.selectAdsAsync(adSelectionConfig);
+        }
+
+        /**
+         * Invokes the {@code reportImpression} method of {@link AdSelectionManager} and returns
+         * a future with Unit
+         */
+        public ListenableFuture<Unit> reportImpression(ReportImpressionRequest input) {
+            return mAdSelectionManager.reportImpressionAsync(input);
+        }
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java
index 6da172a..cb16955 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java
@@ -104,6 +104,14 @@
         }
     }
 
+    public void overrideAppSetIdKillSwitch(boolean override) {
+        if (override) {
+            runShellCommand("setprop debug.adservices.appsetid_kill_switch " + false);
+        } else {
+            runShellCommand("setprop debug.adservices.appsetid_kill_switch " + null);
+        }
+    }
+
     // Override measurement related kill switch to ignore the effect of actual PH values.
     // If isOverride = true, override measurement related kill switch to OFF to allow adservices
     // If isOverride = false, override measurement related kill switch to meaningless value so that
@@ -150,6 +158,64 @@
         runShellCommand("setprop log.tag.adservices VERBOSE");
     }
 
+    public void overrideFledgeSelectAdsKillSwitch(boolean override) {
+        if (override) {
+            runShellCommand("setprop debug.adservices.fledge_select_ads_kill_switch " + false);
+        } else {
+            runShellCommand("setprop debug.adservices.fledge_select_ads_kill_switch " + null);
+        }
+    }
+
+    public void overrideFledgeCustomAudienceServiceKillSwitch(boolean override) {
+        if (override) {
+            runShellCommand("setprop debug.adservices.fledge_custom_audience_service_kill_switch "
+                    + false);
+        } else {
+            runShellCommand("setprop debug.adservices.fledge_custom_audience_service_kill_switch "
+                    + null);
+        }
+    }
+
+    public void overrideSdkRequestPermitsPerSecond(long maxRequests) {
+        runShellCommand("setprop debug.adservices.sdk_request_permits_per_second " + maxRequests);
+    }
+
+    public void disableDeviceConfigSyncForTests(boolean disabled) {
+        if (disabled) {
+            runShellCommand("device_config set_sync_disabled_for_tests persistent");
+        } else {
+            runShellCommand("device_config set_sync_disabled_for_tests none");
+        }
+    }
+
+    public void disableFledgeEnrollmentCheck(boolean disabled) {
+        if (disabled) {
+            runShellCommand("device_config put adservices disable_fledge_enrollment_check true");
+        } else {
+            runShellCommand("device_config put adservices disable_fledge_enrollment_check false");
+        }
+    }
+
+    public void enableAdServiceSystemService(boolean enabled) {
+        if (enabled) {
+            runShellCommand("device_config put adservices adservice_system_service_enabled "
+                    + "\"true\"");
+        } else {
+            runShellCommand("device_config put adservices adservice_system_service_enabled "
+                    + "\"false\"");
+        }
+    }
+
+    public void enforceFledgeJsIsolateMaxHeapSize(boolean enforce) {
+        if (enforce) {
+            runShellCommand("device_config put adservices fledge_js_isolate_enforce_max_heap_size"
+                    + " true");
+        } else {
+            runShellCommand("device_config put adservices fledge_js_isolate_enforce_max_heap_size"
+                    + " false");
+        }
+    }
+
     @SuppressWarnings("deprecation")
     // Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
     public String getAdServicesPackageName() {
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/appsetid/AppSetIdManagerTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/appsetid/AppSetIdManagerTest.java
new file mode 100644
index 0000000..88973750
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/appsetid/AppSetIdManagerTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.java.endtoend.appsetid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.privacysandbox.ads.adservices.appsetid.AppSetId;
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo;
+import androidx.privacysandbox.ads.adservices.java.appsetid.AppSetIdManagerFutures;
+import androidx.privacysandbox.ads.adservices.java.endtoend.TestUtil;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+// TODO: Consider refactoring so that we're not duplicating code.
+public class AppSetIdManagerTest {
+    private static final String TAG = "AppSetIdManagerTest";
+    TestUtil mTestUtil = new TestUtil(InstrumentationRegistry.getInstrumentation(), TAG);
+
+    @Before
+    public void setup() throws Exception {
+        mTestUtil.overrideAppSetIdKillSwitch(true);
+        mTestUtil.overrideKillSwitches(true);
+        mTestUtil.overrideAllowlists(true);
+    }
+
+    @After
+    public void teardown() {
+        mTestUtil.overrideAppSetIdKillSwitch(false);
+        mTestUtil.overrideKillSwitches(false);
+        mTestUtil.overrideAllowlists(false);
+    }
+
+    @Test
+    public void testAppSetId() throws Exception {
+        // Skip the test if SDK extension 4 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+
+        AppSetIdManagerFutures appSetIdManager =
+                AppSetIdManagerFutures.from(ApplicationProvider.getApplicationContext());
+        AppSetId appSetId = appSetIdManager.getAppSetIdAsync().get();
+        assertThat(appSetId.getId()).isNotEmpty();
+        assertThat(appSetId.getScope()).isNotNull();
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifierTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifierTest.kt
index 00a98bb..229df56 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifierTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifierTest.kt
@@ -29,7 +29,7 @@
 
     @Test
     fun testToString() {
-        val result = "AdTechIdentifier: $identifier"
+        val result = "$identifier"
         val request = AdTechIdentifier(identifier)
         Truth.assertThat(request.toString()).isEqualTo(result)
     }
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/LeaveCustomAudienceTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/LeaveCustomAudienceTest.kt
index 409f780..dc39bcd 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/LeaveCustomAudienceTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/LeaveCustomAudienceTest.kt
@@ -30,7 +30,7 @@
     private val name = "abc"
     @Test
     fun testToString() {
-        val result = "LeaveCustomAudience: buyer=AdTechIdentifier: 1234, name=abc"
+        val result = "LeaveCustomAudience: buyer=1234, name=abc"
         val leaveCustomAudienceRequest = LeaveCustomAudienceRequest(adTechIdentifier, name)
         Truth.assertThat(leaveCustomAudienceRequest.toString()).isEqualTo(result)
     }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifier.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifier.kt
index 775e14e..4180e13 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifier.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifier.kt
@@ -55,6 +55,6 @@
     /** @return The identifier in String form.
      */
     override fun toString(): String {
-        return "AdTechIdentifier: $identifier"
+        return "$identifier"
     }
 }