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

[Prebid] Implement integration to keep external IDs updated #91

Merged
merged 1 commit into from
Jun 27, 2024
Merged
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
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,
IanDBird marked this conversation as resolved.
Show resolved Hide resolved
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() },
IanDBird marked this conversation as resolved.
Show resolved Hide resolved
) : 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> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incase a consumer wants to explicitly update the ExternalUserIds on Prebid, they can use this to access our current collection.

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
Loading