Skip to content

Commit

Permalink
Add ReSdk<>ReSdk mediated full screen ad (#115)
Browse files Browse the repository at this point in the history
* ReSdk-ReSdk Mediation

* ReSdk-ReSdk Mediation

* ReSdk-ReSdk Mediation

* ReSdk-ReSdk Mediation

* ReSdk-ReSdk Mediation

* Load interstitial ad.

* Load RE_SDK<>RE_SDK mediated interstitial ad.

* Load RE_SDK<>RE_SDK mediated interstitial ad.

* Fix fullscreen ad implementation - PR #108

* Remove unnecessary ext in gradle file + remove SSV in banner_ad.xml.

* Take mediation type string as input instead of boolean + add class definition for ActivityHandler.

* Fix mediationType value.

* Remove mediationType input for show().
  • Loading branch information
anaghask-google authored Aug 12, 2024
1 parent 7da5dbf commit d574eaf
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,10 @@ class MainActivity : AppCompatActivity() {
}

private fun showFullscreenView() = lifecycleScope.launch {
if (mediationDropDownMenu.selectedItemId != MediationOption.NONE.ordinal.toLong()) {
makeToast("Mediated interstitial ad is not yet implemented!")
} else {
val fullscreenAd = FullscreenAd.create(this@MainActivity)
fullscreenAd.show(this@MainActivity, shouldStartActivityPredicate())
}
val mediationType =
MediationOption.entries[mediationDropDownMenu.selectedItemId.toInt()].toString()
val fullscreenAd = FullscreenAd.create(this@MainActivity, mediationType)
fullscreenAd.show(this@MainActivity, shouldStartActivityPredicate())
}

private fun shouldStartActivityPredicate() : () -> Boolean {
Expand Down
1 change: 1 addition & 0 deletions PrivacySandboxKotlin/example-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1"

implementation "androidx.privacysandbox.activity:activity-client:$privacy_sandbox_activity_version"
implementation "androidx.privacysandbox.activity:activity-core:$privacy_sandbox_activity_version"
implementation "androidx.privacysandbox.activity:activity-provider:$privacy_sandbox_activity_version"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ interface SdkService {

suspend fun getBanner(request: SdkBannerRequest, requestMediatedAd: Boolean): SdkSandboxedUiAdapter?

suspend fun getFullscreenAd(): FullscreenAd
suspend fun getFullscreenAd(mediationType: String): FullscreenAd
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.example.implementation

import android.content.Context
import android.os.Build
import android.os.RemoteException
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.webkit.WebView
Expand All @@ -14,10 +15,14 @@ import androidx.privacysandbox.activity.core.SdkActivityLauncher
import androidx.privacysandbox.sdkruntime.core.activity.ActivityHolder
import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
import com.example.R
import com.example.api.FullscreenAd


class FullscreenAdImpl(private val sdkContext: Context) : FullscreenAd {
class FullscreenAdImpl(private val sdkContext: Context,
private val mediateeSdk: com.mediatee.api.SdkService?,
private val mediationType: String
) : FullscreenAd {

private val webView = WebView(sdkContext)
private val controller = SdkSandboxControllerCompat.from(sdkContext)
Expand All @@ -35,23 +40,33 @@ class FullscreenAdImpl(private val sdkContext: Context) : FullscreenAd {
}

override suspend fun show(activityLauncher: SdkActivityLauncher) {
val handler = object : SdkSandboxActivityHandlerCompat {
@RequiresApi(Build.VERSION_CODES.R)
override fun onActivityCreated(activityHolder: ActivityHolder) {
val activityHandler = ActivityHandler(activityHolder, webView)
activityHandler.buildLayout()
if (mediationType == sdkContext.getString(R.string.mediation_option_re_re)) {
if (mediateeSdk == null) {
throw RemoteException("Mediatee SDK not loaded!")
}
// Activity Launcher to be used to load interstitial ad will be passed from
// mediator to mediatee SDK.
mediateeSdk.getFullscreenAd().show(activityLauncher)
}
else {
val handler = object : SdkSandboxActivityHandlerCompat {
@RequiresApi(Build.VERSION_CODES.R)
override fun onActivityCreated(activityHolder: ActivityHolder) {
val activityHandler = ActivityHandler(activityHolder, webView)
activityHandler.buildLayout()

ViewCompat.setOnApplyWindowInsetsListener(activityHolder.getActivity().window.decorView) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(top = insets.top)
WindowInsetsCompat.CONSUMED
ViewCompat.setOnApplyWindowInsetsListener(activityHolder.getActivity().window.decorView) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(top = insets.top)
WindowInsetsCompat.CONSUMED
}
}
}
}

val token = controller.registerSdkSandboxActivityHandler(handler)
val launched = activityLauncher.launchSdkActivity(token)
if (!launched) controller.unregisterSdkSandboxActivityHandler(handler)
val token = controller.registerSdkSandboxActivityHandler(handler)
val launched = activityLauncher.launchSdkActivity(token)
if (!launched) controller.unregisterSdkSandboxActivityHandler(handler)
}
}

private fun initializeSettings(settings: WebSettings) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
import com.example.R
import com.mediatee.api.SdkServiceFactory
import com.example.api.SdkSandboxedUiAdapter

Expand Down Expand Up @@ -85,7 +86,19 @@ class SdkServiceImpl(private val context: Context) : SdkService {
}
}

override suspend fun getFullscreenAd() : FullscreenAd {
return FullscreenAdImpl(context)
override suspend fun getFullscreenAd(mediationType: String): FullscreenAd {
if (mediationType == context.getString(R.string.mediation_option_re_re)) {
try {
if (remoteInstance == null) {
val controller = SdkSandboxControllerCompat.from(context)
val sandboxedSdk = controller.loadSdk(mediateeSdkName, Bundle.EMPTY)
remoteInstance =
SdkServiceFactory.wrapToSdkService(sandboxedSdk.getInterface()!!)
}
} catch (e: Exception) {
Log.e(tag, "Failed to load SDK, error code: $e", e)
}
}
return FullscreenAdImpl(context, remoteInstance, mediationType)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 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.
-->

<resources>
<string name="mediation_option_none">NONE</string>
<string name="mediation_option_re_re">RUNTIME_RUNTIME</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import androidx.privacysandbox.activity.core.SdkActivityLauncher
import com.example.api.FullscreenAd

class FullscreenAd(private val sdkFullscreenAd: FullscreenAd) {
suspend fun show(baseActivity: AppCompatActivity, allowSdkActivityLaunch: () -> Boolean) {
suspend fun show(
baseActivity: AppCompatActivity,
allowSdkActivityLaunch: () -> Boolean
) {
val activityLauncher = baseActivity.createSdkActivityLauncher(allowSdkActivityLaunch)
sdkFullscreenAd.show(activityLauncher)
}
Expand All @@ -17,9 +20,13 @@ class FullscreenAd(private val sdkFullscreenAd: FullscreenAd) {
// This method could divert a percentage of requests to a sandboxed SDK and fallback to
// existing ad logic. For this example, we send all requests to the sandboxed SDK as long as
// it exists.
suspend fun create(context: Context): com.existing.sdk.FullscreenAd {
suspend fun create(
context: Context,
mediationType: String
): com.existing.sdk.FullscreenAd {
if (ExistingSdk.isSdkLoaded()) {
val remoteFullscreenAd = ExistingSdk.loadSdkIfNeeded(context)?.getFullscreenAd()
val remoteFullscreenAd =
ExistingSdk.loadSdkIfNeeded(context)?.getFullscreenAd(mediationType)
if (remoteFullscreenAd != null)
return FullscreenAd(remoteFullscreenAd)
}
Expand Down
3 changes: 2 additions & 1 deletion PrivacySandboxKotlin/mediatee-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ android {
}

dependencies {
implementation 'androidx.activity:activity-ktx:1.9.0'
ksp 'androidx.annotation:annotation:1.6.0'
implementation "androidx.privacysandbox.ui:ui-client:$privacy_sandbox_ui_version"
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"
Expand All @@ -58,6 +58,7 @@ dependencies {
implementation "androidx.privacysandbox.activity:activity-provider:$privacy_sandbox_activity_version"

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

implementation "androidx.privacysandbox.sdkruntime:sdkruntime-core:$privacy_sandbox_sdk_runtime_version"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mediatee.api

import androidx.privacysandbox.activity.core.SdkActivityLauncher
import androidx.privacysandbox.tools.PrivacySandboxInterface

@PrivacySandboxInterface
interface FullscreenAd {
suspend fun show(activityLauncher: SdkActivityLauncher)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ import androidx.privacysandbox.tools.PrivacySandboxService
@PrivacySandboxService
interface SdkService {
suspend fun getBanner(request: SdkBannerRequest): SdkSandboxedUiAdapter

suspend fun getFullscreenAd(): FullscreenAd
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.mediatee.implementation

import android.content.Intent
import android.content.pm.ActivityInfo
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcher
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.privacysandbox.sdkruntime.core.activity.ActivityHolder

/* This class creates the layout of the activity that shows the ad. */
class ActivityHandler(
private val activityHolder: ActivityHolder,
private val adView: View,
) {
private var activity = activityHolder.getActivity()
private var backControlButton: Button? = null
private var destroyActivityButton: Button? = null
private var openLandingPage: Button? = null

fun buildLayout() {
val layout = buildLayoutProgrammatically()
registerBackControlButton()
registerDestroyActivityButton()
registerOpenLandingPageButton()
registerLifecycleListener()
}

/**
* Building the activity layout programmatically.
*/
private fun buildLayoutProgrammatically(): ViewGroup {
val mainLayout = LinearLayout(activity)
mainLayout.orientation = LinearLayout.VERTICAL
mainLayout.layoutParams =
ViewGroup.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)

backControlButton = Button(activity)
backControlButton!!.text = DISABLE_BACK_NAVIGATION
mainLayout.addView(backControlButton)

destroyActivityButton = Button(activity)
destroyActivityButton!!.text = DESTROY_ACTIVITY
mainLayout.addView(destroyActivityButton)

openLandingPage = Button(activity)
openLandingPage!!.text = OPEN_LANDING_PAGE
mainLayout.addView(openLandingPage)

if (adView.parent != null) {
(adView.parent as ViewGroup).removeView(adView)
}
adView.layoutParams = ViewGroup.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
mainLayout.addView(adView)

activity.setContentView(mainLayout)
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
return mainLayout
}

private fun registerBackControlButton() {
val disabler = BackNavigationDisabler(
activityHolder.getOnBackPressedDispatcher(), backControlButton!!
)
backControlButton!!.setOnClickListener { disabler.toggle() }
}

private fun registerDestroyActivityButton() {
destroyActivityButton!!.setOnClickListener { activity.finish() }
}

private fun registerOpenLandingPageButton() {
openLandingPage!!.setOnClickListener {
val visitUrl = Intent(Intent.ACTION_VIEW)
visitUrl.setData(Uri.parse(LANDING_PAGE_URL))
visitUrl.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
activity.startActivity(visitUrl)
}
}

private fun registerLifecycleListener() {
activityHolder.lifecycle.addObserver(LocalLifecycleObserver())
}

private fun makeToast(message: String) {
activity.runOnUiThread { Toast.makeText(activity, message, Toast.LENGTH_SHORT).show() }
}

inner class BackNavigationDisabler(
private val dispatcher: OnBackPressedDispatcher,
private val backButton: Button
) {
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
makeToast("Can not go back!")
}
}

private var backNavigationDisabled = false // default is back enabled.

@Synchronized
fun toggle() {
if (backNavigationDisabled) {
onBackPressedCallback.remove()
backButton.text = DISABLE_BACK_NAVIGATION
} else {
dispatcher.addCallback(onBackPressedCallback)
backButton.text = ENABLE_BACK_NAVIGATION
}
backNavigationDisabled = !backNavigationDisabled
}
}

inner class LocalLifecycleObserver : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
makeToast("Current activity state is: $event")
}
}

companion object {
private const val DISABLE_BACK_NAVIGATION = "Disable Back Navigation"
private const val ENABLE_BACK_NAVIGATION = "Enable Back Navigation"
private const val DESTROY_ACTIVITY = "Destroy Activity"
private const val OPEN_LANDING_PAGE = "Open Landing Page"
private const val LANDING_PAGE_URL = "https://www.google.com"
}
}
Loading

0 comments on commit d574eaf

Please sign in to comment.