Skip to content

Commit

Permalink
Merge pull request #98 from IABTechLab/dave/euid
Browse files Browse the repository at this point in the history
EUID Support
  • Loading branch information
dcaunt authored Sep 10, 2024
2 parents 0a8762e + 429d744 commit c786b25
Show file tree
Hide file tree
Showing 22 changed files with 348 additions and 55 deletions.
4 changes: 4 additions & 0 deletions dev-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31">

<!-- Metadata Required for Server-Side Integration -->
<!-- This information is only consumed by the DevApp (not the SDK) to simulate a server side integration. -->
<meta-data android:name="uid2_api_key" android:value=""/>
<meta-data android:name="uid2_api_secret" android:value=""/>

<!-- Metadata for toggling UID2 and EUID environments. If true, EUID is used. -->
<meta-data android:name="uid2_environment_euid" android:value="false"/>

<activity android:name="com.uid2.dev.MainActivity"
android:exported="true">
<intent-filter>
Expand Down
26 changes: 23 additions & 3 deletions dev-app/src/main/java/com/uid2/dev/DevApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package com.uid2.dev
import android.app.Application
import android.os.StrictMode
import android.util.Log
import com.uid2.EUIDManager
import com.uid2.UID2Manager
import com.uid2.dev.utils.getMetadata
import com.uid2.dev.utils.isEnvironmentEUID
import com.uid2.prebid.UID2Prebid
import org.prebid.mobile.PrebidMobile

Expand All @@ -13,16 +16,32 @@ class DevApplication : Application() {
override fun onCreate() {
super.onCreate()

val isEnvironmentEUID = getMetadata().isEnvironmentEUID()

// Initialise the UID2Manager class. We will use it's DefaultNetworkSession rather than providing our own
// custom implementation. This can be done to allow wrapping something like OkHttp.
UID2Manager.init(context = this, serverUrl = INTEG_SERVER_URL, isLoggingEnabled = true)
if (isEnvironmentEUID) {
EUIDManager.init(
context = this,
EUIDManager.Environment.Custom(EUID_INTEG_SERVER_URL),
isLoggingEnabled = true,
)
} else {
UID2Manager.init(
context = this,
UID2Manager.Environment.Custom(UID2_INTEG_SERVER_URL),
isLoggingEnabled = true,
)
}

// 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 {
prebid = UID2Prebid(
if (isEnvironmentEUID) EUIDManager.getInstance() else UID2Manager.getInstance(),
).apply {
initialize()
}

Expand All @@ -45,6 +64,7 @@ class DevApplication : Application() {
private companion object {
const val TAG = "DevApplication"

const val INTEG_SERVER_URL = "https://operator-integ.uidapi.com"
const val UID2_INTEG_SERVER_URL = "https://operator-integ.uidapi.com"
const val EUID_INTEG_SERVER_URL = "https://integ.euid.eu/v2"
}
}
13 changes: 12 additions & 1 deletion dev-app/src/main/java/com/uid2/dev/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,34 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.material.MaterialTheme
import com.uid2.EUIDManager
import com.uid2.UID2Manager
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.dev.utils.getMetadata
import com.uid2.dev.utils.isEnvironmentEUID
import com.uid2.devapp.R

class MainActivity : ComponentActivity() {

private val viewModel: MainScreenViewModel by viewModels {
val isEUID = getMetadata().isEnvironmentEUID()
MainScreenViewModelFactory(
AppUID2Client.fromContext(baseContext),
UID2Manager.getInstance(),
if (isEUID) EUIDManager.getInstance() else UID2Manager.getInstance(),
isEUID,
)
}

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

if (getMetadata().isEnvironmentEUID()) {
setTitle(R.string.app_name_euid)
}

setContent {
MaterialTheme {
MainScreen(viewModel)
Expand Down
18 changes: 1 addition & 17 deletions dev-app/src/main/java/com/uid2/dev/network/AppUID2Client.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package com.uid2.dev.network

import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Base64
import com.uid2.data.UID2Identity
import com.uid2.dev.utils.getMetadata
import com.uid2.network.DataEnvelope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -191,18 +188,5 @@ class AppUID2Client(
it.getString(UID2_API_SECRET_KEY, ""),
)
}

private fun Context.getMetadata(): Bundle = packageManager.getApplicationInfoCompat(
packageName,
PackageManager.GET_META_DATA,
).metaData

private fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int = 0): ApplicationInfo =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong()))
} else {
@Suppress("DEPRECATION")
getApplicationInfo(packageName, flags)
}
}
}
23 changes: 16 additions & 7 deletions dev-app/src/main/java/com/uid2/dev/ui/MainScreenViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ sealed interface MainScreenState : ViewState {
class MainScreenViewModel(
private val api: AppUID2Client,
private val manager: UID2Manager,
isEUID: Boolean,
) : BasicViewModel<MainScreenAction, MainScreenState>() {

private val _viewState = MutableStateFlow<MainScreenState>(UserUpdatedState(null, NO_IDENTITY))
override val viewState: StateFlow<MainScreenState> = _viewState.asStateFlow()

private val subscriptionId: String = if (isEUID) SUBSCRIPTION_ID_EUID else SUBSCRIPTION_ID_UID2
private val publicKey: String = if (isEUID) PUBLIC_KEY_EUID else PUBLIC_KEY_UID2

init {
// Observe the state of the UID2Manager and translate those into our own ViewState. This will happen when the
// Identity is initial set, or refreshed, or reset.
Expand Down Expand Up @@ -98,8 +102,8 @@ class MainScreenViewModel(
// Generate the identity via Client Side Integration (client side token generation).
manager.generateIdentity(
IdentityRequest.Email(action.address),
SUBSCRIPTION_ID,
PUBLIC_KEY,
subscriptionId,
publicKey,
onGenerateResult,
)
} else {
Expand All @@ -120,8 +124,8 @@ class MainScreenViewModel(
// Generate the identity via Client Side Integration (client side token generation).
manager.generateIdentity(
IdentityRequest.Phone(action.number),
SUBSCRIPTION_ID,
PUBLIC_KEY,
subscriptionId,
publicKey,
onGenerateResult,
)
} else {
Expand Down Expand Up @@ -149,19 +153,24 @@ class MainScreenViewModel(
private companion object {
const val TAG = "MainScreenViewModel"

const val SUBSCRIPTION_ID = "toPh8vgJgt"
const val SUBSCRIPTION_ID_UID2 = "toPh8vgJgt"
const val SUBSCRIPTION_ID_EUID = "w6yPQzN4dA"

@Suppress("ktlint:standard:max-line-length")
const val PUBLIC_KEY_UID2 = "UID2-X-I-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKAbPfOz7u25g1fL6riU7p2eeqhjmpALPeYoyjvZmZ1xM2NM8UeOmDZmCIBnKyRZ97pz5bMCjrs38WM22O7LJuw=="

@Suppress("ktlint:standard:max-line-length")
const val PUBLIC_KEY = "UID2-X-I-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKAbPfOz7u25g1fL6riU7p2eeqhjmpALPeYoyjvZmZ1xM2NM8UeOmDZmCIBnKyRZ97pz5bMCjrs38WM22O7LJuw=="
const val PUBLIC_KEY_EUID = "EUID-X-I-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEH/k7HYGuWhjhCo8nXgj/ypClo5kek7uRKvzCGwj04Y1eXOWmHDOLAQVCPquZdfVVezIpABNAl9zvsSEC7g+ZGg=="
}
}

class MainScreenViewModelFactory(
private val api: AppUID2Client,
private val manager: UID2Manager,
private val isEUID: Boolean,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainScreenViewModel(api, manager) as T
return MainScreenViewModel(api, manager, isEUID) as T
}
}
7 changes: 7 additions & 0 deletions dev-app/src/main/java/com/uid2/dev/utils/BundleEx.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.uid2.dev.utils

import android.os.Bundle

private const val UID2_ENVIRONMENT_EUID = "uid2_environment_euid"

fun Bundle.isEnvironmentEUID(): Boolean = getBoolean(UID2_ENVIRONMENT_EUID, false)
10 changes: 10 additions & 0 deletions dev-app/src/main/java/com/uid2/dev/utils/ContextEx.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.uid2.dev.utils

import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle

fun Context.getMetadata(): Bundle = packageManager.getApplicationInfoCompat(
packageName,
PackageManager.GET_META_DATA,
).metaData
14 changes: 14 additions & 0 deletions dev-app/src/main/java/com/uid2/dev/utils/PackageManagerEx.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.uid2.dev.utils

import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.Build

fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int = 0): ApplicationInfo =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@Suppress("WrongConstant")
getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong()))
} else {
@Suppress("DEPRECATION")
getApplicationInfo(packageName, flags)
}
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
8 changes: 8 additions & 0 deletions dev-app/src/main/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<network-security-config>
<debug-overrides>
<trust-anchors>
<!-- Trust user added CAs while debuggable only -->
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
119 changes: 119 additions & 0 deletions sdk/src/main/java/com/uid2/EUIDManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.uid2

import android.content.Context
import com.uid2.UID2Manager.Companion.APPLICATION_ID_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 object EUIDManager {

public sealed interface Environment {
public val serverUrl: String

/**
* AWS EU West 2 (London).
*/
public data object London : Environment {
override val serverUrl: String = EUID_API_URL_PRODUCTION
}

/**
* The default Environment, equivalent to [London].
*/
public data object Production : Environment {
override val serverUrl: String = EUID_API_URL_PRODUCTION
}

/**
* An Environment with its own API endpoint, such as for integration testing.
*/
public data class Custom(
override val serverUrl: String,
) : Environment
}

// The default API server.
internal const val EUID_API_URL_PRODUCTION = "https://prod.euid.eu/v2"

private var serverUrl: String = EUID_API_URL_PRODUCTION
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 environment The API Environment to use.
* @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 {
if (storageManager == null) {
throw InitializationException()
}
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
}
}
}
Loading

0 comments on commit c786b25

Please sign in to comment.