Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class TheApplication extends Application {
public void onCreate() {
super.onCreate();

OptableConfig config = new OptableConfig(this, "prebidtest", "js-sdk");
OptableConfig config = new OptableConfig(this, "prebidtest", "android-sdk");
optable = new OptableSDK(config);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class TheApplication : Application() {
override fun onCreate() {
super.onCreate()

val config = OptableConfig(this, "prebidtest", "js-sdk")
val config = OptableConfig(this, "prebidtest", "android-sdk", "ca.edge.optable.co")
optable = OptableSDK(config)
}

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,13 @@ To build the Kotlin demo app, from [Android Studio](https://developer.android.co
open the `DemoApp/DemoAppKotlin` directory. To build the Java demo app, open the `DemoApp/DemoAppJava` directory. In
both cases, you should be able to build and run the resulting project directly, since it will automatically download the
`co.optable.android_sdk` library from the [JitPack](https://jitpack.io/) Maven repository.

### Development

Test coverage is measured using the Kotlinx Kover plugin. To generate a report, run the following command:

```bash
./gradlew android_sdk:koverHtmlReportDebug
```

The HTML report will be available at `./android_sdk/build/reports/kover/htmlDebug/index.html`.
2 changes: 1 addition & 1 deletion android_sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.9.4"

// Advertising ID
implementation 'com.google.android.gms:play-services-ads-identifier:18.2.0'
implementation 'com.google.android.gms:play-services-ads-identifier:18.3.0'

// Unit tests
testImplementation("junit:junit:4.13.2")
Expand Down
6 changes: 4 additions & 2 deletions android_sdk/src/main/java/co/optable/sdk/OptableSDK.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class OptableSDK(
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO + ceh)

init {
if (!config.skipAdvertisingIdDetection) {
googleAdIdManager.updateAdvertisingId()
scope.launch {
googleAdIdManager.fetchAdvertisingId()
}
}

Expand All @@ -49,6 +49,7 @@ class OptableSDK(
*/
fun identify(ids: List<OptableIdentifier>, listener: OptableResultListener<Unit>) {
scope.launch {
googleAdIdManager.deferredTask.await()
val encodedIds = identifiersEncoder.encode(ids)
val response = networkClient.identify(encodedIds)

Expand Down Expand Up @@ -135,6 +136,7 @@ class OptableSDK(
*/
fun targeting(ids: List<OptableIdentifier>, listener: OptableResultListener<OptableTargeting>) {
scope.launch {
googleAdIdManager.deferredTask.await()
val ids = identifiersEncoder.encode(ids)
val response = networkClient.targeting(ids)

Expand Down
49 changes: 32 additions & 17 deletions android_sdk/src/main/java/co/optable/sdk/core/GoogleAdIdManager.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package co.optable.sdk.core

import android.util.Log
import co.optable.sdk.OptableConfig
import com.google.android.gms.ads.identifier.AdvertisingIdClient
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull

internal class GoogleAdIdManager(
val config: OptableConfig,
Expand All @@ -15,20 +17,7 @@ internal class GoogleAdIdManager(
private var limitAdTracking: Boolean? = null
}

fun updateAdvertisingId() {
GlobalScope.launch {
var adInfo: AdvertisingIdClient.Info? = null
try {
adInfo = AdvertisingIdClient.getAdvertisingIdInfo(config.context)
} catch (_: Exception) {
}

MainScope().launch {
adId = adInfo?.id
limitAdTracking = adInfo?.isLimitAdTrackingEnabled
}
}
}
val deferredTask = CompletableDeferred<String?>()

fun getId(): String? {
if (limitAdTracking == true) {
Expand All @@ -37,4 +26,30 @@ internal class GoogleAdIdManager(
return adId
}

suspend fun fetchAdvertisingId() {
if (config.skipAdvertisingIdDetection) {
deferredTask.complete(null)
return
}

val id = withContext(Dispatchers.IO) {
withTimeoutOrNull(3_000) {
fetch()
}
}
deferredTask.complete(id)
}

private fun fetch(): String? {
try {
val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(config.context)
adId = adInfo.id
limitAdTracking = adInfo.isLimitAdTrackingEnabled
return adInfo.id
} catch (exception: Exception) {
Log.w("OptableGaidManager", "Failed to fetch advertising ID: " + exception.message)
}
return null
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ internal class IdentifiersEncoder(
fun encode(identifiers: List<OptableIdentifier>): List<String> {
val result = mutableListOf<String>()

var containsCustomGaid = false
val googleAdId = googleAdIdManager.getId()
if (googleAdId != null) {
result.addIfNotNull(GAID, googleAdId, ::normalize)
}

for (identifier in identifiers) {
when (identifier) {
is OptableIdentifier.Email -> result.addIfNotNull(EMAIL, identifier.value, ::encrypt)
Expand All @@ -51,8 +55,10 @@ internal class IdentifiersEncoder(
is OptableIdentifier.Utiq -> result.addIfNotNull(UTIQ, identifier.value, ::normalize)

is OptableIdentifier.GoogleGaid -> {
if (normalize(googleAdId ?: "") == normalize(identifier.value)) {
continue
}
result.addIfNotNull(GAID, identifier.value, ::normalize)
containsCustomGaid = true
}

is OptableIdentifier.Custom -> {
Expand All @@ -67,10 +73,6 @@ internal class IdentifiersEncoder(
}
}

val googleAdId = googleAdIdManager.getId()
if (!containsCustomGaid && googleAdId != null) {
result.addIfNotNull(GAID, googleAdId, ::trim)
}

return result
}
Expand Down
22 changes: 20 additions & 2 deletions android_sdk/src/test/java/co/optable/sdk/OptableIdentifiersTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -178,19 +178,37 @@ class OptableIdentifiersTest {

@Test
fun `custom gaid`() {
every { mockGoogleAdIdManager.getId() } returns "managerId"
every { mockGoogleAdIdManager.getId() } returns null

val actual = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid("customId")))
val expected = listOf("g:customid")
assertEquals(expected, actual)
}

@Test
fun `both gaids, send two`() {
every { mockGoogleAdIdManager.getId() } returns "managerId"

val actual = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid("customId")))
val expected = listOf("g:managerid", "g:customid")
assertEquals(expected, actual)
}

@Test
fun `both gaids equals, send only one`() {
every { mockGoogleAdIdManager.getId() } returns "sameId"

val actual = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid("sameid")))
val expected = listOf("g:sameid")
assertEquals(expected, actual)
}

@Test
fun `gaid from manager`() {
every { mockGoogleAdIdManager.getId() } returns "managerId"

val actual = identifiersEncoder.encode(emptyList())
val expected = listOf("g:managerId")
val expected = listOf("g:managerid")
assertEquals(expected, actual)
}

Expand Down
2 changes: 2 additions & 0 deletions android_sdk/src/test/java/co/optable/sdk/OptableSDKTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ class OptableSDKTest {
mockkStatic(Log::class)
every { Log.e(any(), any()) } returns 0
every { Log.e(any(), any(), any()) } returns 0
every { Log.w(any(), any<String>()) } returns 0

mockkStatic(Base64::class)

every { Base64.encodeToString(any(), any()) } returns "mockedBase64String"

sdk = OptableSDK(mockConfig)
Expand Down
Loading
Loading