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