[go: nahoru, domu]

Introduce dedicated Privacy Sandbox Activity libraries.

This deprecates the SdkActivityLauncher interface and utilities from the
UI library and moves it to dedicates libraries under
androidx.privacysandbox.activity.{core,client,provider}.

There is no extra functionality added, this is a simple refactor with
the following (minor) differences:
- The client test now shares the same package as the library.
- The libraries now depend on the published version of androidx.core.
- We now use Stable AIDL to define the AIDL interfaces.

Bug: 307515108
Relnote: "Introduce dedicated Privacy Sandbox Activity library. It
    contains interfaces for launching activities from the SDK Runtime. The
    interfaces were previously defined in the Privacy Sandbox UI library."
Test: ./gradlew
    privacysandbox:activity:activity-{client,provider}:connectedAndroidTests
    privacysandbox:ui:ui-{client,provider}:connectedAndroidTests
Change-Id: I68beb04e2ec882bbf13b87d5b29e5885bc74ddce
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 5040829..805070a 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -292,6 +292,9 @@
     docs(project(":preference:preference"))
     docs(project(":preference:preference-ktx"))
     docs(project(":print:print"))
+    docs(project(":privacysandbox:activity:activity-client"))
+    docs(project(":privacysandbox:activity:activity-core"))
+    docs(project(":privacysandbox:activity:activity-provider"))
     docs(project(":privacysandbox:ads:ads-adservices"))
     docs(project(":privacysandbox:ads:ads-adservices-java"))
     docs(project(":privacysandbox:sdkruntime:sdkruntime-client"))
diff --git a/libraryversions.toml b/libraryversions.toml
index 6a80758..8218f60 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -102,6 +102,7 @@
 PERCENTLAYOUT = "1.1.0-alpha01"
 PREFERENCE = "1.3.0-alpha01"
 PRINT = "1.1.0-beta01"
+PRIVACYSANDBOX_ACTIVITY = "1.0.0-alpha01"
 PRIVACYSANDBOX_ADS = "1.1.0-beta02"
 PRIVACYSANDBOX_PLUGINS = "1.0.0-alpha03"
 PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha10"
@@ -165,7 +166,6 @@
 WINDOW_EXTENSIONS = "1.2.0-rc01"
 WINDOW_EXTENSIONS_CORE = "1.1.0-alpha01"
 WINDOW_SIDECAR = "1.0.0-rc01"
-# Do not remove comment
 WORK = "2.10.0-alpha01"
 
 [groups]
@@ -246,6 +246,7 @@
 PERCENTLAYOUT = { group = "androidx.percentlayout", atomicGroupVersion = "versions.PERCENTLAYOUT" }
 PREFERENCE = { group = "androidx.preference", atomicGroupVersion = "versions.PREFERENCE" }
 PRINT = { group = "androidx.print", atomicGroupVersion = "versions.PRINT" }
+PRIVACYSANDBOX_ACTIVITY = { group = "androidx.privacysandbox.activity", atomicGroupVersion = "versions.PRIVACYSANDBOX_ACTIVITY" }
 PRIVACYSANDBOX_ADS = { group = "androidx.privacysandbox.ads", atomicGroupVersion = "versions.PRIVACYSANDBOX_ADS" }
 PRIVACYSANDBOX_PLUGINS = { group = "androidx.privacysandbox.plugins", atomicGroupVersion = "versions.PRIVACYSANDBOX_PLUGINS" }
 PRIVACYSANDBOX_SDKRUNTIME = { group = "androidx.privacysandbox.sdkruntime", atomicGroupVersion = "versions.PRIVACYSANDBOX_SDKRUNTIME" }
diff --git a/privacysandbox/activity/activity-client/api/current.txt b/privacysandbox/activity/activity-client/api/current.txt
new file mode 100644
index 0000000..b744203
--- /dev/null
+++ b/privacysandbox/activity/activity-client/api/current.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.privacysandbox.activity.client {
+
+  public interface LocalSdkActivityLauncher<T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> extends androidx.privacysandbox.activity.core.SdkActivityLauncher {
+    method public void dispose();
+  }
+
+  public final class SdkActivityLaunchers {
+    method public static <T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> androidx.privacysandbox.activity.client.LocalSdkActivityLauncher<T> createSdkActivityLauncher(T, kotlin.jvm.functions.Function0<java.lang.Boolean> allowLaunch);
+    method public static android.os.Bundle toLauncherInfo(androidx.privacysandbox.activity.core.SdkActivityLauncher);
+  }
+
+}
+
diff --git a/privacysandbox/activity/activity-client/api/res-current.txt b/privacysandbox/activity/activity-client/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/privacysandbox/activity/activity-client/api/res-current.txt
diff --git a/privacysandbox/activity/activity-client/api/restricted_current.txt b/privacysandbox/activity/activity-client/api/restricted_current.txt
new file mode 100644
index 0000000..b744203
--- /dev/null
+++ b/privacysandbox/activity/activity-client/api/restricted_current.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.privacysandbox.activity.client {
+
+  public interface LocalSdkActivityLauncher<T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> extends androidx.privacysandbox.activity.core.SdkActivityLauncher {
+    method public void dispose();
+  }
+
+  public final class SdkActivityLaunchers {
+    method public static <T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> androidx.privacysandbox.activity.client.LocalSdkActivityLauncher<T> createSdkActivityLauncher(T, kotlin.jvm.functions.Function0<java.lang.Boolean> allowLaunch);
+    method public static android.os.Bundle toLauncherInfo(androidx.privacysandbox.activity.core.SdkActivityLauncher);
+  }
+
+}
+
diff --git a/privacysandbox/activity/activity-client/build.gradle b/privacysandbox/activity/activity-client/build.gradle
new file mode 100644
index 0000000..f7ec166
--- /dev/null
+++ b/privacysandbox/activity/activity-client/build.gradle
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    api("androidx.annotation:annotation:1.1.0")
+
+    implementation("androidx.core:core:1.12.0")
+    implementation("androidx.lifecycle:lifecycle-common:2.2.0")
+    implementation("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha08")
+    implementation(project(":privacysandbox:activity:activity-core"))
+
+    androidTestImplementation(project(":internal-testutils-runtime"))
+    androidTestImplementation(project(":appcompat:appcompat"))
+    androidTestImplementation(libs.espressoIntents)
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.truth)
+}
+
+android {
+    namespace "androidx.privacysandbox.activity.client"
+    defaultConfig {
+        minSdk 21
+    }
+}
+
+androidx {
+    name = "androidx.privacysandbox.activity:activity-client"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2023"
+    description = "Manage Privacy Sandbox Activities from outside the sandbox."
+}
diff --git a/privacysandbox/activity/activity-client/src/androidTest/AndroidManifest.xml b/privacysandbox/activity/activity-client/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..7cbcd44
--- /dev/null
+++ b/privacysandbox/activity/activity-client/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<!--
+  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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application>
+        <activity
+            android:name="androidx.privacysandbox.activity.client.TestActivity"
+            android:theme="@style/Theme.AppCompat"
+            android:exported="true"/>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/privacysandbox/activity/activity-client/src/androidTest/java/androidx/privacysandbox/activity/client/CreateSdkActivityLauncherTest.kt b/privacysandbox/activity/activity-client/src/androidTest/java/androidx/privacysandbox/activity/client/CreateSdkActivityLauncherTest.kt
new file mode 100644
index 0000000..969a525
--- /dev/null
+++ b/privacysandbox/activity/activity-client/src/androidTest/java/androidx/privacysandbox/activity/client/CreateSdkActivityLauncherTest.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.activity.client
+
+import android.app.Activity
+import android.app.Instrumentation.ActivityResult
+import android.content.Intent
+import android.os.Binder
+import android.os.Build
+import androidx.lifecycle.Lifecycle
+import androidx.test.espresso.intent.Intents.intended
+import androidx.test.espresso.intent.Intents.intending
+import androidx.test.espresso.intent.Intents.times
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
+import androidx.test.espresso.intent.rule.IntentsRule
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.hamcrest.Matchers.`is`
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class CreateSdkActivityLauncherTest {
+    @get:Rule
+    var activityScenarioRule = ActivityScenarioRule(TestActivity::class.java)
+
+    @get:Rule
+    var intentsRule = IntentsRule()
+
+    private val sdkSandboxActivityMatcher =
+        hasAction(`is`("android.app.sdksandbox.action.START_SANDBOXED_ACTIVITY"))
+
+    @Before
+    fun setUp() {
+        // Intercepts intent to start sandboxed activity and immediately return a result.
+        // This allows us to avoid loading and setting up an SDK just for checking if activities are
+        // launched.
+        intending(sdkSandboxActivityMatcher)
+            .respondWith(ActivityResult(Activity.RESULT_OK, Intent()))
+    }
+
+    @Test
+    fun returnedLauncher_launchesActivitiesWhenAllowed() = runBlocking {
+        val launcher = activityScenarioRule.withActivity { this.createSdkActivityLauncher { true } }
+
+        val result = launcher.launchSdkActivity(Binder())
+
+        assertThat(result).isTrue()
+        intended(sdkSandboxActivityMatcher, times(1))
+    }
+
+    @Test
+    fun returnedLauncher_rejectsActivityLaunchesAccordingToPredicate() = runBlocking {
+        val launcher =
+            activityScenarioRule.withActivity { this.createSdkActivityLauncher { false } }
+
+        val result = launcher.launchSdkActivity(Binder())
+
+        assertThat(result).isFalse()
+        intended(sdkSandboxActivityMatcher, times(0))
+    }
+
+    @Test
+    fun returnedLauncher_rejectsActivityLaunchesWhenDisposed() = runBlocking {
+        val launcher = activityScenarioRule.withActivity { this.createSdkActivityLauncher { true } }
+        launcher.dispose()
+
+        val result = launcher.launchSdkActivity(Binder())
+
+        assertThat(result).isFalse()
+        intended(sdkSandboxActivityMatcher, times(0))
+    }
+
+    @Test
+    fun returnedLauncher_disposeCanBeCalledMultipleTimes() = runBlocking {
+        val launcher = activityScenarioRule.withActivity { this.createSdkActivityLauncher { true } }
+        launcher.dispose()
+
+        val result = launcher.launchSdkActivity(Binder())
+        launcher.dispose()
+        launcher.dispose()
+
+        assertThat(result).isFalse()
+        intended(sdkSandboxActivityMatcher, times(0))
+    }
+
+    @Test
+    fun returnedLauncher_rejectsActivityLaunchesWhenHostActivityIsDestroyed() = runBlocking {
+        val launcher = activityScenarioRule.withActivity { this.createSdkActivityLauncher { true } }
+        activityScenarioRule.scenario.moveToState(Lifecycle.State.DESTROYED)
+
+        val result = launcher.launchSdkActivity(Binder())
+
+        assertThat(result).isFalse()
+        intended(sdkSandboxActivityMatcher, times(0))
+    }
+
+    @Test
+    fun returnedLauncher_rejectsActivityLaunchesWhenHostActivityWasAlreadyDestroyed() =
+        runBlocking {
+            val activity = activityScenarioRule.withActivity { this }
+            activityScenarioRule.scenario.moveToState(Lifecycle.State.DESTROYED)
+            val launcher = activity.createSdkActivityLauncher { true }
+
+            val result = launcher.launchSdkActivity(Binder())
+
+            assertThat(result).isFalse()
+            intended(sdkSandboxActivityMatcher, times(0))
+        }
+}
diff --git a/privacysandbox/activity/activity-client/src/androidTest/java/androidx/privacysandbox/activity/client/TestActivity.kt b/privacysandbox/activity/activity-client/src/androidTest/java/androidx/privacysandbox/activity/client/TestActivity.kt
new file mode 100644
index 0000000..74ffb5d
--- /dev/null
+++ b/privacysandbox/activity/activity-client/src/androidTest/java/androidx/privacysandbox/activity/client/TestActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.activity.client
+
+import androidx.appcompat.app.AppCompatActivity
+
+class TestActivity : AppCompatActivity()
diff --git a/privacysandbox/activity/activity-client/src/main/java/androidx/privacysandbox/activity/client/SdkActivityLaunchers.kt b/privacysandbox/activity/activity-client/src/main/java/androidx/privacysandbox/activity/client/SdkActivityLaunchers.kt
new file mode 100644
index 0000000..2dd726e
--- /dev/null
+++ b/privacysandbox/activity/activity-client/src/main/java/androidx/privacysandbox/activity/client/SdkActivityLaunchers.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("SdkActivityLaunchers")
+
+package androidx.privacysandbox.activity.client
+
+import android.app.Activity
+import android.os.Bundle
+import android.os.IBinder
+import androidx.core.os.BundleCompat
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.privacysandbox.activity.core.ISdkActivityLauncher
+import androidx.privacysandbox.activity.core.ISdkActivityLauncherCallback
+import androidx.privacysandbox.activity.core.ProtocolConstants.SDK_ACTIVITY_LAUNCHER_BINDER_KEY
+import androidx.privacysandbox.activity.core.SdkActivityLauncher
+import androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat
+import java.util.concurrent.atomic.AtomicReference
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Returns an SdkActivityLauncher that launches activities on behalf of an SDK by using this
+ * activity as a starting context.
+ *
+ * @param T the current activity from which new SDK activities will be launched. If this activity is
+ * destroyed any further SDK activity launches will simply be ignored.
+ * @param allowLaunch predicate called each time an activity is about to be launched by the
+ * SDK, the activity will only be launched if it returns true.
+ */
+fun <T> T.createSdkActivityLauncher(
+    allowLaunch: () -> Boolean
+): LocalSdkActivityLauncher<T>
+    where T : Activity, T : LifecycleOwner {
+    val cancellationJob = Job(parent = lifecycleScope.coroutineContext[Job])
+    val launcher = LocalSdkActivityLauncherImpl(
+        activity = this,
+        allowLaunch = allowLaunch,
+         cancellationJob.cancel() },
+    )
+    cancellationJob.invokeOnCompletion {
+        launcher.dispose()
+    }
+    return launcher
+}
+
+/**
+ * Returns a [Bundle] with the information necessary to recreate this launcher.
+ * Possibly in a different process.
+ */
+fun SdkActivityLauncher.toLauncherInfo(): Bundle {
+    val binderDelegate = SdkActivityLauncherBinderDelegate(this)
+    return Bundle().also { bundle ->
+        BundleCompat.putBinder(bundle, SDK_ACTIVITY_LAUNCHER_BINDER_KEY, binderDelegate)
+    }
+}
+
+/**
+ * Local implementation of an SDK Activity launcher.
+ *
+ * It allows callers in the app process to dispose resources used to launch SDK activities.
+ */
+interface LocalSdkActivityLauncher<T> : SdkActivityLauncher where T : Activity, T : LifecycleOwner {
+    /**
+     * Clears references used to launch activities.
+     *
+     * After this method is called all further attempts to launch activities wil be rejected.
+     * Doesn't do anything if the launcher was already disposed of.
+     */
+    fun dispose()
+}
+
+private class LocalSdkActivityLauncherImpl<T>(
+    activity: T,
+    allowLaunch: () -> Boolean,
+    onDispose: () -> Unit
+) : LocalSdkActivityLauncher<T> where T : Activity, T : LifecycleOwner {
+
+    /** Internal state for [LocalSdkActivityLauncher], cleared when the launcher is disposed. */
+    private class LocalLauncherState<T>(
+        val activity: T,
+        val allowLaunch: () -> Boolean,
+        val sdkSandboxManager: SdkSandboxManagerCompat,
+        val onDispose: () -> Unit
+    ) where T : Activity, T : LifecycleOwner
+
+    private val stateReference: AtomicReference<LocalLauncherState<T>?> =
+        AtomicReference<LocalLauncherState<T>?>(
+        LocalLauncherState(
+            activity,
+            allowLaunch,
+            SdkSandboxManagerCompat.from(activity),
+            onDispose
+        )
+    )
+
+    override suspend fun launchSdkActivity(
+        sdkActivityHandlerToken: IBinder
+    ): Boolean {
+        val state = stateReference.get() ?: return false
+        return withContext(Dispatchers.Main.immediate) {
+            state.run {
+                allowLaunch().also { didAllowLaunch ->
+                    if (didAllowLaunch) {
+                        sdkSandboxManager.startSdkSandboxActivity(activity, sdkActivityHandlerToken)
+                    }
+                }
+            }
+        }
+    }
+
+    override fun dispose() {
+        stateReference.getAndSet(null)?.run {
+            onDispose()
+        }
+    }
+}
+
+private class SdkActivityLauncherBinderDelegate(private val launcher: SdkActivityLauncher) :
+    ISdkActivityLauncher.Stub() {
+
+    private val coroutineScope = CoroutineScope(Dispatchers.Main)
+
+    override fun launchSdkActivity(
+        sdkActivityHandlerToken: IBinder?,
+        callback: ISdkActivityLauncherCallback?
+    ) {
+        requireNotNull(sdkActivityHandlerToken)
+        requireNotNull(callback)
+
+        coroutineScope.launch {
+            val accepted = try {
+                launcher.launchSdkActivity(sdkActivityHandlerToken)
+            } catch (t: Throwable) {
+                callback.onLaunchError(t.message ?: "Unknown error launching SDK activity.")
+                return@launch
+            }
+
+            if (accepted) {
+                callback.onLaunchAccepted(sdkActivityHandlerToken)
+            } else {
+                callback.onLaunchRejected(sdkActivityHandlerToken)
+            }
+        }
+    }
+}
diff --git a/privacysandbox/activity/activity-core/api/aidlRelease/current/androidx/privacysandbox/activity/core/ISdkActivityLauncher.aidl b/privacysandbox/activity/activity-core/api/aidlRelease/current/androidx/privacysandbox/activity/core/ISdkActivityLauncher.aidl
new file mode 100644
index 0000000..f5fc693
--- /dev/null
+++ b/privacysandbox/activity/activity-core/api/aidlRelease/current/androidx/privacysandbox/activity/core/ISdkActivityLauncher.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package androidx.privacysandbox.activity.core;
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+interface ISdkActivityLauncher {
+  oneway void launchSdkActivity(in IBinder sdkActivityHandlerToken, androidx.privacysandbox.activity.core.ISdkActivityLauncherCallback callback) = 1;
+}
diff --git a/privacysandbox/activity/activity-core/api/aidlRelease/current/androidx/privacysandbox/activity/core/ISdkActivityLauncherCallback.aidl b/privacysandbox/activity/activity-core/api/aidlRelease/current/androidx/privacysandbox/activity/core/ISdkActivityLauncherCallback.aidl
new file mode 100644
index 0000000..baf0647
--- /dev/null
+++ b/privacysandbox/activity/activity-core/api/aidlRelease/current/androidx/privacysandbox/activity/core/ISdkActivityLauncherCallback.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package androidx.privacysandbox.activity.core;
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+interface ISdkActivityLauncherCallback {
+  oneway void onLaunchAccepted(in IBinder sdkActivityHandlerToken) = 1;
+  oneway void onLaunchRejected(in IBinder sdkActivityHandlerToken) = 2;
+  oneway void onLaunchError(String message) = 3;
+}
diff --git a/privacysandbox/activity/activity-core/api/current.txt b/privacysandbox/activity/activity-core/api/current.txt
new file mode 100644
index 0000000..9051c68
--- /dev/null
+++ b/privacysandbox/activity/activity-core/api/current.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.privacysandbox.activity.core {
+
+  public interface SdkActivityLauncher {
+    method public suspend Object? launchSdkActivity(android.os.IBinder sdkActivityHandlerToken, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+  }
+
+}
+
diff --git a/privacysandbox/activity/activity-core/api/res-current.txt b/privacysandbox/activity/activity-core/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/privacysandbox/activity/activity-core/api/res-current.txt
diff --git a/privacysandbox/activity/activity-core/api/restricted_current.txt b/privacysandbox/activity/activity-core/api/restricted_current.txt
new file mode 100644
index 0000000..9051c68
--- /dev/null
+++ b/privacysandbox/activity/activity-core/api/restricted_current.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.privacysandbox.activity.core {
+
+  public interface SdkActivityLauncher {
+    method public suspend Object? launchSdkActivity(android.os.IBinder sdkActivityHandlerToken, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+  }
+
+}
+
diff --git a/privacysandbox/activity/activity-core/build.gradle b/privacysandbox/activity/activity-core/build.gradle
new file mode 100644
index 0000000..b52889a
--- /dev/null
+++ b/privacysandbox/activity/activity-core/build.gradle
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 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.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+    id("androidx.stableaidl")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    api("androidx.annotation:annotation:1.1.0")
+}
+
+android {
+    namespace "androidx.privacysandbox.activity.core"
+    buildFeatures {
+        aidl = true
+    }
+    buildTypes.all {
+        stableAidl {
+            version 1
+        }
+    }
+    defaultConfig {
+        minSdk 21
+    }
+}
+
+androidx {
+    name = "androidx.privacysandbox.activity:activity-core"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2023"
+    description = "Core utilities for Activities in the Privacy Sandbox."
+}
diff --git a/privacysandbox/activity/activity-core/src/main/java/androidx/privacysandbox/activity/core/ProtocolConstants.kt b/privacysandbox/activity/activity-core/src/main/java/androidx/privacysandbox/activity/core/ProtocolConstants.kt
new file mode 100644
index 0000000..b0936c5
--- /dev/null
+++ b/privacysandbox/activity/activity-core/src/main/java/androidx/privacysandbox/activity/core/ProtocolConstants.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.activity.core
+
+import androidx.annotation.RestrictTo
+
+/**
+ * Constants shared between Activity library artifacts to establish an IPC protocol across library
+ * versions. Adding new constants is allowed, but **never change the value of a constant** or
+ * you'll break compatibility between library versions.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+object ProtocolConstants {
+    const val SDK_ACTIVITY_LAUNCHER_BINDER_KEY = "sdkActivityLauncherBinderKey"
+}
diff --git a/privacysandbox/activity/activity-core/src/main/java/androidx/privacysandbox/activity/core/SdkActivityLauncher.kt b/privacysandbox/activity/activity-core/src/main/java/androidx/privacysandbox/activity/core/SdkActivityLauncher.kt
new file mode 100644
index 0000000..8fe6567
--- /dev/null
+++ b/privacysandbox/activity/activity-core/src/main/java/androidx/privacysandbox/activity/core/SdkActivityLauncher.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.activity.core
+
+import android.os.IBinder
+
+/**
+ * Interface that allows SDKs running in the Privacy Sandbox to launch activities.
+ *
+ * Apps can create launchers by calling
+ * [createActivityLauncher][androidx.privacysandbox.activity.client.createSdkActivityLauncher]
+ * from one of their activities.
+ *
+ * To send an [SdkActivityLauncher] to another process, they can call
+ * [toLauncherInfo][androidx.privacysandbox.activity.client.toLauncherInfo]
+ * and send the resulting bundle.
+ *
+ * SDKs can create launchers from an app-provided bundle by calling
+ * [createFromLauncherInfo][androidx.privacysandbox.activity.provider.SdkActivityLauncherFactory.createFromLauncherInfo].
+ */
+interface SdkActivityLauncher {
+
+    /**
+     * Tries to launch a new SDK activity using the given [sdkActivityHandlerToken],
+     * assumed to be registered in the [SdkSandboxControllerCompat][androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat].
+     *
+     * Returns true if the SDK activity intent was sent, false if the launch was rejected for any
+     * reason.
+     */
+    suspend fun launchSdkActivity(sdkActivityHandlerToken: IBinder): Boolean
+}
diff --git a/privacysandbox/activity/activity-core/src/main/stableAidl/androidx/privacysandbox/activity/core/ISdkActivityLauncher.aidl b/privacysandbox/activity/activity-core/src/main/stableAidl/androidx/privacysandbox/activity/core/ISdkActivityLauncher.aidl
new file mode 100644
index 0000000..a3c69e2
--- /dev/null
+++ b/privacysandbox/activity/activity-core/src/main/stableAidl/androidx/privacysandbox/activity/core/ISdkActivityLauncher.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 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.activity.core;
+
+import androidx.privacysandbox.activity.core.ISdkActivityLauncherCallback;
+
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+oneway interface ISdkActivityLauncher {
+  void launchSdkActivity(
+        in IBinder sdkActivityHandlerToken,
+        ISdkActivityLauncherCallback callback) = 1;
+}
\ No newline at end of file
diff --git a/privacysandbox/activity/activity-core/src/main/stableAidl/androidx/privacysandbox/activity/core/ISdkActivityLauncherCallback.aidl b/privacysandbox/activity/activity-core/src/main/stableAidl/androidx/privacysandbox/activity/core/ISdkActivityLauncherCallback.aidl
new file mode 100644
index 0000000..2a3c245
--- /dev/null
+++ b/privacysandbox/activity/activity-core/src/main/stableAidl/androidx/privacysandbox/activity/core/ISdkActivityLauncherCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 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.activity.core;
+
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+oneway interface ISdkActivityLauncherCallback {
+  void onLaunchAccepted(in IBinder sdkActivityHandlerToken) = 1;
+  void onLaunchRejected(in IBinder sdkActivityHandlerToken) = 2;
+  void onLaunchError(String message) = 3;
+}
\ No newline at end of file
diff --git a/privacysandbox/activity/activity-provider/api/current.txt b/privacysandbox/activity/activity-provider/api/current.txt
new file mode 100644
index 0000000..ddf6b94
--- /dev/null
+++ b/privacysandbox/activity/activity-provider/api/current.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.privacysandbox.activity.provider {
+
+  public final class SdkActivityLauncherFactory {
+    method public static androidx.privacysandbox.activity.core.SdkActivityLauncher fromLauncherInfo(android.os.Bundle launcherInfo);
+    field public static final androidx.privacysandbox.activity.provider.SdkActivityLauncherFactory INSTANCE;
+  }
+
+}
+
diff --git a/privacysandbox/activity/activity-provider/api/res-current.txt b/privacysandbox/activity/activity-provider/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/privacysandbox/activity/activity-provider/api/res-current.txt
diff --git a/privacysandbox/activity/activity-provider/api/restricted_current.txt b/privacysandbox/activity/activity-provider/api/restricted_current.txt
new file mode 100644
index 0000000..ddf6b94
--- /dev/null
+++ b/privacysandbox/activity/activity-provider/api/restricted_current.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.privacysandbox.activity.provider {
+
+  public final class SdkActivityLauncherFactory {
+    method public static androidx.privacysandbox.activity.core.SdkActivityLauncher fromLauncherInfo(android.os.Bundle launcherInfo);
+    field public static final androidx.privacysandbox.activity.provider.SdkActivityLauncherFactory INSTANCE;
+  }
+
+}
+
diff --git a/privacysandbox/activity/activity-provider/build.gradle b/privacysandbox/activity/activity-provider/build.gradle
new file mode 100644
index 0000000..856130b
--- /dev/null
+++ b/privacysandbox/activity/activity-provider/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 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.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    api("androidx.annotation:annotation:1.1.0")
+
+    implementation(project(":privacysandbox:activity:activity-core"))
+    implementation("androidx.core:core:1.12.0")
+    implementation(libs.kotlinCoroutinesCore)
+
+    androidTestImplementation(project(":privacysandbox:activity:activity-client"))
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.truth)
+}
+
+android {
+    namespace "androidx.privacysandbox.activity.provider"
+    defaultConfig {
+        minSdk 21
+    }
+}
+
+androidx {
+    name = "androidx.privacysandbox.activity:activity-provider"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2023"
+    description = "Utilities for launchig Activities from the Privacy Sandbox."
+}
diff --git a/privacysandbox/activity/activity-provider/src/androidTest/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherBundlingTest.kt b/privacysandbox/activity/activity-provider/src/androidTest/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherBundlingTest.kt
new file mode 100644
index 0000000..4659813
--- /dev/null
+++ b/privacysandbox/activity/activity-provider/src/androidTest/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherBundlingTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.activity.provider
+
+import android.os.Binder
+import android.os.IBinder
+import androidx.privacysandbox.activity.client.toLauncherInfo
+import androidx.privacysandbox.activity.core.SdkActivityLauncher
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SdkActivityLauncherBundlingTest {
+
+    @Test
+    fun unbundledSdkActivityLauncher_launchesActivities(): Unit = runBlocking {
+        val launcher = TestSdkActivityLauncher()
+        val launcherInfo = launcher.toLauncherInfo()
+
+        val unbundledLauncher = SdkActivityLauncherFactory.fromLauncherInfo(launcherInfo)
+        val token = Binder()
+        val result = unbundledLauncher.launchSdkActivity(token)
+
+        assertThat(result).isTrue()
+        assertThat(launcher.tokensReceived).containsExactly(token)
+    }
+
+    @Test
+    fun unbundledSdkActivityLauncher_rejectsActivityLaunches(): Unit = runBlocking {
+        val launcher = TestSdkActivityLauncher()
+        launcher.allowActivityLaunches = false
+        val launcherInfo = launcher.toLauncherInfo()
+
+        val unbundledLauncher = SdkActivityLauncherFactory.fromLauncherInfo(launcherInfo)
+        val token = Binder()
+        val result = unbundledLauncher.launchSdkActivity(token)
+
+        assertThat(result).isFalse()
+        assertThat(launcher.tokensReceived).containsExactly(token)
+    }
+
+    class TestSdkActivityLauncher : SdkActivityLauncher {
+        var allowActivityLaunches: Boolean = true
+
+        var tokensReceived = mutableListOf<IBinder>()
+
+        override suspend fun launchSdkActivity(sdkActivityHandlerToken: IBinder):
+            Boolean {
+            tokensReceived.add(sdkActivityHandlerToken)
+            return allowActivityLaunches
+        }
+    }
+}
diff --git a/privacysandbox/activity/activity-provider/src/main/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherFactory.kt b/privacysandbox/activity/activity-provider/src/main/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherFactory.kt
new file mode 100644
index 0000000..8e53590
--- /dev/null
+++ b/privacysandbox/activity/activity-provider/src/main/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherFactory.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.activity.provider
+
+import android.os.Bundle
+import android.os.IBinder
+import androidx.core.os.BundleCompat
+import androidx.privacysandbox.activity.core.ISdkActivityLauncher
+import androidx.privacysandbox.activity.core.ISdkActivityLauncherCallback
+import androidx.privacysandbox.activity.core.ProtocolConstants.SDK_ACTIVITY_LAUNCHER_BINDER_KEY
+import androidx.privacysandbox.activity.core.SdkActivityLauncher
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+object SdkActivityLauncherFactory {
+
+    /**
+     * Creates a [SdkActivityLauncher] using the given [launcherInfo] Bundle.
+     *
+     * You can create such a Bundle by calling [toLauncherInfo][androidx.privacysandbox.activity.client.toLauncherInfo].
+     * A [launcherInfo] is expected to have a valid SdkActivityLauncher Binder with
+     * `"sdkActivityLauncherBinderKey"` for a key, [IllegalArgumentException] is thrown otherwise.
+     */
+    @JvmStatic
+    fun fromLauncherInfo(launcherInfo: Bundle): SdkActivityLauncher {
+        val remote: ISdkActivityLauncher? = ISdkActivityLauncher.Stub.asInterface(
+            BundleCompat.getBinder(launcherInfo, SDK_ACTIVITY_LAUNCHER_BINDER_KEY)
+        )
+        requireNotNull(remote) { "Invalid SdkActivityLauncher info bundle." }
+        return SdkActivityLauncherProxy(remote)
+    }
+
+    private class SdkActivityLauncherProxy(
+        private val remote: ISdkActivityLauncher
+    ) : SdkActivityLauncher {
+        override suspend fun launchSdkActivity(sdkActivityHandlerToken: IBinder): Boolean =
+            suspendCancellableCoroutine {
+                remote.launchSdkActivity(
+                    sdkActivityHandlerToken,
+                    object : ISdkActivityLauncherCallback.Stub() {
+                        override fun onLaunchAccepted(sdkActivityHandlerToken: IBinder?) {
+                            it.resume(true)
+                        }
+
+                        override fun onLaunchRejected(sdkActivityHandlerToken: IBinder?) {
+                            it.resume(false)
+                        }
+
+                        override fun onLaunchError(message: String?) {
+                            it.resumeWithException(RuntimeException(message))
+                        }
+                    })
+            }
+    }
+}
diff --git a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
index 46db8fff..40089ca 100644
--- a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
+++ b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
@@ -116,10 +116,23 @@
         assertThat(getShortErrorMessages()).containsExactly(*errors)
     }
 
-    private fun getRawErrorMessages() =
-        (result.diagnostics[Diagnostic.Kind.ERROR] ?: emptyList()) +
-            (result.diagnostics[Diagnostic.Kind.WARNING] ?: emptyList()) +
+    private fun getRawErrorMessages(): List<DiagnosticMessage> {
+        // Filter SdkActivityLauncher deprecation warnings. Our tests currently use the old version
+        // so the warnings are expected.
+        // TODO(b/307696996) - Remove this once the generator uses the new version exclusively.
+        val warningsToIgnore = listOf(
+            "'SdkActivityLauncher' is deprecated.",
+            "'SdkActivityLauncherFactory' is deprecated.",
+            "'toLauncherInfo(): Bundle' is deprecated.",
+        )
+        val filteredWarnings = result.diagnostics[Diagnostic.Kind.WARNING]?.filter { warning ->
+            !warningsToIgnore.any { warning.msg.contains(it) }
+        } ?: emptyList()
+
+        return (result.diagnostics[Diagnostic.Kind.ERROR] ?: emptyList()) +
+            filteredWarnings +
             (result.diagnostics[Diagnostic.Kind.MANDATORY_WARNING] ?: emptyList())
+    }
 
     private fun getShortErrorMessages() =
         result.diagnostics[Diagnostic.Kind.ERROR]?.map(DiagnosticMessage::msg)
diff --git a/privacysandbox/ui/ui-client/api/current.txt b/privacysandbox/ui/ui-client/api/current.txt
index 6db1a94..3e1cb8f 100644
--- a/privacysandbox/ui/ui-client/api/current.txt
+++ b/privacysandbox/ui/ui-client/api/current.txt
@@ -1,8 +1,8 @@
 // Signature format: 4.0
 package androidx.privacysandbox.ui.client {
 
-  public interface LocalSdkActivityLauncher<T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> extends androidx.privacysandbox.ui.core.SdkActivityLauncher {
-    method public void dispose();
+  @Deprecated public interface LocalSdkActivityLauncher<T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> extends androidx.privacysandbox.ui.core.SdkActivityLauncher {
+    method @Deprecated public void dispose();
   }
 
   public final class SandboxedUiAdapterFactory {
@@ -11,8 +11,8 @@
   }
 
   public final class SdkActivityLaunchers {
-    method public static <T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> androidx.privacysandbox.ui.client.LocalSdkActivityLauncher<T> createSdkActivityLauncher(T, kotlin.jvm.functions.Function0<java.lang.Boolean> allowLaunch);
-    method public static android.os.Bundle toLauncherInfo(androidx.privacysandbox.ui.core.SdkActivityLauncher);
+    method @Deprecated public static <T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> androidx.privacysandbox.ui.client.LocalSdkActivityLauncher<T> createSdkActivityLauncher(T, kotlin.jvm.functions.Function0<java.lang.Boolean> allowLaunch);
+    method @Deprecated public static android.os.Bundle toLauncherInfo(androidx.privacysandbox.ui.core.SdkActivityLauncher);
   }
 
 }
diff --git a/privacysandbox/ui/ui-client/api/restricted_current.txt b/privacysandbox/ui/ui-client/api/restricted_current.txt
index 6db1a94..3e1cb8f 100644
--- a/privacysandbox/ui/ui-client/api/restricted_current.txt
+++ b/privacysandbox/ui/ui-client/api/restricted_current.txt
@@ -1,8 +1,8 @@
 // Signature format: 4.0
 package androidx.privacysandbox.ui.client {
 
-  public interface LocalSdkActivityLauncher<T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> extends androidx.privacysandbox.ui.core.SdkActivityLauncher {
-    method public void dispose();
+  @Deprecated public interface LocalSdkActivityLauncher<T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> extends androidx.privacysandbox.ui.core.SdkActivityLauncher {
+    method @Deprecated public void dispose();
   }
 
   public final class SandboxedUiAdapterFactory {
@@ -11,8 +11,8 @@
   }
 
   public final class SdkActivityLaunchers {
-    method public static <T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> androidx.privacysandbox.ui.client.LocalSdkActivityLauncher<T> createSdkActivityLauncher(T, kotlin.jvm.functions.Function0<java.lang.Boolean> allowLaunch);
-    method public static android.os.Bundle toLauncherInfo(androidx.privacysandbox.ui.core.SdkActivityLauncher);
+    method @Deprecated public static <T extends android.app.Activity & androidx.lifecycle.LifecycleOwner> androidx.privacysandbox.ui.client.LocalSdkActivityLauncher<T> createSdkActivityLauncher(T, kotlin.jvm.functions.Function0<java.lang.Boolean> allowLaunch);
+    method @Deprecated public static android.os.Bundle toLauncherInfo(androidx.privacysandbox.ui.core.SdkActivityLauncher);
   }
 
 }
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/CreateSdkActivityLauncherTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/CreateSdkActivityLauncherTest.kt
index 55c5a54..829a41a 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/CreateSdkActivityLauncherTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/CreateSdkActivityLauncherTest.kt
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// TODO(b/307696996) Remove this file when activity library is released.
+@file:Suppress("DEPRECATION")
+
 package androidx.privacysandbox.ui.client.test
 
 import android.app.Activity
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SdkActivityLaunchers.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SdkActivityLaunchers.kt
index d8a6f94..5197cfd 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SdkActivityLaunchers.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SdkActivityLaunchers.kt
@@ -15,6 +15,8 @@
  */
 
 @file:JvmName("SdkActivityLaunchers")
+// TODO(b/307696996) Remove this file when activity library is released.
+@file:Suppress("DEPRECATION")
 
 package androidx.privacysandbox.ui.client
 
@@ -45,6 +47,10 @@
  * @param allowLaunch predicate called each time an activity is about to be launched by the
  * SDK, the activity will only be launched if it returns true.
  */
+@Deprecated("Use the Privacy Sandbox Activity library version instead.",
+    ReplaceWith(
+        expression = "createSdkActivityLauncher",
+        imports = arrayOf("androidx.privacysandbox.activity.client.createSdkActivityLauncher")))
 fun <T> T.createSdkActivityLauncher(
     allowLaunch: () -> Boolean
 ): LocalSdkActivityLauncher<T>
@@ -65,6 +71,10 @@
  * Returns a [Bundle] with the information necessary to recreate this launcher.
  * Possibly in a different process.
  */
+@Deprecated("Use the Privacy Sandbox Activity library version instead.",
+    ReplaceWith(
+        expression = "toLauncherInfo",
+        imports = arrayOf("androidx.privacysandbox.activity.client.toLauncherInfo")))
 fun SdkActivityLauncher.toLauncherInfo(): Bundle {
     val binderDelegate = SdkActivityLauncherBinderDelegate(this)
     return Bundle().also { bundle ->
@@ -77,6 +87,10 @@
  *
  * It allows callers in the app process to dispose resources used to launch SDK activities.
  */
+@Deprecated("Use the Privacy Sandbox Activity library version instead.",
+    ReplaceWith(
+        expression = "LocalSdkActivityLauncher",
+        imports = arrayOf("androidx.privacysandbox.activity.client.LocalSdkActivityLauncher")))
 interface LocalSdkActivityLauncher<T> : SdkActivityLauncher where T : Activity, T : LifecycleOwner {
     /**
      * Clears references used to launch activities.
diff --git a/privacysandbox/ui/ui-core/api/current.txt b/privacysandbox/ui/ui-core/api/current.txt
index 2537c3e..5ab678d 100644
--- a/privacysandbox/ui/ui-core/api/current.txt
+++ b/privacysandbox/ui/ui-core/api/current.txt
@@ -20,8 +20,8 @@
     method public void onSessionOpened(androidx.privacysandbox.ui.core.SandboxedUiAdapter.Session session);
   }
 
-  public interface SdkActivityLauncher {
-    method public suspend Object? launchSdkActivity(android.os.IBinder sdkActivityHandlerToken, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+  @Deprecated public interface SdkActivityLauncher {
+    method @Deprecated public suspend Object? launchSdkActivity(android.os.IBinder sdkActivityHandlerToken, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
   }
 
   public final class SdkRuntimeUiLibVersions {
diff --git a/privacysandbox/ui/ui-core/api/restricted_current.txt b/privacysandbox/ui/ui-core/api/restricted_current.txt
index 2537c3e..5ab678d 100644
--- a/privacysandbox/ui/ui-core/api/restricted_current.txt
+++ b/privacysandbox/ui/ui-core/api/restricted_current.txt
@@ -20,8 +20,8 @@
     method public void onSessionOpened(androidx.privacysandbox.ui.core.SandboxedUiAdapter.Session session);
   }
 
-  public interface SdkActivityLauncher {
-    method public suspend Object? launchSdkActivity(android.os.IBinder sdkActivityHandlerToken, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+  @Deprecated public interface SdkActivityLauncher {
+    method @Deprecated public suspend Object? launchSdkActivity(android.os.IBinder sdkActivityHandlerToken, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
   }
 
   public final class SdkRuntimeUiLibVersions {
diff --git a/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SdkActivityLauncher.kt b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SdkActivityLauncher.kt
index 9134f7e..72ac835 100644
--- a/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SdkActivityLauncher.kt
+++ b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SdkActivityLauncher.kt
@@ -32,6 +32,11 @@
  * SDKs can create launchers from an app-provided bundle by calling
  * [createFromLauncherInfo][androidx.privacysandbox.ui.provider.SdkActivityLauncherFactory.createFromLauncherInfo].
  */
+@Deprecated("Use the Privacy Sandbox Activity library version instead.",
+    ReplaceWith(
+        expression = "SdkActivityLauncher",
+        imports = arrayOf("androidx.privacysandbox.activity.core.SdkActivityLauncher")))
+// TODO(b/307696996) Remove this file when activity library is released.
 interface SdkActivityLauncher {
 
     /**
diff --git a/privacysandbox/ui/ui-provider/api/current.txt b/privacysandbox/ui/ui-provider/api/current.txt
index 1c7512c..0ba0f20 100644
--- a/privacysandbox/ui/ui-provider/api/current.txt
+++ b/privacysandbox/ui/ui-provider/api/current.txt
@@ -5,9 +5,9 @@
     method public static android.os.Bundle toCoreLibInfo(androidx.privacysandbox.ui.core.SandboxedUiAdapter, android.content.Context context);
   }
 
-  public final class SdkActivityLauncherFactory {
-    method public static androidx.privacysandbox.ui.core.SdkActivityLauncher fromLauncherInfo(android.os.Bundle launcherInfo);
-    field public static final androidx.privacysandbox.ui.provider.SdkActivityLauncherFactory INSTANCE;
+  @Deprecated public final class SdkActivityLauncherFactory {
+    method @Deprecated public static androidx.privacysandbox.ui.core.SdkActivityLauncher fromLauncherInfo(android.os.Bundle launcherInfo);
+    field @Deprecated public static final androidx.privacysandbox.ui.provider.SdkActivityLauncherFactory INSTANCE;
   }
 
 }
diff --git a/privacysandbox/ui/ui-provider/api/restricted_current.txt b/privacysandbox/ui/ui-provider/api/restricted_current.txt
index 1c7512c..0ba0f20 100644
--- a/privacysandbox/ui/ui-provider/api/restricted_current.txt
+++ b/privacysandbox/ui/ui-provider/api/restricted_current.txt
@@ -5,9 +5,9 @@
     method public static android.os.Bundle toCoreLibInfo(androidx.privacysandbox.ui.core.SandboxedUiAdapter, android.content.Context context);
   }
 
-  public final class SdkActivityLauncherFactory {
-    method public static androidx.privacysandbox.ui.core.SdkActivityLauncher fromLauncherInfo(android.os.Bundle launcherInfo);
-    field public static final androidx.privacysandbox.ui.provider.SdkActivityLauncherFactory INSTANCE;
+  @Deprecated public final class SdkActivityLauncherFactory {
+    method @Deprecated public static androidx.privacysandbox.ui.core.SdkActivityLauncher fromLauncherInfo(android.os.Bundle launcherInfo);
+    field @Deprecated public static final androidx.privacysandbox.ui.provider.SdkActivityLauncherFactory INSTANCE;
   }
 
 }
diff --git a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/SdkActivityLauncherFactory.kt b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/SdkActivityLauncherFactory.kt
index 5e72d7a..409bd36 100644
--- a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/SdkActivityLauncherFactory.kt
+++ b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/SdkActivityLauncherFactory.kt
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// TODO(b/307696996) Remove this file when activity library is released.
+@file:Suppress("DEPRECATION")
+
 package androidx.privacysandbox.ui.provider
 
 import android.os.Bundle
@@ -27,6 +30,10 @@
 import kotlin.coroutines.resumeWithException
 import kotlinx.coroutines.suspendCancellableCoroutine
 
+@Deprecated("Use the Privacy Sandbox Activity library version instead.",
+    ReplaceWith(
+        expression = "SdkActivityLauncherFactory",
+        imports = arrayOf("androidx.privacysandbox.activity.provider.SdkActivityLauncherFactory")))
 object SdkActivityLauncherFactory {
 
     /**
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/activity/SdkActivityLauncherBundlingTest.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/activity/SdkActivityLauncherBundlingTest.kt
index 66fdcdf..6b5b722 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/activity/SdkActivityLauncherBundlingTest.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/activity/SdkActivityLauncherBundlingTest.kt
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+// TODO(b/307696996) Remove this file when activity library is released.
+@file:Suppress("DEPRECATION")
+
 package androidx.privacysandbox.ui.tests.activity
 
 import android.os.Binder
diff --git a/settings.gradle b/settings.gradle
index e7da77a..38ea422 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -821,6 +821,9 @@
 includeProject(":preference:preference", [BuildType.MAIN])
 includeProject(":preference:preference-ktx", [BuildType.MAIN])
 includeProject(":print:print", [BuildType.MAIN])
+includeProject(":privacysandbox:activity:activity-client", [BuildType.MAIN])
+includeProject(":privacysandbox:activity:activity-core", [BuildType.MAIN])
+includeProject(":privacysandbox:activity:activity-provider", [BuildType.MAIN])
 includeProject(":privacysandbox:ads:ads-adservices", [BuildType.MAIN])
 includeProject(":privacysandbox:ads:ads-adservices-java", [BuildType.MAIN])
 includeProject(":privacysandbox:plugins:plugins-privacysandbox-library", [BuildType.MAIN])