Skip to content

Commit

Permalink
[Prebid] Implement Prebid integration to keep external IDs configured
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian Bird committed Jun 13, 2024
1 parent 87747af commit 0b57225
Show file tree
Hide file tree
Showing 13 changed files with 402 additions and 7 deletions.
5 changes: 4 additions & 1 deletion dev-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ composeCompiler {
}

dependencies {
implementation project(path: ':sdk')
implementation project(":sdk")
implementation project(":prebid")

implementation(libs.androidx.appcompat)
implementation(libs.androidx.lifecycle)
Expand All @@ -55,6 +56,8 @@ dependencies {

implementation(libs.okhttp.core)

implementation(libs.prebid)

debugImplementation(libs.compose.tooling)
debugImplementation(libs.compose.test.manifest)
}
13 changes: 13 additions & 0 deletions dev-app/src/main/java/com/uid2/dev/DevApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package com.uid2.dev

import android.app.Application
import android.os.StrictMode
import android.util.Log
import com.uid2.UID2Manager
import com.uid2.prebid.UID2Prebid
import org.prebid.mobile.PrebidMobile

class DevApplication : Application() {
private lateinit var prebid: UID2Prebid

override fun onCreate() {
super.onCreate()

Expand All @@ -15,6 +20,12 @@ class DevApplication : Application() {
// Alternatively, we could initialise the UID2Manager with our own custom NetworkSession...
// UID2Manager.init(this, INTEG_SERVER_URL, OkNetworkSession(), true)

// Create the Prebid integration and allow it to start observing the UID2Manager instance.
PrebidMobile.initializeSdk(this) { Log.i(TAG, "Prebid: $it") }
prebid = UID2Prebid().apply {
initialize()
}

// For the development app, we will enable a strict thread policy to ensure we have suitable visibility of any
// issues within the SDK.
enableStrictMode()
Expand All @@ -32,6 +43,8 @@ class DevApplication : Application() {
}

private companion object {
const val TAG = "DevApplication"

const val INTEG_SERVER_URL = "https://operator-integ.uidapi.com"
}
}
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ compose-tooling = "1.6.7"
gma = "23.1.0"
ima = "3.33.0"
mockkVersion = "1.13.11"
prebid = "2.2.1"

[libraries]
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
Expand Down Expand Up @@ -38,6 +39,9 @@ androidx-media = { group = "androidx.media", name = "media", version = "1.7.0" }
androidx-browser = { group = "androidx.browser", name = "browser", version = "1.8.0" }
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version = "1.9.0" }

# Prebid
prebid = { group = "org.prebid", name = "prebid-mobile-sdk", version.ref = "prebid" }

# Testing
junit = { group = "junit", name = "junit", version.ref = "junit" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines-version" }
Expand Down
35 changes: 35 additions & 0 deletions prebid/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
plugins {
alias libs.plugins.androidLibrary
alias libs.plugins.kotlinAndroid
alias libs.plugins.dokka
alias libs.plugins.mavenPublish
}

apply from: rootProject.file("$rootDir/common.gradle")

android {
namespace 'com.uid2.prebid'
defaultConfig {
minSdk 19
}

kotlin {
explicitApi()
}

kotlinOptions {
freeCompilerArgs += [ "-opt-in=com.uid2.InternalUID2Api" ]
}
}

dependencies {
implementation project(":sdk")
implementation(libs.prebid)

testImplementation(libs.junit)

testImplementation(libs.mockk.android)
testImplementation(libs.mockk.agent)

testImplementation(libs.json)
}
3 changes: 3 additions & 0 deletions prebid/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_NAME=UID2 Android SDK (Prebid)
POM_ARTIFACT_ID=uid2-android-sdk-prebid
POM_DESCRIPTION=An SDK for integrating UID2 and Prebid into Android applications.
2 changes: 2 additions & 0 deletions prebid/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
132 changes: 132 additions & 0 deletions prebid/src/main/java/com/uid2/prebid/UID2Prebid.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.uid2.prebid

import com.uid2.UID2Manager
import com.uid2.UID2ManagerState
import com.uid2.UID2ManagerState.Established
import com.uid2.UID2ManagerState.Refreshed
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.prebid.mobile.ExternalUserId
import org.prebid.mobile.PrebidMobile

/**
* Interface to wrap access to [PrebidMobile]. This is used to improve testability, rather than having the [UID2Prebid]
* access it via static methods.
*/
internal fun interface PrebidExternalUserIdInteractor {
operator fun invoke(ids: List<ExternalUserId>)
}

/**
* Prebid integration that will observe a given [UID2Manager] instance and update Prebid when a new [ExternalUserId] is
* available. After creating the instance, a consumer must explicitly call [initialize] for this instance to start
* observing changes.
*/
public class UID2Prebid internal constructor(
private val manager: UID2Manager,
private val externalUserIdFactory: () -> List<ExternalUserId>,
private val prebidInteractor: PrebidExternalUserIdInteractor,
dispatcher: CoroutineDispatcher,
) {

// We redirect to the logger owned by the UID2Manager, as it's been configured correctly.
private val logger = manager.logger

/**
* Constructor.
*
* @param manager The [UID2Manager] instance to be observed.
* @param externalUserIdFactory A factory that will allow the consumer to add any other [ExternalUserId]s that should
* also be included, rather than just a single list containing only UID2's instance.
*/
@JvmOverloads
public constructor(
manager: UID2Manager = UID2Manager.getInstance(),
externalUserIdFactory: () -> List<ExternalUserId> = { emptyList() },
) : this(
manager,
externalUserIdFactory,
PrebidExternalUserIdInteractor { ids -> PrebidMobile.setExternalUserIds(ids) },
Dispatchers.Default,
)

private val scope = CoroutineScope(dispatcher + SupervisorJob())

/**
* Initializes the integration which will start observing the associated [UID2Manager] instance for changes in the
* availability of the advertising token. As the token is refreshed, this will automatically update Prebid's list
* of ExternalUserIds.
*/
public fun initialize() {
// Once the UID2Manager instance has been initialized, we will start observing it for changes.
manager.addOnInitializedListener {
updateExternalUserId(manager.getAdvertisingToken(), "Initialized")
observeIdentityChanges()
}
}

/**
* Releases this instance, to not be used again.
*/
public fun release() {
scope.cancel()
}

/**
* Returns the list of UID2 scoped [ExternalUserId]s.
*/
public fun getExternalUserIdList(): List<ExternalUserId> {
return getExternalUserIdList(manager.getAdvertisingToken())
}

/**
* Observes changes in the [UID2ManagerState] of the [UID2Manager] to update Prebid's [ExternalUserId]s.
*/
private fun observeIdentityChanges() {
scope.launch {
manager.state.collect { state ->
when (state) {
is Established -> updateExternalUserId(state.identity.advertisingToken, "Identity Established")
is Refreshed -> updateExternalUserId(state.identity.advertisingToken, "Identity Refreshed")
else -> updateExternalUserId(null, "Identity Changed: $state")
}
}
}
}

/**
* Updates Prebid's [ExternalUserId]s.
*/
private fun updateExternalUserId(advertisingToken: String?, reason: String) {
// We should set the external user ids to contain both our own UID2 specific one, along with any provided
// externally.
logger.i(TAG) { "Updating Prebid: $reason" }
val userIds = getExternalUserIdList(advertisingToken)
prebidInteractor(externalUserIdFactory() + userIds)
}

/**
* Converts the given token to the associated list of [ExternalUserId]s.
*/
private fun getExternalUserIdList(advertisingToken: String?): List<ExternalUserId> {
return advertisingToken?.toExternalUserIdList() ?: emptyList()
}

/**
* Extension function to build a list containing the single [ExternalUserId] that is associated with UID2.
*/
private fun String.toExternalUserIdList(): List<ExternalUserId> {
return listOf(
ExternalUserId(USER_ID_SOURCE, this, null, null),
)
}

private companion object {
const val TAG = "UID2Prebid"
const val USER_ID_SOURCE = "uidapi.com"
}
}
Loading

0 comments on commit 0b57225

Please sign in to comment.