Skip to content

Commit

Permalink
EUID Support
Browse files Browse the repository at this point in the history
  • Loading branch information
dcaunt committed Aug 14, 2024
1 parent a381e93 commit 48620ac
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 17 deletions.
4 changes: 4 additions & 0 deletions dev-app/src/main/java/com/uid2/dev/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.uid2.dev.network.AppUID2Client
import com.uid2.dev.ui.MainScreen
import com.uid2.dev.ui.MainScreenViewModel
import com.uid2.dev.ui.MainScreenViewModelFactory
import com.uid2.devapp.R

class MainActivity : ComponentActivity() {

Expand All @@ -22,6 +23,9 @@ class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

//TODO: Configure for EUID/UID2, set title, pass env to viewModel
// setTitle(R.string.app_name_euid)
setContent {
MaterialTheme {
MainScreen(viewModel)
Expand Down
1 change: 1 addition & 0 deletions dev-app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<resources>
<string name="app_name">UID2 SDK Dev App</string>
<string name="app_name_euid">EUID SDK Dev App</string>

<string name="email">Email</string>
<string name="phone">Phone Number</string>
Expand Down
107 changes: 107 additions & 0 deletions sdk/src/main/java/com/uid2/EUIDManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.uid2

import android.content.Context
import com.uid2.UID2Manager.Companion
import com.uid2.UID2Manager.Companion.APPLICATION_ID_DEFAULT
import com.uid2.UID2Manager.Companion.UID2_API_URL_DEFAULT
import com.uid2.network.DefaultNetworkSession
import com.uid2.network.NetworkSession
import com.uid2.storage.FileStorageManager
import com.uid2.storage.FileStorageManager.Store.EUID
import com.uid2.storage.StorageManager
import com.uid2.utils.InputUtils
import com.uid2.utils.Logger
import com.uid2.utils.TimeUtils
import kotlinx.coroutines.Dispatchers

public class EUIDManager {

public companion object {
public data class Environment private constructor(
val serverUrl: String
) {
public companion object {
// AWS EU West 2 (London)
public val london: Environment = Environment("https://prod.euid.eu/v2")

// Equivalent to `london`
public val production: Environment = london
public fun custom(serverUrl: String): Environment {
return Environment(serverUrl)
}
}
}

private var serverUrl: String = UID2_API_URL_DEFAULT
private var applicationId: String = APPLICATION_ID_DEFAULT
private var networkSession: NetworkSession = DefaultNetworkSession()
private var storageManager: StorageManager? = null
private var isLoggingEnabled: Boolean = false

private var instance: UID2Manager? = null

/**
* Initializes the class with the given [Context], along with a [NetworkSession] that will be responsible
* for making any required network calls.
*
* @param context The context to initialise from. This will be used to obtain the package's metadata to extract
* the API URL.
* @param networkSession A custom [NetworkSession] which can be used for making any required network calls.
* The default implementation supported by the SDK can be found as [DefaultNetworkSession].
*/
@JvmStatic
@JvmOverloads
@Throws(InitializationException::class)
public fun init(
context: Context,
environment: Environment = Environment.production,
networkSession: NetworkSession = DefaultNetworkSession(),
isLoggingEnabled: Boolean = false,
) {
if (instance != null) {
throw InitializationException()
}

this.serverUrl = environment.serverUrl
this.applicationId = context.packageName
this.networkSession = networkSession
this.storageManager = FileStorageManager(context.applicationContext, EUID)
this.isLoggingEnabled = isLoggingEnabled
}

/**
* Returns True if the manager is already initialised, otherwise False.
*/
@JvmStatic
public fun isInitialized(): Boolean = instance != null

/**
* Gets the current singleton instance of the manager.
*
* @throws InitializationException Thrown if the manager has not yet been initialised.
*/
@JvmStatic
public fun getInstance(): UID2Manager {
val storage = storageManager ?: throw InitializationException()
val logger = Logger(isLoggingEnabled)

return instance ?: UID2Manager(
UID2Client(
apiUrl = serverUrl,
session = networkSession,
applicationId = applicationId,
logger = logger,
),
storage,
TimeUtils,
InputUtils(),
Dispatchers.Default,
true,
logger,
).apply {
instance = this
}
}

}
}
61 changes: 57 additions & 4 deletions sdk/src/main/java/com/uid2/UID2Manager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import com.uid2.data.IdentityStatus.REFRESH_EXPIRED
import com.uid2.data.UID2Identity
import com.uid2.network.DefaultNetworkSession
import com.uid2.network.NetworkSession
import com.uid2.storage.FileStorageManager
import com.uid2.storage.FileStorageManager.Store.UID2
import com.uid2.storage.StorageManager
import com.uid2.utils.InputUtils
import com.uid2.utils.Logger
Expand Down Expand Up @@ -533,12 +535,36 @@ public class UID2Manager internal constructor(
}

public companion object {
public data class Environment private constructor(
val serverUrl: String
) {
public companion object {
// AWS US East (Ohio)
public val ohio: Environment = Environment("https://prod.uidapi.com")
// AWS US West (Oregon)
public val oregon: Environment = Environment("https://usw.prod.uidapi.com")
// AWS Asia Pacific (Singapore)
public val singapore: Environment = Environment("https://sg.prod.uidapi.com")
// AWS Asia Pacific (Sydney)
public val sydney: Environment = Environment("https://au.prod.uidapi.com")
// AWS Asia Pacific (Tokyo)
public val tokyo: Environment = Environment("https://jp.prod.uidapi.com")

// Equivalent to `ohio`
public val production: Environment = ohio

public fun custom(serverUrl: String): Environment {
return Environment(serverUrl)
}
}
}

private const val TAG = "UID2Manager"

// The default API server.
private const val UID2_API_URL_DEFAULT = "https://prod.uidapi.com"
internal const val UID2_API_URL_DEFAULT = "https://prod.uidapi.com"

private const val APPLICATION_ID_DEFAULT = "unknown"
internal const val APPLICATION_ID_DEFAULT = "unknown"

private const val PACKAGE_NOT_AVAILABLE = "Identity not available"
private const val PACKAGE_AD_TOKEN_NOT_AVAILABLE = "advertising_token is not available or is not valid"
Expand Down Expand Up @@ -577,20 +603,47 @@ public class UID2Manager internal constructor(
@JvmStatic
@JvmOverloads
@Throws(InitializationException::class)
@Deprecated(
message = "Initialize with a custom Environment rather than a serverUrl String",
replaceWith = ReplaceWith("init(context, environment, networkSession, isLoggingEnabled)"),
level = DeprecationLevel.WARNING
)
public fun init(
context: Context,
serverUrl: String = UID2_API_URL_DEFAULT,
networkSession: NetworkSession = DefaultNetworkSession(),
isLoggingEnabled: Boolean = false,
) {
init2(context, Environment.custom(serverUrl), networkSession, isLoggingEnabled)
}

/**
* Initializes the class with the given [Context], along with a [NetworkSession] that will be responsible
* for making any required network calls.
*
* @param context The context to initialise from. This will be used to obtain the package's metadata to extract
* the API URL.
* @param networkSession A custom [NetworkSession] which can be used for making any required network calls.
* The default implementation supported by the SDK can be found as [DefaultNetworkSession].
*/
@JvmStatic
@JvmOverloads
@JvmName("initWithEnvironment")
@Throws(InitializationException::class)
public fun init2(
context: Context,
environment: Environment = Environment.production,
networkSession: NetworkSession = DefaultNetworkSession(),
isLoggingEnabled: Boolean = false,
) {
if (instance != null) {
throw InitializationException()
}

this.serverUrl = serverUrl
this.serverUrl = environment.serverUrl
this.applicationId = context.packageName
this.networkSession = networkSession
this.storageManager = StorageManager.getInstance(context.applicationContext)
this.storageManager = FileStorageManager(context.applicationContext, UID2)
this.isLoggingEnabled = isLoggingEnabled
}

Expand Down
10 changes: 8 additions & 2 deletions sdk/src/main/java/com/uid2/storage/FileStorageManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@ internal class FileStorageManager(
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
) : StorageManager {

enum class Store(val filename: String) {
UID2(UID2_FILE_IDENTITY),
EUID(EUID_FILE_IDENTITY)
}

// For storage, we use the parent filesDir which is part of the Application's internal storage. This internal
// storage is sandboxed to prevent any other app, or even the user, from accessing it directly. We rely on Android
// keeping this file secure.
//
// On Android 10+, this location is also likely encrypted.
//
// https://developer.android.com/training/data-storage/app-specific#internal-access-files
constructor(context: Context) : this({ File(context.filesDir, FILE_IDENTITY) })
constructor(context: Context, store: Store) : this({ File(context.filesDir, store.filename) })

// This lazy value *should* only be requested on the ioDispatcher.
private val identityFile: File by lazy { identityFileFactory() }
Expand Down Expand Up @@ -59,7 +64,8 @@ internal class FileStorageManager(
}

private companion object {
const val FILE_IDENTITY = "uid2_identity.json"
const val UID2_FILE_IDENTITY = "uid2_identity.json"
const val EUID_FILE_IDENTITY = "euid_identity.json"
const val KEY_STATUS = "identity_status"

// The character set used for both reading and writing to the file.
Expand Down
11 changes: 0 additions & 11 deletions sdk/src/main/java/com/uid2/storage/StorageManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,4 @@ internal interface StorageManager {
* Clears any previously stored data.
*/
suspend fun clear(): Boolean

companion object {
private var instance: StorageManager? = null

/**
* Gets the single instance of the FileStorageManager.
*/
fun getInstance(context: Context) = instance ?: FileStorageManager(context).apply {
instance = this
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.uid2.securesignals.gma

import android.content.Context
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.mediation.InitializationCompleteCallback
import com.google.android.gms.ads.mediation.MediationConfiguration
import com.google.android.gms.ads.mediation.rtb.RtbAdapter
import com.google.android.gms.ads.mediation.rtb.RtbSignalData
import com.google.android.gms.ads.mediation.rtb.SignalCallbacks
import com.uid2.EUIDManager
import com.uid2.UID2
import com.google.android.gms.ads.mediation.VersionInfo as GmaVersionInfo

/**
* An implementation of Google's GMS RtbAdapter that integrates UID2 tokens, accessed via the UID2Manager.
*/
public class EUIDMediationAdapter : RtbAdapter() {

/**
* Gets the version of the UID2 SDK.
*/
@Suppress("DEPRECATION")
public override fun getSDKVersionInfo(): GmaVersionInfo = UID2.getVersionInfo().let {
GmaVersionInfo(it.major, it.minor, it.patch)
}

/**
* Gets the version of the UID2 Secure Signals plugin.
*/
@Suppress("DEPRECATION")
public override fun getVersionInfo(): GmaVersionInfo = PluginVersion.getVersionInfo().let {
GmaVersionInfo(it.major, it.minor, it.patch)
}

/**
* Initialises the UID2 SDK with the given Context.
*/
override fun initialize(
context: Context,
initializationCompleteCallback: InitializationCompleteCallback,
mediationConfigurations: MutableList<MediationConfiguration>,
) {
// It's possible that the UID2Manager is already initialised. If so, it's a no-op.
if (!EUIDManager.isInitialized()) {
EUIDManager.init(context)
}

// After we've asked to initialize the manager, we should wait until it's complete before reporting success.
// This will potentially allow any previously persisted identity to be fully restored before we allow any
// signals to be collected.
EUIDManager.getInstance().addOnInitializedListener(initializationCompleteCallback::onInitializationSucceeded)
}

/**
* Collects the UID2 advertising token, if available.
*/
override fun collectSignals(rtbSignalData: RtbSignalData, signalCallbacks: SignalCallbacks) {
EUIDManager.getInstance().let { manager ->
val token = manager.getAdvertisingToken()
if (token != null) {
signalCallbacks.onSuccess(token)
} else {
// We include the IdentityStatus in the "error" to have better visibility on why the Advertising Token
// was not present. There are a number of valid reasons why we don't have a token, but we are still
// required to report these as "failures".
signalCallbacks.onFailure(
AdError(
manager.currentIdentityStatus.value,
"No Advertising Token",
"UID2",
),
)
}
}
}
}

0 comments on commit 48620ac

Please sign in to comment.