Initial implementation of SdkSandboxManagerCompat and SandboxedSdkProviderCompat

Bug: 249981539
Test: SdkSandboxManagerCompatTest, SandboxedSdkProviderAdapterTest
Relnote: Initial version of components for SdkRuntime backward compat
Change-Id: Ie9a1ca9c3b56530348b1bf00a6bf8638692d1dd0
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/api/current.txt b/privacysandbox/sdkruntime/sdkruntime-client/api/current.txt
index e6f50d0..83c8347 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/api/current.txt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/api/current.txt
@@ -1 +1,15 @@
 // Signature format: 4.0
+package androidx.privacysandbox.sdkruntime.client {
+
+  public final class SdkSandboxManagerCompat {
+    method @kotlin.jvm.Throws(exceptionClasses=LoadSdkCompatException::class) public suspend Object? loadSdk(String sdkName, android.os.Bundle params, kotlin.coroutines.Continuation<? super androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat>) throws androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException;
+    method public static androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat obtain(android.content.Context context);
+    field public static final androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.Companion Companion;
+  }
+
+  public static final class SdkSandboxManagerCompat.Companion {
+    method public androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat obtain(android.content.Context context);
+  }
+
+}
+
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/api/public_plus_experimental_current.txt b/privacysandbox/sdkruntime/sdkruntime-client/api/public_plus_experimental_current.txt
index e6f50d0..83c8347 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/api/public_plus_experimental_current.txt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/api/public_plus_experimental_current.txt
@@ -1 +1,15 @@
 // Signature format: 4.0
+package androidx.privacysandbox.sdkruntime.client {
+
+  public final class SdkSandboxManagerCompat {
+    method @kotlin.jvm.Throws(exceptionClasses=LoadSdkCompatException::class) public suspend Object? loadSdk(String sdkName, android.os.Bundle params, kotlin.coroutines.Continuation<? super androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat>) throws androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException;
+    method public static androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat obtain(android.content.Context context);
+    field public static final androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.Companion Companion;
+  }
+
+  public static final class SdkSandboxManagerCompat.Companion {
+    method public androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat obtain(android.content.Context context);
+  }
+
+}
+
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/api/restricted_current.txt b/privacysandbox/sdkruntime/sdkruntime-client/api/restricted_current.txt
index e6f50d0..83c8347 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/api/restricted_current.txt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/api/restricted_current.txt
@@ -1 +1,15 @@
 // Signature format: 4.0
+package androidx.privacysandbox.sdkruntime.client {
+
+  public final class SdkSandboxManagerCompat {
+    method @kotlin.jvm.Throws(exceptionClasses=LoadSdkCompatException::class) public suspend Object? loadSdk(String sdkName, android.os.Bundle params, kotlin.coroutines.Continuation<? super androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat>) throws androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException;
+    method public static androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat obtain(android.content.Context context);
+    field public static final androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.Companion Companion;
+  }
+
+  public static final class SdkSandboxManagerCompat.Companion {
+    method public androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat obtain(android.content.Context context);
+  }
+
+}
+
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
index 12371e8..e339dd0 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
+++ b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
@@ -24,6 +24,27 @@
 
 dependencies {
     api(libs.kotlinStdlib)
+    api(libs.kotlinCoroutinesCore)
+    implementation("androidx.core:core-ktx:1.8.0")
+
+    api("androidx.annotation:annotation:1.2.0")
+
+    api project(path: ':privacysandbox:sdkruntime:sdkruntime-core')
+
+    //Needed for BuildCompat.isAtLeastU().
+    // TODO(b/249981547) Remove as soon as SdkSandbox APIs dropped to T
+    implementation("androidx.core:core:1.8.0")
+
+    // TODO(b/249982004): cleanup dependencies
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.truth)
+    androidTestImplementation(libs.junit)
+
+    androidTestImplementation(libs.mockitoCore, excludes.bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(libs.dexmakerMockitoInline, excludes.bytebuddy) // DexMaker has it"s own MockMaker
 }
 
 android {
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/AndroidManifest.xml b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..3e3ea90
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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">
+</manifest>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt
new file mode 100644
index 0000000..830080b
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2022 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.sdkruntime.client
+
+import android.app.sdksandbox.LoadSdkException
+import android.app.sdksandbox.SandboxedSdk
+import android.app.sdksandbox.SdkSandboxManager
+import android.content.Context
+import android.os.Binder
+import android.os.Build
+import android.os.Bundle
+import android.os.OutcomeReceiver
+import androidx.annotation.RequiresApi
+import androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.Companion.obtain
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.invocation.InvocationOnMock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+// TODO(b/249982507) DexmakerMockitoInline requires P+. Test should be rewritten to support P-
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+class SdkSandboxManagerCompatTest {
+
+    private lateinit var mContext: Context
+
+    @Before
+    fun setUp() {
+        mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+    }
+
+    @Test
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+    fun loadSdk_whenApi33_delegateToPlatformLoadSdk() {
+        val sdkSandboxManager = mockSandboxManager(mContext)
+        setupLoadSdkAnswer(sdkSandboxManager, SandboxedSdk(Binder()))
+
+        val managerCompat = obtain(mContext)
+        val sdkName = "test"
+        val params = Bundle()
+
+        runBlocking {
+            managerCompat.loadSdk(sdkName, params)
+        }
+
+        verify(sdkSandboxManager).loadSdk(
+            eq(sdkName),
+            eq(params),
+            any(),
+            any()
+        )
+    }
+
+    @Test
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.S_V2)
+    fun loadSdk_whenApiPre33_notDelegateToPlatform() {
+        val managerCompat = obtain(mContext)
+
+        assertThrows(LoadSdkCompatException::class.java) {
+            runBlocking {
+                managerCompat.loadSdk("test", Bundle())
+            }
+        }
+
+        verifyZeroInteractions(mContext)
+    }
+
+    @Test
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+    fun loadSdk_whenApi33_returnResultFromPlatformLoadSdk() {
+        val sdkSandboxManager = mockSandboxManager(mContext)
+
+        val sandboxedSdk = SandboxedSdk(Binder())
+        setupLoadSdkAnswer(sdkSandboxManager, sandboxedSdk)
+
+        val managerCompat = obtain(mContext)
+
+        val result = runBlocking {
+            managerCompat.loadSdk("test", Bundle())
+        }
+
+        assertThat(result.getInterface()).isEqualTo(sandboxedSdk.getInterface())
+    }
+
+    @Test
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+    fun loadSdk_whenApi33_returnErrorFromPlatformLoadSdk() {
+        val sdkSandboxManager = mockSandboxManager(mContext)
+
+        val loadSdkException = LoadSdkException(
+            RuntimeException(),
+            Bundle()
+        )
+        setupLoadSdkAnswer(sdkSandboxManager, loadSdkException)
+
+        val managerCompat = obtain(mContext)
+
+        val result = assertThrows(LoadSdkCompatException::class.java) {
+            runBlocking {
+                managerCompat.loadSdk("test", Bundle())
+            }
+        }
+
+        assertThat(result.cause).isEqualTo(loadSdkException.cause)
+        assertThat(result.extraInformation).isEqualTo(loadSdkException.extraInformation)
+        assertThat(result.loadSdkErrorCode).isEqualTo(loadSdkException.loadSdkErrorCode)
+    }
+
+    @Test
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.S_V2)
+    fun loadSdk_whenApiPre33_returnError() {
+        val managerCompat = obtain(mContext)
+
+        val result = assertThrows(LoadSdkCompatException::class.java) {
+            runBlocking {
+                managerCompat.loadSdk("test", Bundle())
+            }
+        }
+
+        assertThat(result.loadSdkErrorCode)
+            .isEqualTo(LoadSdkCompatException.LOAD_SDK_SDK_SANDBOX_DISABLED)
+    }
+
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    companion object Api33 {
+
+        private fun mockSandboxManager(spyContext: Context): SdkSandboxManager {
+            val sdkSandboxManager = mock(SdkSandboxManager::class.java)
+            `when`(spyContext.getSystemService(SdkSandboxManager::class.java))
+                .thenReturn(sdkSandboxManager)
+            return sdkSandboxManager
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        private fun setupLoadSdkAnswer(
+            sdkSandboxManager: SdkSandboxManager,
+            sandboxedSdk: SandboxedSdk
+        ) {
+            // do not inline - to avoid initializationError on old platforms because of SandboxedSdk
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<SandboxedSdk, LoadSdkException>>(3)
+                receiver.onResult(sandboxedSdk)
+                null
+            }
+            doAnswer(answer)
+                .`when`(sdkSandboxManager).loadSdk(
+                    any(),
+                    any(),
+                    any(),
+                    any()
+                )
+        }
+
+        private fun setupLoadSdkAnswer(
+            sdkSandboxManager: SdkSandboxManager,
+            loadSdkException: LoadSdkException
+        ) {
+            // do not inline - to avoid initializationError on old platforms because of LoadSdkException
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<SandboxedSdk, LoadSdkException>>(3)
+                receiver.onError(loadSdkException)
+                null
+            }
+            doAnswer(answer)
+                .`when`(sdkSandboxManager).loadSdk(
+                    any(),
+                    any(),
+                    any(),
+                    any()
+                )
+        }
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
new file mode 100644
index 0000000..b169a6d
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2022 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.sdkruntime.client
+
+import android.app.sdksandbox.LoadSdkException
+import android.app.sdksandbox.SandboxedSdk
+import android.app.sdksandbox.SdkSandboxManager
+import android.content.Context
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.Bundle
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.core.os.BuildCompat
+import androidx.core.os.CancellationSignal
+import androidx.core.os.asOutcomeReceiver
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.LOAD_SDK_SDK_SANDBOX_DISABLED
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.toLoadCompatSdkException
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.Companion.toSandboxedSdkCompat
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Compat version of [SdkSandboxManager].
+ *
+ * Provides APIs to load [androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat]
+ * into SDK sandbox process or locally, and then interact with them.
+ *
+ * @see [SdkSandboxManager]
+ */
+class SdkSandboxManagerCompat private constructor(private val mPlatformApi: PlatformApi) {
+    /**
+     * Load SDK in a SDK sandbox java process or locally.
+     *
+     * @param sdkName name of the SDK to be loaded.
+     * @param params additional parameters to be passed to the SDK in the form of a [Bundle]
+     *  as agreed between the client and the SDK.
+     * @return [SandboxedSdkCompat] from SDK on a successful run.
+     * @throws [LoadSdkCompatException] on fail.
+     *
+     * @see [SdkSandboxManager.loadSdk]
+     */
+    @Throws(LoadSdkCompatException::class)
+    suspend fun loadSdk(
+        sdkName: String,
+        params: Bundle
+    ): SandboxedSdkCompat {
+        // TODO(b/249982004) Try to load SDK bundled with App first, fallback to platform after.
+        return mPlatformApi.loadSdk(sdkName, params)
+    }
+
+    private interface PlatformApi {
+        @DoNotInline
+        @Throws(LoadSdkCompatException::class)
+        suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat
+    }
+
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @RequiresApi(UPSIDE_DOWN_CAKE)
+    private class Api33Impl(private val mSdkSandboxManager: SdkSandboxManager) :
+        PlatformApi {
+        constructor(context: Context) : this(
+            context.getSystemService<SdkSandboxManager>(
+                SdkSandboxManager::class.java
+            )
+        )
+
+        @DoNotInline
+        override suspend fun loadSdk(
+            sdkName: String,
+            params: Bundle
+        ): SandboxedSdkCompat {
+            try {
+                val sandboxedSdk = loadSdkInternal(sdkName, params)
+                return toSandboxedSdkCompat(sandboxedSdk)
+            } catch (ex: LoadSdkException) {
+                throw toLoadCompatSdkException(ex)
+            }
+        }
+
+        private suspend fun loadSdkInternal(
+            sdkName: String,
+            params: Bundle
+        ): SandboxedSdk {
+            return suspendCancellableCoroutine { continuation ->
+
+                val canceller = CancellationSignal()
+                continuation.invokeOnCancellation { canceller.cancel() }
+
+                mSdkSandboxManager.loadSdk(
+                    sdkName,
+                    params,
+                    Runnable::run,
+                    continuation.asOutcomeReceiver()
+                )
+            }
+        }
+    }
+
+    private class FailImpl : PlatformApi {
+        @DoNotInline
+        override suspend fun loadSdk(
+            sdkName: String,
+            params: Bundle
+        ): SandboxedSdkCompat {
+            throw LoadSdkCompatException(LOAD_SDK_SDK_SANDBOX_DISABLED, "Not implemented")
+        }
+    }
+
+    companion object {
+        /**
+         *  Creates [SdkSandboxManagerCompat].
+         *
+         *  @param context Application context
+         *
+         *  @return SdkSandboxManagerCompat object.
+         */
+        @JvmStatic
+        @androidx.annotation.OptIn(markerClass = [BuildCompat.PrereleaseSdkCheck::class])
+        fun obtain(context: Context): SdkSandboxManagerCompat {
+            // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+            return if (BuildCompat.isAtLeastU()) {
+                SdkSandboxManagerCompat(Api33Impl(context))
+            } else {
+                SdkSandboxManagerCompat(FailImpl())
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/androidx/privacysandbox/sdkruntime/androidx-privacysandbox-sdkruntime-sdkruntime-client-documentation.md b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/androidx-privacysandbox-sdkruntime-sdkruntime-client-documentation.md
similarity index 100%
rename from privacysandbox/sdkruntime/sdkruntime-client/src/main/androidx/privacysandbox/sdkruntime/androidx-privacysandbox-sdkruntime-sdkruntime-client-documentation.md
rename to privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/androidx-privacysandbox-sdkruntime-sdkruntime-client-documentation.md
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/api/current.txt b/privacysandbox/sdkruntime/sdkruntime-core/api/current.txt
index e6f50d0..c4a2898 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/api/current.txt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/api/current.txt
@@ -1 +1,61 @@
 // Signature format: 4.0
+package androidx.privacysandbox.sdkruntime.core {
+
+  public final class LoadSdkCompatException extends java.lang.Exception {
+    ctor public LoadSdkCompatException(Throwable cause, android.os.Bundle extraInfo);
+    method public android.os.Bundle getExtraInformation();
+    method public int getLoadSdkErrorCode();
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException toLoadCompatSdkException(android.app.sdksandbox.LoadSdkException ex);
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public android.app.sdksandbox.LoadSdkException toLoadSdkException();
+    property public final android.os.Bundle extraInformation;
+    property public final int loadSdkErrorCode;
+    field public static final androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion Companion;
+    field public static final int LOAD_SDK_ALREADY_LOADED = 101; // 0x65
+    field public static final int LOAD_SDK_INTERNAL_ERROR = 500; // 0x1f4
+    field public static final int LOAD_SDK_NOT_FOUND = 100; // 0x64
+    field public static final int LOAD_SDK_SDK_DEFINED_ERROR = 102; // 0x66
+    field public static final int LOAD_SDK_SDK_SANDBOX_DISABLED = 103; // 0x67
+    field public static final int SDK_SANDBOX_PROCESS_NOT_AVAILABLE = 503; // 0x1f7
+  }
+
+  public static final class LoadSdkCompatException.Companion {
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException toLoadCompatSdkException(android.app.sdksandbox.LoadSdkException ex);
+  }
+
+  public abstract sealed class SandboxedSdkCompat {
+    method public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat create(android.os.IBinder binder);
+    method @DoNotInline public abstract android.os.IBinder? getInterface();
+    method @DoNotInline @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public abstract android.app.sdksandbox.SandboxedSdk toSandboxedSdk();
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat toSandboxedSdkCompat(android.app.sdksandbox.SandboxedSdk sandboxedSdk);
+    field public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.Companion Companion;
+  }
+
+  public static final class SandboxedSdkCompat.Companion {
+    method public androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat create(android.os.IBinder binder);
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat toSandboxedSdkCompat(android.app.sdksandbox.SandboxedSdk sandboxedSdk);
+  }
+
+  @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public final class SandboxedSdkProviderAdapter extends android.app.sdksandbox.SandboxedSdkProvider {
+    ctor public SandboxedSdkProviderAdapter();
+    method @VisibleForTesting public androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat? getDelegate();
+    method public android.view.View getView(android.content.Context windowContext, android.os.Bundle params, int width, int height);
+    method @kotlin.jvm.Throws(exceptionClasses=LoadSdkException::class) public android.app.sdksandbox.SandboxedSdk onLoadSdk(android.os.Bundle params) throws android.app.sdksandbox.LoadSdkException;
+    property @VisibleForTesting public final androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat? delegate;
+    field public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderAdapter.Companion Companion;
+  }
+
+  public static final class SandboxedSdkProviderAdapter.Companion {
+  }
+
+  public abstract class SandboxedSdkProviderCompat {
+    ctor public SandboxedSdkProviderCompat();
+    method public final void attachContext(android.content.Context context);
+    method public void beforeUnloadSdk();
+    method public final android.content.Context? getContext();
+    method public abstract android.view.View getView(android.content.Context windowContext, android.os.Bundle params, int width, int height);
+    method @kotlin.jvm.Throws(exceptionClasses=LoadSdkCompatException::class) public abstract androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat onLoadSdk(android.os.Bundle params) throws androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException;
+    property public final android.content.Context? context;
+  }
+
+}
+
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/api/public_plus_experimental_current.txt b/privacysandbox/sdkruntime/sdkruntime-core/api/public_plus_experimental_current.txt
index e6f50d0..c4a2898 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/api/public_plus_experimental_current.txt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/api/public_plus_experimental_current.txt
@@ -1 +1,61 @@
 // Signature format: 4.0
+package androidx.privacysandbox.sdkruntime.core {
+
+  public final class LoadSdkCompatException extends java.lang.Exception {
+    ctor public LoadSdkCompatException(Throwable cause, android.os.Bundle extraInfo);
+    method public android.os.Bundle getExtraInformation();
+    method public int getLoadSdkErrorCode();
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException toLoadCompatSdkException(android.app.sdksandbox.LoadSdkException ex);
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public android.app.sdksandbox.LoadSdkException toLoadSdkException();
+    property public final android.os.Bundle extraInformation;
+    property public final int loadSdkErrorCode;
+    field public static final androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion Companion;
+    field public static final int LOAD_SDK_ALREADY_LOADED = 101; // 0x65
+    field public static final int LOAD_SDK_INTERNAL_ERROR = 500; // 0x1f4
+    field public static final int LOAD_SDK_NOT_FOUND = 100; // 0x64
+    field public static final int LOAD_SDK_SDK_DEFINED_ERROR = 102; // 0x66
+    field public static final int LOAD_SDK_SDK_SANDBOX_DISABLED = 103; // 0x67
+    field public static final int SDK_SANDBOX_PROCESS_NOT_AVAILABLE = 503; // 0x1f7
+  }
+
+  public static final class LoadSdkCompatException.Companion {
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException toLoadCompatSdkException(android.app.sdksandbox.LoadSdkException ex);
+  }
+
+  public abstract sealed class SandboxedSdkCompat {
+    method public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat create(android.os.IBinder binder);
+    method @DoNotInline public abstract android.os.IBinder? getInterface();
+    method @DoNotInline @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public abstract android.app.sdksandbox.SandboxedSdk toSandboxedSdk();
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat toSandboxedSdkCompat(android.app.sdksandbox.SandboxedSdk sandboxedSdk);
+    field public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.Companion Companion;
+  }
+
+  public static final class SandboxedSdkCompat.Companion {
+    method public androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat create(android.os.IBinder binder);
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat toSandboxedSdkCompat(android.app.sdksandbox.SandboxedSdk sandboxedSdk);
+  }
+
+  @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public final class SandboxedSdkProviderAdapter extends android.app.sdksandbox.SandboxedSdkProvider {
+    ctor public SandboxedSdkProviderAdapter();
+    method @VisibleForTesting public androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat? getDelegate();
+    method public android.view.View getView(android.content.Context windowContext, android.os.Bundle params, int width, int height);
+    method @kotlin.jvm.Throws(exceptionClasses=LoadSdkException::class) public android.app.sdksandbox.SandboxedSdk onLoadSdk(android.os.Bundle params) throws android.app.sdksandbox.LoadSdkException;
+    property @VisibleForTesting public final androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat? delegate;
+    field public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderAdapter.Companion Companion;
+  }
+
+  public static final class SandboxedSdkProviderAdapter.Companion {
+  }
+
+  public abstract class SandboxedSdkProviderCompat {
+    ctor public SandboxedSdkProviderCompat();
+    method public final void attachContext(android.content.Context context);
+    method public void beforeUnloadSdk();
+    method public final android.content.Context? getContext();
+    method public abstract android.view.View getView(android.content.Context windowContext, android.os.Bundle params, int width, int height);
+    method @kotlin.jvm.Throws(exceptionClasses=LoadSdkCompatException::class) public abstract androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat onLoadSdk(android.os.Bundle params) throws androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException;
+    property public final android.content.Context? context;
+  }
+
+}
+
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/api/restricted_current.txt b/privacysandbox/sdkruntime/sdkruntime-core/api/restricted_current.txt
index e6f50d0..c4a2898 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/api/restricted_current.txt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/api/restricted_current.txt
@@ -1 +1,61 @@
 // Signature format: 4.0
+package androidx.privacysandbox.sdkruntime.core {
+
+  public final class LoadSdkCompatException extends java.lang.Exception {
+    ctor public LoadSdkCompatException(Throwable cause, android.os.Bundle extraInfo);
+    method public android.os.Bundle getExtraInformation();
+    method public int getLoadSdkErrorCode();
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException toLoadCompatSdkException(android.app.sdksandbox.LoadSdkException ex);
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public android.app.sdksandbox.LoadSdkException toLoadSdkException();
+    property public final android.os.Bundle extraInformation;
+    property public final int loadSdkErrorCode;
+    field public static final androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion Companion;
+    field public static final int LOAD_SDK_ALREADY_LOADED = 101; // 0x65
+    field public static final int LOAD_SDK_INTERNAL_ERROR = 500; // 0x1f4
+    field public static final int LOAD_SDK_NOT_FOUND = 100; // 0x64
+    field public static final int LOAD_SDK_SDK_DEFINED_ERROR = 102; // 0x66
+    field public static final int LOAD_SDK_SDK_SANDBOX_DISABLED = 103; // 0x67
+    field public static final int SDK_SANDBOX_PROCESS_NOT_AVAILABLE = 503; // 0x1f7
+  }
+
+  public static final class LoadSdkCompatException.Companion {
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException toLoadCompatSdkException(android.app.sdksandbox.LoadSdkException ex);
+  }
+
+  public abstract sealed class SandboxedSdkCompat {
+    method public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat create(android.os.IBinder binder);
+    method @DoNotInline public abstract android.os.IBinder? getInterface();
+    method @DoNotInline @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public abstract android.app.sdksandbox.SandboxedSdk toSandboxedSdk();
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat toSandboxedSdkCompat(android.app.sdksandbox.SandboxedSdk sandboxedSdk);
+    field public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.Companion Companion;
+  }
+
+  public static final class SandboxedSdkCompat.Companion {
+    method public androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat create(android.os.IBinder binder);
+    method @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat toSandboxedSdkCompat(android.app.sdksandbox.SandboxedSdk sandboxedSdk);
+  }
+
+  @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public final class SandboxedSdkProviderAdapter extends android.app.sdksandbox.SandboxedSdkProvider {
+    ctor public SandboxedSdkProviderAdapter();
+    method @VisibleForTesting public androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat? getDelegate();
+    method public android.view.View getView(android.content.Context windowContext, android.os.Bundle params, int width, int height);
+    method @kotlin.jvm.Throws(exceptionClasses=LoadSdkException::class) public android.app.sdksandbox.SandboxedSdk onLoadSdk(android.os.Bundle params) throws android.app.sdksandbox.LoadSdkException;
+    property @VisibleForTesting public final androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat? delegate;
+    field public static final androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderAdapter.Companion Companion;
+  }
+
+  public static final class SandboxedSdkProviderAdapter.Companion {
+  }
+
+  public abstract class SandboxedSdkProviderCompat {
+    ctor public SandboxedSdkProviderCompat();
+    method public final void attachContext(android.content.Context context);
+    method public void beforeUnloadSdk();
+    method public final android.content.Context? getContext();
+    method public abstract android.view.View getView(android.content.Context windowContext, android.os.Bundle params, int width, int height);
+    method @kotlin.jvm.Throws(exceptionClasses=LoadSdkCompatException::class) public abstract androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat onLoadSdk(android.os.Bundle params) throws androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException;
+    property public final android.content.Context? context;
+  }
+
+}
+
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/build.gradle b/privacysandbox/sdkruntime/sdkruntime-core/build.gradle
index 2bb6772..f6bd870 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/build.gradle
+++ b/privacysandbox/sdkruntime/sdkruntime-core/build.gradle
@@ -24,6 +24,22 @@
 
 dependencies {
     api(libs.kotlinStdlib)
+    api("androidx.annotation:annotation:1.2.0")
+
+    //Needed for BuildCompat.isAtLeastU().
+    // TODO(b/249981547) Remove as soon as SdkSandbox APIs dropped to T
+    implementation("androidx.core:core:1.8.0")
+
+    // TODO(b/249982004): cleanup dependencies
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.truth)
+    androidTestImplementation(libs.junit)
+
+    androidTestImplementation(libs.mockitoCore, excludes.bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(libs.dexmakerMockitoInline, excludes.bytebuddy) // DexMaker has it"s own MockMaker
 }
 
 android {
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/AndroidManifest.xml b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..3f2e804
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 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">
+</manifest>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt
new file mode 100644
index 0000000..abd8e9c
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2022 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.sdkruntime.core
+
+import android.app.sdksandbox.LoadSdkException
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.Bundle
+import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.toLoadCompatSdkException
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+// TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+@SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class LoadSdkCompatExceptionTest {
+
+    @Test
+    fun toLoadSdkException_returnLoadSdkException() {
+        val loadSdkCompatException = LoadSdkCompatException(RuntimeException(), Bundle())
+
+        val loadSdkException = loadSdkCompatException.toLoadSdkException()
+
+        assertThat(loadSdkException.cause)
+            .isSameInstanceAs(loadSdkCompatException.cause)
+        assertThat(loadSdkException.extraInformation)
+            .isSameInstanceAs(loadSdkCompatException.extraInformation)
+        assertThat(loadSdkException.loadSdkErrorCode)
+            .isEqualTo(loadSdkCompatException.loadSdkErrorCode)
+    }
+
+    @Test
+    fun toLoadCompatSdkException_returnLoadCompatSdkException() {
+        val loadSdkException = LoadSdkException(
+            RuntimeException(),
+            Bundle()
+        )
+
+        val loadCompatSdkException = toLoadCompatSdkException(loadSdkException)
+
+        assertThat(loadCompatSdkException.cause)
+            .isSameInstanceAs(loadSdkException.cause)
+        assertThat(loadCompatSdkException.extraInformation)
+            .isSameInstanceAs(loadSdkException.extraInformation)
+        assertThat(loadCompatSdkException.loadSdkErrorCode)
+            .isEqualTo(loadSdkException.loadSdkErrorCode)
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt
new file mode 100644
index 0000000..1a96f8e
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2022 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.sdkruntime.core
+
+import android.app.sdksandbox.SandboxedSdk
+import android.os.Binder
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.Companion.create
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.Companion.toSandboxedSdkCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SandboxedSdkCompatTest {
+
+    @Test
+    fun getInterface_returnsBinderPassedToCreate() {
+        val binder = Binder()
+
+        val sandboxedSdkCompat = create(binder)
+
+        assertThat(sandboxedSdkCompat.getInterface())
+            .isSameInstanceAs(binder)
+    }
+
+    @Test
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+    fun toSandboxedSdk_whenCreatedFromBinder_returnsSandboxedSdkWithSameBinder() {
+        val binder = Binder()
+
+        val toSandboxedSdkResult = create(binder).toSandboxedSdk()
+
+        assertThat(toSandboxedSdkResult.getInterface())
+            .isSameInstanceAs(binder)
+    }
+
+    @Test
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+    fun toSandboxedSdk_whenCreatedFromSandboxedSdk_returnsSameSandboxedSdk() {
+        val binder = Binder()
+        val sandboxedSdk = SandboxedSdk(binder)
+
+        val toSandboxedSdkResult = toSandboxedSdkCompat(sandboxedSdk).toSandboxedSdk()
+
+        assertThat(toSandboxedSdkResult)
+            .isSameInstanceAs(sandboxedSdk)
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapterTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapterTest.kt
new file mode 100644
index 0000000..77f9136
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapterTest.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2022 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.sdkruntime.core
+
+import android.app.sdksandbox.LoadSdkException
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Binder
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.Bundle
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.Companion.create
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when`
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+// TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+@SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@RequiresApi(api = UPSIDE_DOWN_CAKE)
+class SandboxedSdkProviderAdapterTest {
+
+    private lateinit var mContext: Context
+    private lateinit var mPackageManager: PackageManager
+    private lateinit var providerUnderTest: SandboxedSdkProviderAdapter
+
+    @Before
+    fun setUp() {
+        mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+        mPackageManager = mock(PackageManager::class.java)
+        `when`(mContext.packageManager).thenReturn(mPackageManager)
+        providerUnderTest = SandboxedSdkProviderAdapter()
+        providerUnderTest.attachContext(mContext)
+    }
+
+    @Test
+    @Throws(LoadSdkException::class)
+    fun onLoadSdk_shouldInstantiateDelegateAndAttachContext() {
+        setupDelegate(TestOnLoadReturnResultSdkProvider::class.java)
+
+        providerUnderTest.onLoadSdk(Bundle())
+
+        val delegate = extractDelegate(TestOnLoadReturnResultSdkProvider::class.java)
+        assertThat(delegate.context)
+            .isSameInstanceAs(mContext)
+    }
+
+    @Test
+    @Throws(LoadSdkException::class)
+    fun onLoadSdk_shouldDelegateToCompatClassAndReturnResult() {
+        setupDelegate(TestOnLoadReturnResultSdkProvider::class.java)
+        val params = Bundle()
+
+        val result = providerUnderTest.onLoadSdk(params)
+
+        val delegate = extractDelegate(TestOnLoadReturnResultSdkProvider::class.java)
+        assertThat(delegate.mLastOnLoadSdkBundle)
+            .isSameInstanceAs(params)
+        assertThat(result)
+            .isEqualTo(delegate.mResult.toSandboxedSdk())
+    }
+
+    @Test
+    fun loadSdk_shouldRethrowExceptionFromCompatClass() {
+        setupDelegate(TestOnLoadThrowSdkProvider::class.java)
+
+        val ex = assertThrows(LoadSdkException::class.java) {
+            providerUnderTest.onLoadSdk(Bundle())
+        }
+
+        val delegate = extractDelegate(TestOnLoadThrowSdkProvider::class.java)
+        assertThat(ex.cause)
+            .isSameInstanceAs(delegate.mError.cause)
+        assertThat(ex.extraInformation)
+            .isSameInstanceAs(delegate.mError.extraInformation)
+    }
+
+    @Test
+    fun loadSdk_shouldThrowIfCompatClassNotExists() {
+        setupDelegateClassname("NOTEXISTS")
+
+        assertThrows(RuntimeException::class.java) {
+            providerUnderTest.onLoadSdk(Bundle())
+        }
+    }
+
+    @Test
+    fun beforeUnloadSdk_shouldDelegateToCompatProvider() {
+        setupDelegate(TestOnBeforeUnloadDelegateSdkProvider::class.java)
+
+        providerUnderTest.beforeUnloadSdk()
+
+        val delegate = extractDelegate(TestOnBeforeUnloadDelegateSdkProvider::class.java)
+        assertThat(delegate.mBeforeUnloadSdkCalled)
+            .isTrue()
+    }
+
+    @Test
+    fun getView_shouldDelegateToCompatProviderAndReturnResult() {
+        setupDelegate(TestGetViewSdkProvider::class.java)
+        val windowContext = mock(Context::class.java)
+        val params = Bundle()
+        val width = 1
+        val height = 2
+
+        val result = providerUnderTest.getView(windowContext, params, width, height)
+
+        val delegate = extractDelegate(TestGetViewSdkProvider::class.java)
+        assertThat(result)
+            .isSameInstanceAs(delegate.mView)
+        assertThat(delegate.mLastWindowContext)
+            .isSameInstanceAs(windowContext)
+        assertThat(delegate.mLastParams)
+            .isSameInstanceAs(params)
+        assertThat(delegate.mLastWidth)
+            .isSameInstanceAs(width)
+        assertThat(delegate.mLastHeigh)
+            .isSameInstanceAs(height)
+    }
+
+    private fun setupDelegate(clazz: Class<out SandboxedSdkProviderCompat>) {
+        setupDelegateClassname(clazz.name)
+    }
+
+    private fun <T : SandboxedSdkProviderCompat?> extractDelegate(clazz: Class<T>): T {
+        return clazz.cast(providerUnderTest.delegate)!!
+    }
+
+    private fun setupDelegateClassname(className: String) {
+        val property = mock(PackageManager.Property::class.java)
+        try {
+            `when`(
+                mPackageManager.getProperty(
+                    eq("android.sdksandbox.PROPERTY_COMPAT_SDK_PROVIDER_CLASS_NAME"),
+                    any(String::class.java)
+                )
+            ).thenReturn(property)
+        } catch (ignored: PackageManager.NameNotFoundException) {
+        }
+        `when`(property.string)
+            .thenReturn(className)
+    }
+
+    class TestOnLoadReturnResultSdkProvider : SandboxedSdkProviderCompat() {
+        var mResult = create(Binder())
+        var mLastOnLoadSdkBundle: Bundle? = null
+
+        override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
+            mLastOnLoadSdkBundle = params
+            return mResult
+        }
+
+        override fun getView(
+            windowContext: Context,
+            params: Bundle,
+            width: Int,
+            height: Int
+        ): View {
+            throw RuntimeException("Not implemented")
+        }
+    }
+
+    class TestOnLoadThrowSdkProvider : SandboxedSdkProviderCompat() {
+        var mError = LoadSdkCompatException(RuntimeException(), Bundle())
+
+        @Throws(LoadSdkCompatException::class)
+        override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
+            throw mError
+        }
+
+        override fun getView(
+            windowContext: Context,
+            params: Bundle,
+            width: Int,
+            height: Int
+        ): View {
+            throw RuntimeException("Stub!")
+        }
+    }
+
+    class TestOnBeforeUnloadDelegateSdkProvider : SandboxedSdkProviderCompat() {
+        var mBeforeUnloadSdkCalled = false
+
+        override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
+            throw RuntimeException("Not implemented")
+        }
+
+        override fun beforeUnloadSdk() {
+            mBeforeUnloadSdkCalled = true
+        }
+
+        override fun getView(
+            windowContext: Context,
+            params: Bundle,
+            width: Int,
+            height: Int
+        ): View {
+            throw RuntimeException("Not implemented")
+        }
+    }
+
+    class TestGetViewSdkProvider : SandboxedSdkProviderCompat() {
+        val mView: View = mock(View::class.java)
+
+        var mLastWindowContext: Context? = null
+        var mLastParams: Bundle? = null
+        var mLastWidth = 0
+        var mLastHeigh = 0
+
+        override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
+            throw RuntimeException("Not implemented")
+        }
+
+        override fun getView(
+            windowContext: Context,
+            params: Bundle,
+            width: Int,
+            height: Int
+        ): View {
+            mLastWindowContext = windowContext
+            mLastParams = params
+            mLastWidth = width
+            mLastHeigh = height
+
+            return mView
+        }
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt
new file mode 100644
index 0000000..d929adc
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2022 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.sdkruntime.core
+
+import android.app.sdksandbox.LoadSdkException
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.Bundle
+import androidx.annotation.DoNotInline
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
+
+/**
+ * Compat alternative for [LoadSdkException].
+ * Thrown from [SandboxedSdkProviderCompat.onLoadSdk].
+ *
+ * @see [LoadSdkException]
+ */
+class LoadSdkCompatException @JvmOverloads internal constructor(
+    @field:LoadSdkErrorCode @get:LoadSdkErrorCode
+    @param:LoadSdkErrorCode val loadSdkErrorCode: Int,
+    message: String?,
+    cause: Throwable?,
+    val extraInformation: Bundle = Bundle()
+) : Exception(message, cause) {
+
+    constructor(
+        cause: Throwable,
+        extraInfo: Bundle
+    ) : this(LOAD_SDK_SDK_DEFINED_ERROR, "", cause, extraInfo)
+
+    @RestrictTo(LIBRARY_GROUP)
+    constructor(
+        @LoadSdkErrorCode loadSdkErrorCode: Int,
+        message: String?
+    ) : this(loadSdkErrorCode, message, /*cause=*/null)
+
+    /** @hide */
+    @IntDef(
+        SDK_SANDBOX_PROCESS_NOT_AVAILABLE,
+        LOAD_SDK_NOT_FOUND,
+        LOAD_SDK_ALREADY_LOADED,
+        LOAD_SDK_SDK_DEFINED_ERROR,
+        LOAD_SDK_SDK_SANDBOX_DISABLED,
+        LOAD_SDK_INTERNAL_ERROR,
+    )
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @Retention(AnnotationRetention.SOURCE)
+    annotation class LoadSdkErrorCode
+
+    /**
+     *  Create platform [LoadSdkException] from compat exception.
+     *
+     *  @return Platform exception.
+     */
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @RequiresApi(UPSIDE_DOWN_CAKE)
+    fun toLoadSdkException(): LoadSdkException {
+        return Api33Impl.toLoadSdkException(this)
+    }
+
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @RequiresApi(UPSIDE_DOWN_CAKE)
+    private object Api33Impl {
+
+        @DoNotInline
+        fun toLoadSdkException(ex: LoadSdkCompatException): LoadSdkException {
+            return LoadSdkException(
+                ex.cause!!,
+                ex.extraInformation
+            )
+        }
+
+        @DoNotInline
+        fun toLoadCompatSdkException(ex: LoadSdkException): LoadSdkCompatException {
+            return LoadSdkCompatException(
+                toLoadSdkErrorCodeCompat(ex.loadSdkErrorCode),
+                ex.message,
+                ex.cause,
+                ex.extraInformation
+            )
+        }
+
+        @LoadSdkErrorCode
+        private fun toLoadSdkErrorCodeCompat(
+            value: Int
+        ): Int {
+            return value // TODO(b/249982002): Validate and convert
+        }
+    }
+
+    companion object {
+
+        /**
+         * Sdk sandbox process is not available.
+         *
+         * @see [android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_PROCESS_NOT_AVAILABLE]
+         */
+        const val SDK_SANDBOX_PROCESS_NOT_AVAILABLE = 503
+
+        /**
+         * SDK not found.
+         *
+         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_NOT_FOUND]
+         */
+        const val LOAD_SDK_NOT_FOUND = 100
+
+        /**
+         * SDK is already loaded.
+         *
+         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_ALREADY_LOADED]
+         */
+        const val LOAD_SDK_ALREADY_LOADED = 101
+
+        /**
+         * SDK error after being loaded.
+         *
+         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_SDK_DEFINED_ERROR]
+         */
+        const val LOAD_SDK_SDK_DEFINED_ERROR = 102
+
+        /**
+         * SDK sandbox is disabled.
+         *
+         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_SDK_SANDBOX_DISABLED]
+         */
+        const val LOAD_SDK_SDK_SANDBOX_DISABLED = 103
+
+        /** Internal error while loading SDK.
+         *
+         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR]
+         */
+        const val LOAD_SDK_INTERNAL_ERROR = 500
+
+        /**
+         *  Create compat exception from platform [LoadSdkException].
+         *
+         *  @param ex Platform exception
+         *  @return Compat exception.
+         */
+        // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+        @RequiresApi(UPSIDE_DOWN_CAKE)
+        @JvmStatic
+        fun toLoadCompatSdkException(ex: LoadSdkException): LoadSdkCompatException {
+            return Api33Impl.toLoadCompatSdkException(ex)
+        }
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt
new file mode 100644
index 0000000..f93e88f
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2022 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.sdkruntime.core
+
+import android.app.sdksandbox.SandboxedSdk
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.IBinder
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.core.os.BuildCompat
+
+/**
+ * Compat wrapper for [SandboxedSdk].
+ * Represents an SDK loaded in the sandbox process or locally.
+ *
+ * @see [SandboxedSdk]
+ *
+ */
+sealed class SandboxedSdkCompat {
+
+    /**
+     * Returns the interface to the loaded SDK.
+     *
+     * @return Binder object for loaded SDK.
+     *
+     * @see [SandboxedSdk.getInterface]
+     */
+    @DoNotInline
+    abstract fun getInterface(): IBinder?
+
+    /**
+     * Create [SandboxedSdk] from compat object.
+     *
+     * @return Platform SandboxedSdk
+     */
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @RequiresApi(UPSIDE_DOWN_CAKE)
+    @DoNotInline
+    abstract fun toSandboxedSdk(): SandboxedSdk
+
+    // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+    @RequiresApi(UPSIDE_DOWN_CAKE)
+    private class Api33Impl(private val mSandboxedSdk: SandboxedSdk) : SandboxedSdkCompat() {
+        constructor(binder: IBinder) : this(createSandboxedSdk(binder))
+
+        @DoNotInline
+        override fun getInterface(): IBinder? {
+            return mSandboxedSdk.getInterface()
+        }
+
+        @DoNotInline
+        override fun toSandboxedSdk(): SandboxedSdk {
+            return mSandboxedSdk
+        }
+
+        companion object {
+            fun createSandboxedSdk(binder: IBinder): SandboxedSdk {
+                return SandboxedSdk(binder)
+            }
+        }
+    }
+
+    private class CompatImpl(private val mInterface: IBinder) : SandboxedSdkCompat() {
+
+        @DoNotInline
+        override fun getInterface(): IBinder? {
+            return mInterface
+        }
+
+        // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+        @RequiresApi(UPSIDE_DOWN_CAKE)
+        @DoNotInline
+        override fun toSandboxedSdk(): SandboxedSdk {
+            // avoid class verifications errors
+            return Api33Impl.createSandboxedSdk(mInterface)
+        }
+    }
+
+    companion object {
+        /**
+         *  Creates [SandboxedSdkCompat] object from SDK Binder object.
+         *
+         *  @param binder Binder object for SDK Interface
+         *  @return SandboxedSdkCompat object
+         *
+         *  @see [SandboxedSdk]
+         */
+        @JvmStatic
+        @androidx.annotation.OptIn(markerClass = [BuildCompat.PrereleaseSdkCheck::class])
+        fun create(binder: IBinder): SandboxedSdkCompat {
+            // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+            return if (BuildCompat.isAtLeastU()) {
+                Api33Impl(binder)
+            } else {
+                CompatImpl(binder)
+            }
+        }
+
+        /**
+         *  Creates [SandboxedSdkCompat] object from existing [SandboxedSdk] object.
+         *
+         *  @param sandboxedSdk SandboxedSdk object
+         *  @return SandboxedSdkCompat object
+         */
+        // TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+        @RequiresApi(UPSIDE_DOWN_CAKE)
+        @JvmStatic
+        fun toSandboxedSdkCompat(sandboxedSdk: SandboxedSdk): SandboxedSdkCompat {
+            return Api33Impl(sandboxedSdk)
+        }
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt
new file mode 100644
index 0000000..7ea27aa
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2022 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.sdkruntime.core
+
+import android.app.sdksandbox.LoadSdkException
+import android.app.sdksandbox.SandboxedSdk
+import android.app.sdksandbox.SandboxedSdkProvider
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
+
+/**
+ * Implementation of platform [SandboxedSdkProvider] that delegate to [SandboxedSdkProviderCompat]
+ * Gets compat class name from property "android.sdksandbox.PROPERTY_COMPAT_SDK_PROVIDER_CLASS_NAME"
+ *
+ */
+// TODO(b/249981547) Update check when prebuilt with SdkSandbox APIs dropped to T
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class SandboxedSdkProviderAdapter : SandboxedSdkProvider() {
+
+    @get:VisibleForTesting
+    @Volatile
+    var delegate: SandboxedSdkProviderCompat? = null
+        private set
+
+    @Throws(LoadSdkException::class)
+    override fun onLoadSdk(params: Bundle): SandboxedSdk {
+        return try {
+            ensureDelegateLoaded().onLoadSdk(params).toSandboxedSdk()
+        } catch (e: LoadSdkCompatException) {
+            throw e.toLoadSdkException()
+        }
+    }
+
+    override fun beforeUnloadSdk() {
+        ensureDelegateLoaded().beforeUnloadSdk()
+    }
+
+    override fun getView(
+        windowContext: Context,
+        params: Bundle,
+        width: Int,
+        height: Int
+    ): View {
+        return ensureDelegateLoaded().getView(windowContext, params, width, height)
+    }
+
+    private fun ensureDelegateLoaded(): SandboxedSdkProviderCompat {
+        val existing1 = delegate
+        if (existing1 != null) {
+            return existing1
+        }
+        synchronized(this) {
+            val existing2 = delegate
+            if (existing2 != null) {
+                return existing2
+            }
+            val currentContext = context!!
+            return try {
+                val jetPackSdkProviderClassName = getCompatProviderClassName(currentContext)
+                val clz = Class.forName(jetPackSdkProviderClassName)
+                val newDelegate = clz.getConstructor().newInstance() as SandboxedSdkProviderCompat
+                newDelegate.attachContext(currentContext)
+                delegate = newDelegate
+                newDelegate
+            } catch (ex: Exception) {
+                throw RuntimeException("Failed to instantiate SandboxedSdkProviderCompat", ex)
+            }
+        }
+    }
+
+    @Throws(PackageManager.NameNotFoundException::class)
+    private fun getCompatProviderClassName(context: Context): String {
+        return context.packageManager.getProperty(
+            COMPAT_SDK_PROVIDER_CLASS_NAME_ATTRIBUTE_NAME,
+            context.packageName
+        ).string!!
+    }
+
+    companion object {
+        private const val COMPAT_SDK_PROVIDER_CLASS_NAME_ATTRIBUTE_NAME =
+            "android.sdksandbox.PROPERTY_COMPAT_SDK_PROVIDER_CLASS_NAME"
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderCompat.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderCompat.kt
new file mode 100644
index 0000000..791db26
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderCompat.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2022 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.sdkruntime.core
+
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+
+/**
+ * Compat version of [android.app.sdksandbox.SandboxedSdkProvider].
+ *
+ * SDK has to implement this abstract class to generate an entry point for SDK sandbox to be able
+ *  to call it through.
+ *
+ * @see [android.app.sdksandbox.SandboxedSdkProvider]
+ */
+abstract class SandboxedSdkProviderCompat {
+    var context: Context? = null
+        private set
+
+    /**
+     * Sets the SDK [Context] which can then be received using [SandboxedSdkProviderCompat.context]
+     *
+     * @param context The new base context.
+     *
+     * @see [android.app.sdksandbox.SandboxedSdkProvider.attachContext]
+     */
+    fun attachContext(context: Context) {
+        check(this.context == null) { "Context already set" }
+        this.context = context
+    }
+
+    /**
+     * Does the work needed for the SDK to start handling requests.
+     *
+     * @param params list of params passed from the client when it loads the SDK. This can be empty.
+     * @return Returns a [SandboxedSdkCompat], passed back to the client.
+     *
+     * @throws LoadSdkCompatException
+     *
+     * @see [android.app.sdksandbox.SandboxedSdkProvider.onLoadSdk]
+     */
+    @Throws(LoadSdkCompatException::class)
+    abstract fun onLoadSdk(params: Bundle): SandboxedSdkCompat
+
+    /**
+     * Does the work needed for the SDK to free its resources before being unloaded.
+     *
+     * @see [android.app.sdksandbox.SandboxedSdkProvider.beforeUnloadSdk]
+     */
+    open fun beforeUnloadSdk() {}
+
+    /**
+     * Requests a view to be remotely rendered to the client app process.
+     *
+     * @see [android.app.sdksandbox.SandboxedSdkProvider.getView]
+     */
+    abstract fun getView(
+        windowContext: Context,
+        params: Bundle,
+        width: Int,
+        height: Int
+    ): View
+}
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/androidx/privacysandbox/sdkruntime/androidx-privacysandbox-sdkruntime-sdkruntime-core-documentation.md b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/androidx-privacysandbox-sdkruntime-sdkruntime-core-documentation.md
similarity index 100%
rename from privacysandbox/sdkruntime/sdkruntime-core/src/main/androidx/privacysandbox/sdkruntime/androidx-privacysandbox-sdkruntime-sdkruntime-core-documentation.md
rename to privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/androidx-privacysandbox-sdkruntime-sdkruntime-core-documentation.md