Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Remove thread blocking logic from SdkProvider implementation #129

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,13 @@ class MainActivity : AppCompatActivity() {
// Mediated Banner Ad is shown when RUNTIME_MEDIATEE Mediation option is chosen.
val mediationType =
MediationOption.entries[mediationDropDownMenu.selectedItemId.toInt()].toString()
if (mediationType == MediationOption.INAPP_MEDIATEE.toString()) {
makeToast("RE_SDK<>InApp Mediated Banner Ad not yet implemented!")
} else {
bannerAd.loadAd(
this@MainActivity,
PACKAGE_NAME,
shouldStartActivityPredicate(),
loadWebView,
mediationType
)
}
bannerAd.loadAd(
this@MainActivity,
PACKAGE_NAME,
shouldStartActivityPredicate(),
loadWebView,
mediationType
)
}

private fun showFullscreenView() = lifecycleScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,23 @@
*/
package com.example.api

import android.os.Bundle
import androidx.privacysandbox.tools.PrivacySandboxService

@PrivacySandboxService
interface SdkService {

/**
* App has to call this API after loadSdk("mediator") call.
* Mediatee and Adapter SDKs are loaded by the Mediator when this API is called.
*/
suspend fun initialise()

suspend fun getMessage(): String

suspend fun createFile(sizeInMb: Int): String

suspend fun getBanner(request: SdkBannerRequest, mediationType: String): SdkSandboxedUiAdapter?
suspend fun getBanner(request: SdkBannerRequest, mediationType: String): Bundle?

suspend fun getFullscreenAd(mediationType: String): FullscreenAd

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,16 @@
package com.example.implementation

import android.content.Context
import android.os.Bundle
import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
import com.example.api.AbstractSandboxedSdkProviderCompat
import com.example.api.SdkService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

/** Provides an [SdkService] implementation when the SDK is loaded. */
class SdkProvider : AbstractSandboxedSdkProviderCompat() {

private val adapterSdkName = "com.mediateeadapter.sdk"

private val coroutineScope = CoroutineScope(Dispatchers.Main)

/**
* Returns the [SdkService] implementation. Called when the SDK is loaded.
*
* This method signature (and the [AbstractSandboxedSdkProviderCompat] class) is generated by
* the Privacy Sandbox API Compiler plugin as the entry point for the app/SDK communication.
*/
override fun createSdkService(context: Context): SdkService = SdkServiceImpl(context)

/**
* Does the work needed for the SDK to start handling requests. SDK should do any work to be
* ready to handle upcoming requests.
*
* This function is called by the SDK sandbox after it loads the SDK.
*
* Mediator initialises the Runtime-enabled adapters in its own initialisation call.
*/
override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
coroutineScope.launch {
initialiseAdapters()
}
return super.onLoadSdk(params)
}

private suspend fun initialiseAdapters() {
SdkSandboxControllerCompat.from(checkNotNull(context) { "Cannot initialise adapters!" })
.loadSdk(adapterSdkName, Bundle.EMPTY)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import android.content.Context
import android.os.Bundle
import android.os.RemoteException
import android.util.Log
import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
import com.example.R
import com.example.api.FullscreenAd
Expand All @@ -33,17 +34,27 @@ import androidx.privacysandbox.ui.core.SandboxedSdkViewUiInfo
import androidx.privacysandbox.ui.core.SessionObserver
import androidx.privacysandbox.ui.core.SessionObserverContext
import androidx.privacysandbox.ui.core.SessionObserverFactory
import androidx.privacysandbox.ui.provider.toCoreLibInfo
import com.example.api.MediateeAdapterInterface
import com.example.api.SdkSandboxedUiAdapter

class SdkServiceImpl(private val context: Context) : SdkService {
override suspend fun getMessage(): String = "Hello from Privacy Sandbox!"

private var inAppMediateeAdapter: MediateeAdapterInterface? = null
private var mediateeAdapter: MediateeAdapterInterface? = null


private val adapterSdkName = "com.mediateeadapter.sdk"
private val mediateeSdkName = "com.mediatee.sdk"
private val tag = "ExampleSdk"

override suspend fun initialise() {
val sandboxController = SdkSandboxControllerCompat.from(context)
sandboxController.loadSdk(mediateeSdkName, Bundle.EMPTY)
// Adapter should only be loaded after Mediatee is loaded.
sandboxController.loadSdk(adapterSdkName, Bundle.EMPTY)
}

override suspend fun getMessage(): String = "Hello from Privacy Sandbox!"

override suspend fun createFile(sizeInMb: Int): String {
val path = Paths.get(
context.applicationContext.dataDir.path, "file.txt"
Expand All @@ -63,11 +74,21 @@ class SdkServiceImpl(private val context: Context) : SdkService {
override suspend fun getBanner(
request: SdkBannerRequest,
mediationType: String
): SdkSandboxedUiAdapter? {
): Bundle? {
if (mediationType == context.getString(R.string.mediation_option_none)) {
val bannerAdAdapter = SdkSandboxedUiAdapterImpl(context, request, null)
bannerAdAdapter.addObserverFactory(SessionObserverFactoryImpl())
return bannerAdAdapter
return bannerAdAdapter.toCoreLibInfo(context)
}
// For In-app mediatee, SandboxedUiAdapter returned by mediatee is not wrapped, it is
// directly returned to app. This is to avoid nested remote rendering.
// There is no overlay in this case for this reason.
if (mediationType == context.getString(R.string.mediation_option_inapp_mediatee)) {
return inAppMediateeAdapter?.getBannerAd(
request.appPackageName,
request.activityLauncher,
request.isWebViewBannerAd
)
}
return SdkSandboxedUiAdapterImpl(
context,
Expand All @@ -79,7 +100,7 @@ class SdkServiceImpl(private val context: Context) : SdkService {
request.isWebViewBannerAd
)
) { "No banner Ad received from mediatee!" })
)
).toCoreLibInfo(context)
}

override suspend fun getFullscreenAd(mediationType: String): FullscreenAd {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.privacysandbox.activity.client.createSdkActivityLauncher
import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
import androidx.privacysandbox.ui.client.view.SandboxedSdkView
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
import com.example.api.SdkBannerRequest
Expand Down Expand Up @@ -69,7 +70,12 @@ class BannerAd(context: Context, attrs: AttributeSet) : LinearLayout(context, at

val launcher = baseActivity.createSdkActivityLauncher(allowSdkActivityLaunch)
val request = SdkBannerRequest(message, launcher, shouldLoadWebView)
return ExistingSdk.loadSdkIfNeeded(context)?.getBanner(request, mediationType)
return SandboxedUiAdapterFactory.createFromCoreLibInfo(
checkNotNull(
ExistingSdk.loadSdkIfNeeded(
context
)?.getBanner(request, mediationType)
) { "No banner Ad received from ad SDK!" })
}

private fun addViewToLayout(view: View) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class ExistingSdk(private val context: Context) {

val sandboxedSdk = sandboxManagerCompat.loadSdk(SDK_NAME, Bundle.EMPTY)
remoteInstance = SdkServiceFactory.wrapToSdkService(sandboxedSdk.getInterface()!!)
// Initialise Adapters and Mediatees.
remoteInstance?.initialise()
return remoteInstance
} catch (e: LoadSdkCompatException) {
Log.e(TAG, "Failed to load SDK, error code: ${e.loadSdkErrorCode}", e)
Expand Down
9 changes: 9 additions & 0 deletions PrivacySandboxKotlin/inapp-mediatee-sdk-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# In App Mediatee Adapter SDK

This SDK is Runtime Aware but runs in the App process. It facilitates interaction between mediator
and in-app mediatee.

Implements MediateeAdapterInterface declared by mediator (example-sdk).

This could be owned by the mediator sdk during transition, or optionally all the logic here could
also be a part of RA_SDK (existing-sdk).
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,16 @@ dependencies {
debugImplementation project(':example-sdk-bundle')
implementation project(':inapp-mediatee-sdk')

implementation 'androidx.privacysandbox.activity:activity-core:1.0.0-alpha01'
implementation 'androidx.privacysandbox.activity:activity-provider:1.0.0-alpha01'
implementation 'androidx.activity:activity-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.7.0'

implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.10'
implementation "androidx.lifecycle:lifecycle-common:2.7.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1"

implementation "androidx.privacysandbox.ui:ui-core:$privacy_sandbox_ui_version"
implementation "androidx.privacysandbox.ui:ui-provider:$privacy_sandbox_ui_version"

implementation "androidx.privacysandbox.activity:activity-core:$privacy_sandbox_activity_version"
implementation "androidx.privacysandbox.activity:activity-provider:$privacy_sandbox_activity_version"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.inappmediateeadapter.implementation

import android.content.Context
import android.os.Bundle
import androidx.privacysandbox.ui.provider.toCoreLibInfo
import androidx.privacysandbox.activity.core.SdkActivityLauncher
import com.inappmediatee.sdk.InAppMediateeSdk
import com.example.api.MediateeAdapterInterface
Expand All @@ -20,7 +21,8 @@ class InAppMediateeSdkAdapter(private val context: Context): MediateeAdapterInte
activityLauncher: SdkActivityLauncher,
isWebViewBannerAd: Boolean
): Bundle {
TODO("Not yet implemented")
return SandboxedUiAdapterImpl(inAppMediateeSdk.loadBannerAd(isWebViewBannerAd))
.toCoreLibInfo(context)
}

override suspend fun loadFullscreenAd() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.inappmediateeadapter.implementation

import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.os.IBinder
import android.view.View
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
import androidx.privacysandbox.ui.core.SessionObserverFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancel
import java.util.concurrent.Executor

class SandboxedUiAdapterImpl(private val mediateeAdView: View): SandboxedUiAdapter {
override fun openSession(
context: Context,
windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
clientExecutor: Executor,
client: SandboxedUiAdapter.SessionClient
) {
val session = SdkUiSession(clientExecutor, mediateeAdView)
clientExecutor.execute {
client.onSessionOpened(session)
}
}

override fun addObserverFactory(sessionObserverFactory: SessionObserverFactory) {
// Adds a [SessionObserverFactory] with a [SandboxedUiAdapter] for tracking UI presentation
// state across UI sessions. This has no effect on already open sessions.
}

override fun removeObserverFactory(sessionObserverFactory: SessionObserverFactory) {
// Removes a [SessionObserverFactory] from a [SandboxedUiAdapter], if it has been
// previously added with [addObserverFactory].
}
}

private class SdkUiSession(clientExecutor: Executor, mediateeAdView: View) :
SandboxedUiAdapter.Session {

/** A scope for launching coroutines in the client executor. */
private val scope = CoroutineScope(clientExecutor.asCoroutineDispatcher() + Job())

override val signalOptions: Set<String> = setOf()

override val view: View = mediateeAdView

override fun close() {
// Notifies that the client has closed the session. It's a good opportunity to dispose
// any resources that were acquired to maintain the session.
scope.cancel()
}

override fun notifyConfigurationChanged(configuration: Configuration) {
// Notifies that the device configuration has changed and affected the app.
}

override fun notifyResized(width: Int, height: Int) {
// Notifies that the size of the presentation area in the app has changed.
}

override fun notifyUiChanged(uiContainerInfo: Bundle) {
// Notify the session when the presentation state of its UI container has changed.
}

override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
// Notifies that the Z order has changed for the UI associated by this session.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@ package com.inappmediatee.sdk

import android.content.Context
import android.content.Intent
import android.view.View
import android.webkit.WebView
import com.inappmediatee.R

class InAppMediateeSdk(private val context: Context) {

private val webViewUrl = "https://www.google.com"

fun loadBannerAd(isWebViewBannerAd: Boolean) : View {
if (isWebViewBannerAd) {
val webview = WebView(context)
webview.loadUrl(webViewUrl)
return webview
}
return View.inflate(context, R.layout.banner, null)
}

fun loadFullscreenAd() {
// All the heavy logic to load fullscreen Ad that Mdiatee needs to perform goes here.
// All the heavy logic to load fullscreen Ad that Mediatee needs to perform goes here.
}

fun showFullscreenAd() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2024 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp"
android:gravity="center_horizontal"
android:background="@android:color/holo_purple">

<LinearLayout
android:id="@+id/ad_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:background="@android:color/background_light">

<TextView
android:id="@+id/banner_header_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:textColor="@android:color/black"
android:text="@string/banner_ad_label"/>
</LinearLayout>
</LinearLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
-->
<resources>
<string name="activity_label">Full screen ad launched by in-app mediatee!</string>
<string name="banner_ad_label">Ad from In-app Mediatee SDK (no overlay from mediator)</string>
</resources>
Loading