Skip to content

Commit

Permalink
feat: add ConfidenceDeviceInfoContextProducer
Browse files Browse the repository at this point in the history
  • Loading branch information
nicklasl committed Jan 27, 2025
1 parent a3bfec6 commit cc65607
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 15 deletions.
20 changes: 20 additions & 0 deletions Confidence/api/Confidence.api
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ public abstract interface class com/spotify/confidence/ConfidenceContextProvider
public abstract fun getContext ()Ljava/util/Map;
}

public final class com/spotify/confidence/ConfidenceDeviceInfoContextProducer : com/spotify/confidence/ContextProducer {
public static final field APP_BUILD_CONTEXT_KEY Ljava/lang/String;
public static final field APP_VERSION_CONTEXT_KEY Ljava/lang/String;
public static final field BUNDLE_ID_CONTEXT_KEY Ljava/lang/String;
public static final field Companion Lcom/spotify/confidence/ConfidenceDeviceInfoContextProducer$Companion;
public static final field DEVICE_BRAND_CONTEXT_KEY Ljava/lang/String;
public static final field DEVICE_MODEL_CONTEXT_KEY Ljava/lang/String;
public static final field LOCAL_IDENTIFIER_CONTEXT_KEY Ljava/lang/String;
public static final field PREFERRED_LANGUAGES_CONTEXT_KEY Ljava/lang/String;
public static final field SYSTEM_NAME_CONTEXT_KEY Ljava/lang/String;
public static final field SYSTEM_VERSION_CONTEXT_KEY Ljava/lang/String;
public fun <init> (Landroid/content/Context;ZZZZ)V
public synthetic fun <init> (Landroid/content/Context;ZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun contextChanges ()Lkotlinx/coroutines/flow/Flow;
public fun stop ()V
}

public final class com/spotify/confidence/ConfidenceDeviceInfoContextProducer$Companion {
}

public final class com/spotify/confidence/ConfidenceError {
public fun <init> ()V
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.spotify.confidence

import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import java.util.Locale

class ConfidenceDeviceInfoContextProducer(
applicationContext: Context,
withVersionInfo: Boolean = false,
withBundleId: Boolean = false,
withDeviceInfo: Boolean = false,
withLocale: Boolean = false,
) : ContextProducer {
private val contextFlow = MutableStateFlow<Map<String, ConfidenceValue>>(mapOf())
private val packageInfo: PackageInfo? = try {
@Suppress("DEPRECATION")
applicationContext.packageManager.getPackageInfo(applicationContext.packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
Log.w(DebugLogger.TAG, "Failed to get package info", e)
null
}

init {
val context = mutableMapOf<String, ConfidenceValue>()
if (withVersionInfo) {
val currentVersion = ConfidenceValue.String(packageInfo?.versionName ?: "")
val currentBuild = ConfidenceValue.String(packageInfo?.getVersionCodeAsString() ?: "")
val addedContext = mapOf(
APP_VERSION_CONTEXT_KEY to currentVersion,
APP_BUILD_CONTEXT_KEY to currentBuild
)
context += addedContext
}
if (withBundleId) {
val bundleId = ConfidenceValue.String(applicationContext.packageName)
context += mapOf(BUNDLE_ID_CONTEXT_KEY to bundleId)
}
if (withDeviceInfo) {
val deviceInfo = mapOf(
SYSTEM_NAME_CONTEXT_KEY to ConfidenceValue.String("Android"),
DEVICE_BRAND_CONTEXT_KEY to ConfidenceValue.String(Build.BRAND),
DEVICE_MODEL_CONTEXT_KEY to ConfidenceValue.String(Build.MODEL),
SYSTEM_VERSION_CONTEXT_KEY to ConfidenceValue.Double(Build.VERSION.SDK_INT.toDouble())
)
context += deviceInfo
}
if (withLocale) {
val preferredLang = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val locales = applicationContext.resources.configuration.locales
(0 until locales.size()).map { locales.get(it).toString() }
} else {
listOf(Locale.getDefault().toString())
}
val localeIdentifier = Locale.getDefault().toString()
val localeInfo = mapOf(
LOCAL_IDENTIFIER_CONTEXT_KEY to ConfidenceValue.String(localeIdentifier),
PREFERRED_LANGUAGES_CONTEXT_KEY to ConfidenceValue.List(preferredLang.map(ConfidenceValue::String))
)
context += localeInfo
}
contextFlow.value = context
}

override fun contextChanges(): Flow<Map<String, ConfidenceValue>> = contextFlow
override fun stop() {}

companion object {
const val APP_VERSION_CONTEXT_KEY = "app_version"
const val APP_BUILD_CONTEXT_KEY = "app_build"
const val BUNDLE_ID_CONTEXT_KEY = "bundle_id"
const val SYSTEM_NAME_CONTEXT_KEY = "system_name"
const val DEVICE_BRAND_CONTEXT_KEY = "device_brand"
const val DEVICE_MODEL_CONTEXT_KEY = "device_model"
const val SYSTEM_VERSION_CONTEXT_KEY = "system_version"
const val LOCAL_IDENTIFIER_CONTEXT_KEY = "locale_identifier"
const val PREFERRED_LANGUAGES_CONTEXT_KEY = "preferred_languages"
}
}

private fun PackageInfo.getVersionCodeAsString(): String =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
this.longVersionCode.toString()
} else {
@Suppress("DEPRECATION")
this.versionCode.toString()
}
13 changes: 7 additions & 6 deletions Confidence/src/main/java/com/spotify/confidence/DebugLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import android.util.Log
import kotlinx.serialization.json.JsonElement
import java.net.URLEncoder

private const val TAG = "Confidence"

internal interface DebugLogger {
fun logEvent(action: String, event: EngineEvent)
fun logMessage(message: String, isWarning: Boolean = false, throwable: Throwable? = null)
fun logFlag(action: String, details: String? = null)
fun logContext(action: String, context: Map<String, ConfidenceValue>)
fun logResolve(flag: String, context: JsonElement)
companion object {
const val TAG = "Confidence"
}
}

internal class DebugLoggerImpl(private val filterLevel: LoggingLevel, private val clientKey: String) : DebugLogger {
Expand Down Expand Up @@ -60,10 +61,10 @@ internal class DebugLoggerImpl(private val filterLevel: LoggingLevel, private va
private fun log(messageLevel: LoggingLevel, message: String) {
if (messageLevel >= filterLevel) {
when (messageLevel) {
LoggingLevel.VERBOSE -> Log.v(TAG, message)
LoggingLevel.DEBUG -> Log.d(TAG, message)
LoggingLevel.WARN -> Log.w(TAG, message)
LoggingLevel.ERROR -> Log.e(TAG, message)
LoggingLevel.VERBOSE -> Log.v(DebugLogger.TAG, message)
LoggingLevel.DEBUG -> Log.d(DebugLogger.TAG, message)
LoggingLevel.WARN -> Log.w(DebugLogger.TAG, message)
LoggingLevel.ERROR -> Log.e(DebugLogger.TAG, message)
LoggingLevel.NONE -> {
// do nothing
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ interface EventSender : Contextual {
)

/**
* Track Android-specific events like activities or context updates.
* Track Android-specific events like activities or Track Context updates.
* Please note that this method is collecting data in a coroutine scope and will be
* executed on the dispatcher that was defined with the creation of the Confidence instance.
*
* @param producer a producer that produces the events or context updates.
*/
fun track(producer: Producer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ data class Event(
val shouldFlush: Boolean = false
)

interface Producer {
sealed interface Producer {
fun stop()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ internal class ConfidenceEvaluationTest {
cache: ProviderCache = InMemoryCache(),
initialContext: Map<String, ConfidenceValue> = mapOf(),
flagResolver: FlagResolver? = null,
debugLogger: DebugLoggerFake? = null
debugLogger: DebugLoggerFake? = null,
): Confidence =
Confidence(
clientSecret = "",
Expand Down Expand Up @@ -470,7 +470,7 @@ internal class ConfidenceEvaluationTest {
var latestCalledContext = mapOf<String, ConfidenceValue>()
override suspend fun resolve(
flags: List<String>,
context: Map<String, ConfidenceValue>
context: Map<String, ConfidenceValue>,
): Result<FlagResolution> {
latestCalledContext = context
callCount++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class StorageFileCacheTests {
dispatcher: CoroutineDispatcher,
cache: ProviderCache = mock(),
initialContext: Map<String, ConfidenceValue> = mapOf(),
debugLogger: DebugLoggerFake = DebugLoggerFake()
debugLogger: DebugLoggerFake = DebugLoggerFake(),
) = Confidence(
clientSecret = "",
dispatcher = dispatcher,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class AndroidLifecycleEventProducer(
val packageInfo = packageInfo
val currentVersion = ConfidenceValue.String(packageInfo?.versionName ?: "")
val currentBuild = ConfidenceValue.String(packageInfo?.getVersionCode().toString() ?: "")

val previousBuild: ConfidenceValue.String? = sharedPreferences
.getString(APP_BUILD, null)
?.let(ConfidenceValue::String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.spotify.confidence.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Date
Expand Down Expand Up @@ -48,10 +49,20 @@ class MainVm(app: Application) : AndroidViewModel(app) {
loggingLevel = LoggingLevel.VERBOSE
)
confidence.track(AndroidLifecycleEventProducer(getApplication(), false))
confidence.track(
ConfidenceDeviceInfoContextProducer(
applicationContext = getApplication(),
withVersionInfo = true,
withBundleId = true,
withDeviceInfo = true,
withLocale = true
)
)

eventSender = confidence.withContext(mutableMap)

viewModelScope.launch {
if(confidence.isStorageEmpty()) {
if (confidence.isStorageEmpty()) {
confidence.fetchAndActivate()
} else {
confidence.activate()
Expand All @@ -77,8 +88,11 @@ class MainVm(app: Application) : AndroidViewModel(app) {
}.toComposeColor()
_message.postValue(messageValue)
_color.postValue(colorFlag)
_surfaceText.postValue(confidence.getContext().entries.map { "${it.key}=${it.value}"}.joinToString { it })
eventSender.track("navigate", mapOf("my_date" to ConfidenceValue.Date(Date()), "my_time" to ConfidenceValue.Timestamp(Date())))
_surfaceText.postValue(confidence.getContext().entries.map { "${it.key}=${it.value}" }.joinToString { it })
eventSender.track(
"navigate",
mapOf("my_date" to ConfidenceValue.Date(Date()), "my_time" to ConfidenceValue.Timestamp(Date()))
)
}

fun updateContext() {
Expand Down

0 comments on commit cc65607

Please sign in to comment.