From 97c518423f44b36d0aadffe999a070f34ee72296 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 12 Sep 2024 12:04:36 +0200 Subject: [PATCH 01/33] chore: .idea --- .idea/compiler.xml | 2 +- .idea/gradle.xml | 5 ++--- .idea/kotlinc.xml | 6 ++++++ .idea/misc.xml | 3 +-- 4 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 .idea/kotlinc.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 3b22eca..c34ccc3 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,9 +4,8 @@ diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..9a55c2d --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 2a4d5b5..0f86676 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,5 @@ - - + From 9972637f88a2857098c4d751dc79039565812b6b Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 12 Sep 2024 12:05:39 +0200 Subject: [PATCH 02/33] feat: create TelemetryDeck to replace TelemetryManager --- README.md | 34 +- .../sdk/AppLifecycleTelemetryProvider.kt | 6 +- .../sdk/EnvironmentMetadataProvider.kt | 6 +- .../com/telemetrydeck/sdk/SessionProvider.kt | 4 +- .../sdk/TelemetryBroadcastTimer.kt | 31 +- .../com/telemetrydeck/sdk/TelemetryDeck.kt | 462 ++++++++++++++++++ .../com/telemetrydeck/sdk/TelemetryManager.kt | 37 +- .../sdk/TelemetryManagerSignals.kt | 13 + .../telemetrydeck/sdk/TelemetryProvider.kt | 2 +- .../telemetrydeck/sdk/TelemetryDeckTests.kt | 415 ++++++++++++++++ .../telemetrydeck/sdk/TelemetryManagerTest.kt | 21 +- .../com/telemetrydeck/sdk/TestProvider.kt | 14 + 12 files changed, 987 insertions(+), 58 deletions(-) create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt create mode 100644 lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt create mode 100644 lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt diff --git a/README.md b/README.md index 40c6ee7..64ca913 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Sending signals requires access to the internet so the following permission shou ### Using the application manifest -The TelemetryManager can be initialized automatically by adding the application key to the `application` section of the app's `AndroidManifest.xml`: +The TelemetryDeck can be initialized automatically by adding the application key to the `application` section of the app's `AndroidManifest.xml`: ```xml @@ -67,15 +67,15 @@ In addition, the following optional properties are supported: ### Programatic Usage -For greater control you can instead manually start the TelemetryManager client +For greater control you can instead manually start the TelemetryDeck client ```kotlin -val builder = TelemetryManager.Builder() +val builder = TelemetryDeck.Builder() .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") .showDebugLogs(true) .defaultUser("Person") -TelemetryManager.start(application, builder) +TelemetryDeck.start(application, builder) ``` ## Sending Signals @@ -83,24 +83,24 @@ TelemetryManager.start(application, builder) To send a signal immediately ```kotlin -TelemetryManager.send("appLaunchedRegularly") +TelemetryDeck.send("appLaunchedRegularly") ``` -To enqueue a signal to be sent by TelemetryManager at a later time +To enqueue a signal to be sent by TelemetryDeck at a later time ```kotlin -TelemetryManager.queue("appLaunchedRegularly") +TelemetryDeck.queue("appLaunchedRegularly") ``` ## Custom Telemetry -Another way to send signals is to register a custom `TelemetryProvider` . A provider maintains a reference to the TelemetryManager in order to queue or send signals. +Another way to send signals is to register a custom `TelemetryProvider` . A provider maintains a reference to the TelemetryDeck in order to queue or send signals. To create a provider, implement the `TelemetryProvider` interface: ```kotlin class CustomProvider: TelemetryProvider { - override fun register(ctx: Application?, manager: TelemetryManager) { + override fun register(ctx: Application?, manager: TelemetryDeck) { // configure and start the provider } @@ -114,18 +114,18 @@ Setup and start the provider during the `register` method. Tips: -- Do not retain a strong reference to the application context or the TelemetryManager. -- You can use `WeakReference` if you need to be able to call the TelemetryManager at a later time. +- Do not retain a strong reference to the application context or the TelemetryDeck. +- You can use `WeakReference` if you need to be able to call the TelemetryDeck at a later time. -To use your custom provider, register it using the `TelemetryManager.Builder` : +To use your custom provider, register it using the `TelemetryDeck.Builder` : ```kotlin -val builder = TelemetryManager.Builder() +val builder = TelemetryDeck.Builder() .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") .addProvider(CustomProvider()) ``` -When a signal is received by TelemetryManager, it can be enriched with platform and environment specific information. TelemetryManager calls the `enrich` method allowing every registered provider to add additional payload to a signal. +When a signal is received by TelemetryDeck, it can be enriched with platform and environment specific information. TelemetryDeck calls the `enrich` method allowing every registered provider to add additional payload to a signal. ```kotlin override fun enrich( @@ -142,7 +142,7 @@ override fun enrich( } ``` -TelemetryManager also makes use of providers in order to provide lifecycle and environment integration out of the box. Feel free to examine how they work and inspire your own implementations. You can also completely disable or override the default providers with your own. +TelemetryDeck also makes use of providers in order to provide lifecycle and environment integration out of the box. Feel free to examine how they work and inspire your own implementations. You can also completely disable or override the default providers with your own. - `SessionProvider` - Monitors the app lifecycle in order to broadcast the NewSessionBegan signal. This provider is tasked with resetting the sessionID when `sendNewSessionBeganSignal` is enabled. - `AppLifecycleTelemetryProvider` - Emits signals for application and activity lifecycle events. @@ -150,13 +150,13 @@ TelemetryManager also makes use of providers in order to provide lifecycle and e ```kotlin // Append a custom provider -val builder = TelemetryManager.Builder() +val builder = TelemetryDeck.Builder() .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") .addProvider(CustomProvider()) // Replace all default providers -val builder = TelemetryManager.Builder() +val builder = TelemetryDeck.Builder() .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") .providers(listOf(CustomProvider(), AnotherProvider())) ``` diff --git a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt index 106d3f0..333f181 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt @@ -13,11 +13,11 @@ import java.lang.ref.WeakReference */ class AppLifecycleTelemetryProvider : TelemetryProvider, Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { - private var manager: WeakReference? = null + private var manager: WeakReference? = null - override fun register(ctx: Application?, manager: TelemetryManager) { + override fun register(ctx: Application?, manager: TelemetryManagerSignals) { if (ctx == null) { - this.manager?.get()?.logger?.error("AppLifecycleTelemetryProvider requires a context but received null. No signals will be sent.") + this.manager?.get()?.debugLogger?.error("AppLifecycleTelemetryProvider requires a context but received null. No signals will be sent.") } this.manager = WeakReference(manager) ProcessLifecycleOwner.get().lifecycle.addObserver(this) diff --git a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt index 46f2d49..3679f22 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt @@ -11,7 +11,7 @@ class EnvironmentMetadataProvider : TelemetryProvider { private var enabled: Boolean = true private var metadata = mutableMapOf() - override fun register(ctx: Application?, manager: TelemetryManager) { + override fun register(ctx: Application?, manager: TelemetryManagerSignals) { if (ctx != null) { val appVersion = ManifestMetadataReader.getAppVersion(ctx) if (!appVersion.isNullOrEmpty()) { @@ -21,10 +21,10 @@ class EnvironmentMetadataProvider : TelemetryProvider { metadata["buildNumber"] = buildNumber.toString() } } else { - manager.logger?.error("EnvironmentMetadataProvider requires a context but received null. Signals will contain incomplete metadata.") + manager.debugLogger?.error("EnvironmentMetadataProvider requires a context but received null. Signals will contain incomplete metadata.") } if (android.os.Build.VERSION.RELEASE.isNullOrEmpty()) { - manager.logger?.error( + manager.debugLogger?.error( "EnvironmentMetadataProvider found no platform version information (android.os.Build.VERSION.RELEASE). Signal payloads will not be enriched." ) } else { diff --git a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt index 851506c..17e2c35 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt @@ -10,9 +10,9 @@ import java.lang.ref.WeakReference * Monitors the app lifecycle in order to broadcast the NewSessionBegan signal. */ class SessionProvider: TelemetryProvider, DefaultLifecycleObserver { - private var manager: WeakReference? = null + private var manager: WeakReference? = null - override fun register(ctx: Application?, manager: TelemetryManager) { + override fun register(ctx: Application?, manager: TelemetryManagerSignals) { this.manager = WeakReference(manager) ProcessLifecycleOwner.get().lifecycle.addObserver(this) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt index 91c0656..8662afc 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt @@ -6,7 +6,7 @@ import java.lang.ref.WeakReference import java.util.* import kotlin.math.abs -internal class TelemetryBroadcastTimer(private val manager: WeakReference, debugLogger: WeakReference) { +internal class TelemetryBroadcastTimer(private val manager: WeakReference, debugLogger: WeakReference) { // broadcast begins with a 10s delay after initialization and fires every 10s. private val timerChannel = ticker(delayMillis = 10_000, initialDelayMillis = 10_000) @@ -17,16 +17,6 @@ internal class TelemetryBroadcastTimer(private val manager: WeakReference): List { - val now = Date().time - return signals.filter { - // ignore signals older than 24h - (abs(now - it.receivedAt.time) / 1000) <= 24 * 60 * 60 - } - } - } - fun start() { CoroutineScope(Dispatchers.IO).launch { job?.cancel() @@ -37,18 +27,25 @@ internal class TelemetryBroadcastTimer(private val manager: WeakReference = listOf( + AppLifecycleTelemetryProvider() + + ) +): TelemetryManagerSignals { + var cache: SignalCache? = null + var logger: DebugLogger? = null + private val navigationStatus: NavigationStatus = MemoryNavigationStatus() + + override val signalCache: SignalCache? + get() = this.cache + + override val debugLogger: DebugLogger? + get() = this.logger + + override fun newSession(sessionID: UUID) { + this.configuration.sessionID = sessionID + } + + override fun newDefaultUser(user: String?) { + this.configuration.defaultUser = user + } + + override fun queue( + signalType: String, + clientUser: String?, + additionalPayload: Map + ) { + cache?.add(createSignal(signalType, clientUser, additionalPayload)) + } + + override fun queue( + signalType: SignalType, + clientUser: String?, + additionalPayload: Map + ) { + queue(signalType.type, clientUser, additionalPayload) + } + + override fun navigate(sourcePath: String, destinationPath: String, clientUser: String?) { + navigationStatus.applyDestination(destinationPath) + + val payload: Map = mapOf( + PayloadParameters.TelemetryDeckNavigationSchemaVersion.type to "1", + PayloadParameters.TelemetryDeckNavigationIdentifier.type to "$sourcePath -> $destinationPath", + PayloadParameters.TelemetryDeckNavigationSourcePath.type to sourcePath, + PayloadParameters.TelemetryDeckNavigationDestinationPath.type to destinationPath + ) + + queue(SignalType.TelemetryDeckNavigationPathChanged, clientUser, payload) + } + + override fun navigate(destinationPath: String, clientUser: String?) { + navigate(navigationStatus.getLastDestination(), destinationPath, clientUser) + } + + override suspend fun send( + signalType: String, + clientUser: String?, + additionalPayload: Map + ): Result { + return send(createSignal(signalType, clientUser, additionalPayload)) + } + + override suspend fun send( + signalType: SignalType, + clientUser: String?, + additionalPayload: Map + ): Result { + return send(signalType.type, clientUser, additionalPayload) + } + + override suspend fun signal(signals: List): Result { + return send(signals) + } + + suspend fun send( + signal: Signal + ): Result { + return send(listOf(signal)) + } + + suspend fun send( + signals: List + ): Result { + return try { + val client = TelemetryClient( + configuration.telemetryAppID, + configuration.apiBaseURL, + configuration.showDebugLogs, + logger + ) + client.send(signals) + success(Unit) + } catch (e: Exception) { + logger?.error("Failed to send signals due to an error ${e} ${e.stackTraceToString()}") + failure(e) + } + } + + internal var broadcastTimer: TelemetryBroadcastTimer? = null + + private fun installProviders(context: Context?) { + for (provider in providers) { + logger?.debug("Installing provider ${provider::class}.") + provider.register(context?.applicationContext as Application?, this) + } + } + + private fun createSignal( + signalType: String, + clientUser: String? = null, + additionalPayload: Map = emptyMap() + ): Signal { + var enrichedPayload = additionalPayload + for (provider in this.providers) { + enrichedPayload = provider.enrich(signalType, clientUser, enrichedPayload) + } + val userValue = clientUser ?: configuration.defaultUser ?: "" + + val userValueWithSalt = userValue + (configuration.salt ?: "") + val hashedUser = hashString(userValueWithSalt, "SHA-256") + + val payload = SignalPayload(additionalPayload = enrichedPayload) + val signal = Signal( + appID = configuration.telemetryAppID, + type = signalType, + clientUser = hashedUser, + payload = payload.asMultiValueDimension, + isTestMode = configuration.testMode.toString().lowercase() + ) + signal.sessionID = this.configuration.sessionID.toString() + logger?.debug("Created a signal ${signal.type}, session ${signal.sessionID}, test ${signal.isTestMode}") + return signal + } + + private fun hashString(input: String, algorithm: String): String { + return MessageDigest.getInstance(algorithm) + .digest(input.toByteArray()) + .fold("", { str, it -> str + "%02x".format(it) }) + } + + companion object : TelemetryManagerSignals { + internal val defaultTelemetryProviders: List + get() = listOf( + SessionProvider(), + AppLifecycleTelemetryProvider(), + EnvironmentMetadataProvider() + ) + + // TelemetryManager singleton + @Volatile + private var instance: TelemetryDeck? = null + + /** + * Builds and starts the application instance of `TelemetryManager`. + * Calling this method multiple times has no effect. + */ + fun start(context: Application, builder: Builder): TelemetryDeck { + val knownInstance = instance + if (knownInstance != null) { + return knownInstance + } + + return synchronized(this) { + val syncedInstance = instance + if (syncedInstance != null) { + syncedInstance + } else { + val newInstance = builder.build(context) + instance = newInstance + newInstance + } + } + } + + /** + * Shuts down the current instance of `TelemetryManager`. + */ + fun stop() { + val manager = getInstance() + ?: // nothing to do + return + manager.broadcastTimer?.stop() + for (provider in manager.providers) { + provider.stop() + } + synchronized(this) { + instance = null + } + } + + private fun getInstance(): TelemetryDeck? { + val knownInstance = instance + if (knownInstance != null) { + return knownInstance + } + return null + } + + override fun newSession(sessionID: UUID) { + getInstance()?.newSession(sessionID) + } + + override fun newDefaultUser(user: String?) { + getInstance()?.newDefaultUser(user) + } + + override fun queue( + signalType: String, + clientUser: String?, + additionalPayload: Map + ) { + getInstance()?.queue(signalType, clientUser, additionalPayload) + } + + override fun queue( + signalType: SignalType, + clientUser: String?, + additionalPayload: Map + ) { + getInstance()?.queue(signalType, clientUser, additionalPayload) + } + + override fun navigate(sourcePath: String, destinationPath: String, clientUser: String?) { + getInstance()?.navigate(sourcePath, destinationPath, clientUser = clientUser) + } + + override fun navigate(destinationPath: String, clientUser: String?) { + getInstance()?.navigate(destinationPath, clientUser = clientUser) + } + + override suspend fun send( + signalType: String, + clientUser: String?, + additionalPayload: Map + ): Result { + val result = getInstance()?.send(signalType, clientUser, additionalPayload) + if (result != null) { + return result + } + return failure(NullPointerException()) + } + + override suspend fun send( + signalType: SignalType, + clientUser: String?, + additionalPayload: Map + ): Result { + val result = getInstance()?.send(signalType, clientUser, additionalPayload) + if (result != null) { + return result + } + return failure(NullPointerException()) + } + + override suspend fun signal(signals: List): Result { + val result = getInstance()?.signal(signals) + if (result != null) { + return result + } + return failure(NullPointerException()) + } + + override val signalCache: SignalCache? + get() = getInstance()?.signalCache + + override val debugLogger: DebugLogger? + get() = getInstance()?.debugLogger + + override val configuration: TelemetryManagerConfiguration? + get() = getInstance()?.configuration + } + + + data class Builder( + private var configuration: TelemetryManagerConfiguration? = null, + private var providers: List? = null, + private var additionalProviders: MutableList? = null, + private var appID: UUID? = null, + private var defaultUser: String? = null, + private var sessionID: UUID? = null, + private var testMode: Boolean? = null, + private var showDebugLogs: Boolean? = null, + private var sendNewSessionBeganSignal: Boolean? = null, + private var apiBaseURL: URL? = null, + private var logger: DebugLogger? = null, + private var salt: String? = null + ) { + /** + * Set the TelemetryManager configuration. + * Use this method to directly set all configuration fields and bypass any default values. + * + */ + fun configuration(config: TelemetryManagerConfiguration) = apply { + this.configuration = config + } + + /** + * Override the default set of TelemetryProviders. + */ + fun providers(providerList: List) = + apply { this.providers = providerList } + + /** + * Append a custom TelemetryProvider which can produce or enrich signals + */ + fun addProvider(provider: TelemetryProvider) = apply { + if (additionalProviders == null) { + additionalProviders = mutableListOf() + } + additionalProviders?.add(provider) + } + + fun appID(id: String) = apply { + appID(UUID.fromString(id)) + } + + fun appID(id: UUID) = apply { + appID = id + } + + fun sendNewSessionBeganSignal(sendNewSessionBeganSignal: Boolean) = apply { + this.sendNewSessionBeganSignal = sendNewSessionBeganSignal + } + + fun baseURL(url: URL) = apply { + apiBaseURL = url + } + + fun baseURL(url: String) = apply { + apiBaseURL = URL(url) + } + + fun defaultUser(user: String) = apply { + this.defaultUser = user + } + + fun sessionID(sessionID: UUID) = apply { + this.sessionID = sessionID + } + + fun testMode(testMode: Boolean) = apply { + this.testMode = testMode + } + + fun showDebugLogs(showDebugLogs: Boolean) = apply { + this.showDebugLogs = showDebugLogs + } + + fun salt(salt: String?) = apply { + this.salt = salt + } + + /** + * Provide a custom logger implementation to be used by TelemetryManager. + */ + fun logger(debugLogger: DebugLogger?) = apply { + this.logger = debugLogger + } + + fun build(context: Application?): TelemetryDeck { + var config = this.configuration + val appID = this.appID + // check if configuration is already set or create a new instance using appID + val initConfiguration = config == null + if (config == null) { + if (appID == null) { + throw Exception("AppID must be set.") + } + config = TelemetryManagerConfiguration(appID) + } + + // check if providers have been provided or use a default list + var providers = this.providers + if (providers == null) { + providers = defaultTelemetryProviders + } + // check for additional providers that should be appended + if (additionalProviders != null) { + providers = providers + (additionalProviders?.toList() ?: listOf()) + } + + // check if sessionID has been provided to override the default one + val sessionID = this.sessionID + if (sessionID != null) { + config.sessionID = sessionID + } + + // optional fields + val defaultUser = this.defaultUser + if (defaultUser != null) { + config.defaultUser = defaultUser + } + + val testMode = this.testMode + if (testMode != null) { + config.testMode = testMode + } else { + // do not change testMode if it was provided through a configuration object + if (initConfiguration) { + config.testMode = 0 != (context?.applicationInfo?.flags + ?: 0) and ApplicationInfo.FLAG_DEBUGGABLE + } + } + + val salt = this.salt + if (salt != null) { + config.salt = salt + } + + val showDebugLogs = this.showDebugLogs + if (showDebugLogs != null) { + config.showDebugLogs = showDebugLogs + } + + val logger: DebugLogger = this.logger ?: TelemetryManagerDebugLogger + logger.configure(config.showDebugLogs) + + val apiBaseURL = this.apiBaseURL + if (apiBaseURL != null) { + config.apiBaseURL = apiBaseURL + } + + val sendNewSessionBeganSignal = sendNewSessionBeganSignal + if (sendNewSessionBeganSignal != null) { + config.sendNewSessionBeganSignal = sendNewSessionBeganSignal + } + + val manager = TelemetryDeck(config, providers) + manager.logger = logger + manager.installProviders(context) + + val broadcaster = + TelemetryBroadcastTimer(WeakReference(manager), WeakReference(manager.logger)) + broadcaster.start() + manager.broadcastTimer = broadcaster + + if (context != null) { + manager.cache = PersistentSignalCache(context.cacheDir, logger) + } else { + manager.cache = MemorySignalCache() + } + + return manager + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt index 4d352e0..72e5055 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt @@ -3,6 +3,7 @@ package com.telemetrydeck.sdk import android.app.Application import android.content.Context import android.content.pm.ApplicationInfo +import com.telemetrydeck.sdk.TelemetryDeck.Companion import java.lang.ref.WeakReference import java.net.URL import java.security.MessageDigest @@ -10,18 +11,24 @@ import java.util.UUID import kotlin.Result.Companion.failure import kotlin.Result.Companion.success - +@Deprecated("Use TelemetryDeck instead", ReplaceWith("TelemetryDeck")) class TelemetryManager( - val configuration: TelemetryManagerConfiguration, + override val configuration: TelemetryManagerConfiguration, val providers: List = listOf( AppLifecycleTelemetryProvider() - ) + ), ) : TelemetryManagerSignals { var cache: SignalCache? = null var logger: DebugLogger? = null private val navigationStatus: NavigationStatus = MemoryNavigationStatus() + override val signalCache: SignalCache? + get() = this.cache + + override val debugLogger: DebugLogger? + get() = this.logger + override fun newSession(sessionID: UUID) { this.configuration.sessionID = sessionID } @@ -79,6 +86,10 @@ class TelemetryManager( return send(signalType.type, clientUser, additionalPayload) } + override suspend fun signal(signals: List): Result { + return send(signals) + } + suspend fun send( signal: Signal ): Result { @@ -124,7 +135,7 @@ class TelemetryManager( val userValue = clientUser ?: configuration.defaultUser ?: "" val userValueWithSalt = userValue + (configuration.salt ?: "") - val hashedUser = hashString(userValue, "SHA-256") + val hashedUser = hashString(userValueWithSalt, "SHA-256") val payload = SignalPayload(additionalPayload = enrichedPayload) val signal = Signal( @@ -258,9 +269,27 @@ class TelemetryManager( } return failure(NullPointerException()) } + + override suspend fun signal(signals: List): Result { + val result = getInstance()?.signal(signals) + if (result != null) { + return result + } + return failure(NullPointerException()) + } + + override val signalCache: SignalCache? + get() = getInstance()?.signalCache + + override val debugLogger: DebugLogger? + get() = getInstance()?.debugLogger + + override val configuration: TelemetryManagerConfiguration? + get() = getInstance()?.configuration } + @Deprecated("Use TelemetryDeck.Builder instead", ReplaceWith("TelemetryDeck.Builder")) data class Builder( private var configuration: TelemetryManagerConfiguration? = null, private var providers: List? = null, diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt index 610c930..f401fba 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt @@ -71,4 +71,17 @@ interface TelemetryManagerSignals { clientUser: String? = null, additionalPayload: Map = emptyMap() ): Result + + + /** + * Send multiple signals immediately. + */ + suspend fun signal(signals: List): Result + + + val signalCache: SignalCache? + + val debugLogger: DebugLogger? + + val configuration: TelemetryManagerConfiguration? } \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt index 01581b2..013ac0b 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt @@ -10,7 +10,7 @@ interface TelemetryProvider { * Registers the provider with the telemetry manager. * The provider keeps a weak reference to telemetry manager in order to queue or send signals. */ - fun register(ctx: Application?, manager: TelemetryManager) + fun register(ctx: Application?, manager: TelemetryManagerSignals) /** * Calling stop deactivates the provider and prevents future signals from being sent. diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt new file mode 100644 index 0000000..90ba6fa --- /dev/null +++ b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt @@ -0,0 +1,415 @@ +package com.telemetrydeck.sdk + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import java.net.URL +import java.util.Calendar +import java.util.Date +import java.util.UUID +import kotlin.math.abs + +class TelemetryDeckTests { + + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @Test + fun telemetryDeck_sets_signal_properties() { + val appID = "32CB6574-6732-4238-879F-582FEBEB6536" + val config = TelemetryManagerConfiguration(appID) + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.queue("type", "clientUser", emptyMap()) + + val queuedSignal = manager.cache?.empty()?.first() + + Assert.assertNotNull(queuedSignal) + Assert.assertEquals(UUID.fromString(appID), queuedSignal!!.appID) + Assert.assertEquals(config.sessionID, UUID.fromString(queuedSignal.sessionID)) + Assert.assertEquals("type", queuedSignal.type) + Assert.assertEquals( + "6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149", + queuedSignal.clientUser + ) + Assert.assertEquals("false", queuedSignal.isTestMode) + } + + @Test + fun telemetryDeck_applies_custom_salt() { + val appID = "32CB6574-6732-4238-879F-582FEBEB6536" + val config = TelemetryManagerConfiguration(appID) + config.salt = "my salt" + val manager = TelemetryDeck.Builder().configuration(config).build(null) + manager.queue("type", "clientUser", emptyMap()) + val queuedSignal = manager.cache?.empty()?.first() + Assert.assertEquals("9a68a3790deb1db66f80855b8e7c5a97df8002ef90d3039f9e16c94cfbd11d99", queuedSignal?.clientUser) + } + + @Test + fun telemetryDeck_builder_set_configuration() { + val appID = "32CB6574-6732-4238-879F-582FEBEB6536" + val config = TelemetryManagerConfiguration(appID) + config.defaultUser = "user" + config.salt = "salt" + + val sut = TelemetryDeck.Builder() + + val result = sut.configuration(config).build(null) + + Assert.assertEquals(UUID.fromString(appID), result.configuration.telemetryAppID) + Assert.assertEquals(URL("https://nom.telemetrydeck.com"), result.configuration.apiBaseURL) + Assert.assertEquals("user", result.configuration.defaultUser) + Assert.assertEquals(config.sessionID, result.configuration.sessionID) + Assert.assertEquals(config.showDebugLogs, result.configuration.showDebugLogs) + Assert.assertEquals(config.testMode, result.configuration.testMode) + Assert.assertEquals(config.salt, result.configuration.salt) + } + + @Test + fun telemetryDeck_builder_set_app_ID() { + val appID = "32CB6574-6732-4238-879F-582FEBEB6536" + val sut = TelemetryDeck.Builder() + + val result = sut.appID(appID).build(null) + + Assert.assertEquals(UUID.fromString(appID), result.configuration.telemetryAppID) + Assert.assertEquals(URL("https://nom.telemetrydeck.com"), result.configuration.apiBaseURL) + Assert.assertEquals(null, result.configuration.defaultUser) + } + + @Test + fun telemetryDeck_builder_set_baseURL() { + val sut = TelemetryDeck.Builder() + val result = + sut.appID("32CB6574-6732-4238-879F-582FEBEB6536").baseURL("https://telemetrydeck.com") + .build(null) + Assert.assertEquals(URL("https://telemetrydeck.com"), result.configuration.apiBaseURL) + } + + + @Test + fun telemetryDeck_builder_set_testMode() { + val sut = TelemetryDeck.Builder() + val result = sut + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .testMode(true) + .build(null) + Assert.assertEquals(true, result.configuration.testMode) + } + + @Test + fun telemetryDeck_builder_testMode_off_by_default() { + val sut = TelemetryDeck.Builder() + val result = sut + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .build(null) + Assert.assertEquals(false, result.configuration.testMode) + } + + @Test + fun telemetryDeck_builder_set_defaultUser() { + val sut = TelemetryDeck.Builder() + val result = + sut.appID("32CB6574-6732-4238-879F-582FEBEB6536") + .defaultUser("Dear Person") + .build(null) + Assert.assertEquals("Dear Person", result.configuration.defaultUser) + } + + @Test + fun telemetryDeck_builder_set_salt() { + val sut = TelemetryDeck.Builder() + val result = + sut.appID("32CB6574-6732-4238-879F-582FEBEB6536") + .salt("salty") + .build(null) + Assert.assertEquals("salty", result.configuration.salt) + } + + @Test + fun telemetryDeck_builder_set_showDebugLogs() { + val sut = TelemetryDeck.Builder() + val result = + sut + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .showDebugLogs(true) + .build(null) + Assert.assertEquals(true, result.configuration.showDebugLogs) + } + + @Test + fun telemetryDeck_builder_installs_default_logger_with_logging_disabled() { + val sut = TelemetryDeck.Builder() + val result = sut + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .build(null) + Assert.assertNotNull(result.logger) + Assert.assertFalse(result.configuration.showDebugLogs) + } + + @Test + fun telemetryDeck_builder_set_sessionID() { + val sessionID = UUID.randomUUID() + val sut = TelemetryDeck.Builder() + val result = sut + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .sessionID(sessionID) + .build(null) + Assert.assertEquals(sessionID, result.configuration.sessionID) + } + + @Test + fun telemetryDeck_newSession_resets_sessionID() { + val sessionID = UUID.randomUUID() + val builder = TelemetryDeck.Builder() + val sut = builder + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .sessionID(sessionID) + .build(null) + sut.newSession() + Assert.assertNotEquals(sessionID, sut.configuration.sessionID) + } + + @Test + fun telemetryDeck_newSession_set_preferred_sessionID() { + val sessionID = UUID.randomUUID() + val wantedSessionID = UUID.randomUUID() + Assert.assertNotEquals(sessionID, wantedSessionID) + val builder = TelemetryDeck.Builder() + val sut = builder + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .sessionID(sessionID) + .build(null) + sut.newSession(wantedSessionID) + Assert.assertEquals(wantedSessionID, sut.configuration.sessionID) + } + + @Test + fun telemetryDeck_newDefaultUser_changes_defaultUser() { + val builder = TelemetryDeck.Builder() + val sut = builder + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .defaultUser("user1") + .build(null) + sut.newDefaultUser("user2") + Assert.assertEquals("user2", sut.configuration.defaultUser) + } + + @Test + fun telemetryDeck_testMode_on_added_to_signals() { + val builder = TelemetryDeck.Builder() + val sut = builder + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .testMode(true) + .build(null) + sut.queue("type") + + Assert.assertEquals("true", sut.cache?.empty()?.get(0)?.isTestMode) + } + + @Test + fun telemetryDeck_testMode_off_added_to_signals() { + val builder = TelemetryDeck.Builder() + val sut = builder + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .testMode(false) + .build(null) + sut.queue("type") + + Assert.assertEquals("false", sut.cache?.empty()?.get(0)?.isTestMode) + } + + @Test + fun telemetryDeck_addProvider_appends_after_default_providers() { + val builder = TelemetryDeck.Builder() + val sut = builder + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .addProvider(TestProvider()) + .build(null) + sut.queue("type") + + Assert.assertEquals(4, sut.providers.count()) + Assert.assertTrue(sut.providers[3] is TestProvider) + } + + @Test + fun telemetryDeck_addProvider_custom_provider_is_registered() { + val provider = TestProvider() + Assert.assertFalse(provider.registered) + + val builder = TelemetryDeck.Builder() + builder + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .addProvider(provider) + .build(null) + + Assert.assertTrue(provider.registered) + } + + @Test + fun telemetryBroadcastTimer_can_filter_older_signals() { + // an old signal is received longer than 24h ago + val okSignal = Signal(appID = UUID.randomUUID(), "okSignal", "user", SignalPayload()) + val oldSignal = Signal(appID = UUID.randomUUID(), "oldSignal", "user", SignalPayload()) + val calendar = Calendar.getInstance() + calendar.add(Calendar.DAY_OF_YEAR, -2) + oldSignal.receivedAt = calendar.time + + val filteredSignals = filterOldSignals(listOf(okSignal, oldSignal)) + + Assert.assertEquals(1, filteredSignals.count()) + Assert.assertEquals("okSignal", filteredSignals[0].type) + } + + @Test + fun telemetryDeck_navigate_source_destination_sets_default_parameters() { + val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536") + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.navigate("source", "destination") + + val queuedSignal = manager.cache?.empty()?.first() + + Assert.assertNotNull(queuedSignal) + + // validate the signal type + Assert.assertEquals(queuedSignal?.type, "TelemetryDeck.Navigation.pathChanged") + + // validate the navigation status payload + // https://github.com/TelemetryDeck/KotlinSDK/issues/28 + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.schemaVersion") }, + "TelemetryDeck.Navigation.schemaVersion:1" + ) + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.identifier") }, + "TelemetryDeck.Navigation.identifier:source -> destination" + ) + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.sourcePath") }, + "TelemetryDeck.Navigation.sourcePath:source" + ) + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.destinationPath") }, + "TelemetryDeck.Navigation.destinationPath:destination" + ) + } + + @Test + fun telemetryDeck_navigate_source_destination_sets_clientUser() { + val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536") + config.defaultUser = "user" + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.navigate("source", "destination", "clientUser") + + val queuedSignal = manager.cache?.empty()?.first() + + Assert.assertNotNull(queuedSignal) + + // validate that the provided user was used and not default + Assert.assertEquals( + queuedSignal?.clientUser, + "6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149" + ) + } + + @Test + fun telemetryDeck_navigate_source_destination_uses_default_user() { + val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536") + config.defaultUser = "clientUser" + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.navigate("source", "destination") + + val queuedSignal = manager.cache?.empty()?.first() + + Assert.assertNotNull(queuedSignal) + + // validate that the default user was used + Assert.assertEquals( + queuedSignal?.clientUser, + "6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149" + ) + } + + @Test + fun telemetryDeck_navigate_destination_no_previous_source() { + val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536") + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.navigate("destination") + + val queuedSignal = manager.cache?.empty()?.first() + + Assert.assertNotNull(queuedSignal) + + // validate the signal type + Assert.assertEquals(queuedSignal?.type, "TelemetryDeck.Navigation.pathChanged") + + // validate the navigation status payload + // https://github.com/TelemetryDeck/KotlinSDK/issues/28 + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.schemaVersion") }, + "TelemetryDeck.Navigation.schemaVersion:1" + ) + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.identifier") }, + "TelemetryDeck.Navigation.identifier: -> destination" + ) + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.sourcePath") }, + "TelemetryDeck.Navigation.sourcePath:" + ) + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.destinationPath") }, + "TelemetryDeck.Navigation.destinationPath:destination" + ) + } + + @Test + fun telemetryDeck_navigate_destination_uses_previous_destination_as_source() { + val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536") + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.navigate("destination1") + manager.navigate("destination2") + + val queuedSignal = manager.cache?.empty()?.last() + + Assert.assertNotNull(queuedSignal) + + // validate the signal type + Assert.assertEquals(queuedSignal?.type, "TelemetryDeck.Navigation.pathChanged") + + // validate the navigation status payload + // https://github.com/TelemetryDeck/KotlinSDK/issues/28 + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.schemaVersion") }, + "TelemetryDeck.Navigation.schemaVersion:1" + ) + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.identifier") }, + "TelemetryDeck.Navigation.identifier:destination1 -> destination2" + ) + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.sourcePath") }, + "TelemetryDeck.Navigation.sourcePath:destination1" + ) + Assert.assertEquals( + queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.destinationPath") }, + "TelemetryDeck.Navigation.destinationPath:destination2" + ) + } + + private fun filterOldSignals(signals: List): List { + val now = Date().time + return signals.filter { + // ignore signals older than 24h + (abs(now - it.receivedAt.time) / 1000) <= 24 * 60 * 60 + } + } +} \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryManagerTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryManagerTest.kt index d37f4c8..ff62ffa 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryManagerTest.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryManagerTest.kt @@ -1,13 +1,14 @@ package com.telemetrydeck.sdk -import android.app.Application import androidx.arch.core.executor.testing.InstantTaskExecutorRule import org.junit.Assert import org.junit.Rule import org.junit.Test import java.net.URL import java.util.Calendar +import java.util.Date import java.util.UUID +import kotlin.math.abs class TelemetryManagerTest { @@ -256,7 +257,7 @@ class TelemetryManagerTest { calendar.add(Calendar.DAY_OF_YEAR, -2) oldSignal.receivedAt = calendar.time - val filteredSignals = TelemetryBroadcastTimer.filterOldSignals(listOf(okSignal, oldSignal)) + val filteredSignals = filterOldSignals(listOf(okSignal, oldSignal)) Assert.assertEquals(1, filteredSignals.count()) Assert.assertEquals("okSignal", filteredSignals[0].type) @@ -402,15 +403,13 @@ class TelemetryManagerTest { "TelemetryDeck.Navigation.destinationPath:destination2" ) } -} -open class TestProvider : TelemetryProvider { - var registered = false - override fun register(ctx: Application?, manager: TelemetryManager) { - registered = true + private fun filterOldSignals(signals: List): List { + val now = Date().time + return signals.filter { + // ignore signals older than 24h + (abs(now - it.receivedAt.time) / 1000) <= 24 * 60 * 60 + } } +} - override fun stop() { - // - } -} \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt b/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt new file mode 100644 index 0000000..f56bda2 --- /dev/null +++ b/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt @@ -0,0 +1,14 @@ +package com.telemetrydeck.sdk + +import android.app.Application + +open class TestProvider : TelemetryProvider { + var registered = false + override fun register(ctx: Application?, manager: TelemetryManagerSignals) { + registered = true + } + + override fun stop() { + // + } +} \ No newline at end of file From 0e8e9ce19e95eb33daeb4515a448bc29011bf4c4 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 12 Sep 2024 12:12:05 +0200 Subject: [PATCH 03/33] feat: rename TelemetryManagerSignals interface to TelemetryDeckClient --- README.md | 11 +++++++---- .../sdk/AppLifecycleTelemetryProvider.kt | 4 ++-- .../telemetrydeck/sdk/EnvironmentMetadataProvider.kt | 2 +- .../java/com/telemetrydeck/sdk/SessionProvider.kt | 4 ++-- .../com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt | 4 +--- .../main/java/com/telemetrydeck/sdk/TelemetryDeck.kt | 4 ++-- ...emetryManagerSignals.kt => TelemetryDeckClient.kt} | 2 +- .../java/com/telemetrydeck/sdk/TelemetryManager.kt | 5 ++--- .../java/com/telemetrydeck/sdk/TelemetryProvider.kt | 2 +- .../test/java/com/telemetrydeck/sdk/TestProvider.kt | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) rename lib/src/main/java/com/telemetrydeck/sdk/{TelemetryManagerSignals.kt => TelemetryDeckClient.kt} (98%) diff --git a/README.md b/README.md index 64ca913..f5e13e9 100644 --- a/README.md +++ b/README.md @@ -94,13 +94,13 @@ TelemetryDeck.queue("appLaunchedRegularly") ## Custom Telemetry -Another way to send signals is to register a custom `TelemetryProvider` . A provider maintains a reference to the TelemetryDeck in order to queue or send signals. +Another way to send signals is to register a custom `TelemetryProvider` . A provider maintains a reference to the TelemetryDeck client in order to queue or send signals. To create a provider, implement the `TelemetryProvider` interface: ```kotlin class CustomProvider: TelemetryProvider { - override fun register(ctx: Application?, manager: TelemetryDeck) { + override fun register(ctx: Application?, manager: TelemetryDeckClient) { // configure and start the provider } @@ -114,7 +114,7 @@ Setup and start the provider during the `register` method. Tips: -- Do not retain a strong reference to the application context or the TelemetryDeck. +- Do not retain a strong reference to the application context or the TelemetryDeck client instance. - You can use `WeakReference` if you need to be able to call the TelemetryDeck at a later time. To use your custom provider, register it using the `TelemetryDeck.Builder` : @@ -125,7 +125,7 @@ val builder = TelemetryDeck.Builder() .addProvider(CustomProvider()) ``` -When a signal is received by TelemetryDeck, it can be enriched with platform and environment specific information. TelemetryDeck calls the `enrich` method allowing every registered provider to add additional payload to a signal. +In the implementation of your custom `TelemetryProvider`, we offer a callback function where you can append additional payload attributes to the signal. The `enrich` call is made right before sending the signal: ```kotlin override fun enrich( @@ -133,11 +133,14 @@ override fun enrich( clientUser: String?, additionalPayload: Map ): Map { + // retrieve the payload of signal val signalPayload = additionalPayload.toMutableMap() + // add additional attributes of your choice val today = LocalDateTime.now().dayOfWeek if (today == DayOfWeek.MONDAY) { signalPayload["isMonday"] = "yes" } + // return the enriched payload return signalPayload } ``` diff --git a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt index 333f181..7bf0c5b 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt @@ -13,9 +13,9 @@ import java.lang.ref.WeakReference */ class AppLifecycleTelemetryProvider : TelemetryProvider, Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { - private var manager: WeakReference? = null + private var manager: WeakReference? = null - override fun register(ctx: Application?, manager: TelemetryManagerSignals) { + override fun register(ctx: Application?, manager: TelemetryDeckClient) { if (ctx == null) { this.manager?.get()?.debugLogger?.error("AppLifecycleTelemetryProvider requires a context but received null. No signals will be sent.") } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt index 3679f22..cd1522c 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt @@ -11,7 +11,7 @@ class EnvironmentMetadataProvider : TelemetryProvider { private var enabled: Boolean = true private var metadata = mutableMapOf() - override fun register(ctx: Application?, manager: TelemetryManagerSignals) { + override fun register(ctx: Application?, manager: TelemetryDeckClient) { if (ctx != null) { val appVersion = ManifestMetadataReader.getAppVersion(ctx) if (!appVersion.isNullOrEmpty()) { diff --git a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt index 17e2c35..5e78842 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt @@ -10,9 +10,9 @@ import java.lang.ref.WeakReference * Monitors the app lifecycle in order to broadcast the NewSessionBegan signal. */ class SessionProvider: TelemetryProvider, DefaultLifecycleObserver { - private var manager: WeakReference? = null + private var manager: WeakReference? = null - override fun register(ctx: Application?, manager: TelemetryManagerSignals) { + override fun register(ctx: Application?, manager: TelemetryDeckClient) { this.manager = WeakReference(manager) ProcessLifecycleOwner.get().lifecycle.addObserver(this) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt index 8662afc..a53f7e5 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt @@ -3,10 +3,8 @@ package com.telemetrydeck.sdk import kotlinx.coroutines.* import kotlinx.coroutines.channels.ticker import java.lang.ref.WeakReference -import java.util.* -import kotlin.math.abs -internal class TelemetryBroadcastTimer(private val manager: WeakReference, debugLogger: WeakReference) { +internal class TelemetryBroadcastTimer(private val manager: WeakReference, debugLogger: WeakReference) { // broadcast begins with a 10s delay after initialization and fires every 10s. private val timerChannel = ticker(delayMillis = 10_000, initialDelayMillis = 10_000) diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt index 182f8d0..2a3503d 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt @@ -16,7 +16,7 @@ class TelemetryDeck( AppLifecycleTelemetryProvider() ) -): TelemetryManagerSignals { +): TelemetryDeckClient { var cache: SignalCache? = null var logger: DebugLogger? = null private val navigationStatus: NavigationStatus = MemoryNavigationStatus() @@ -154,7 +154,7 @@ class TelemetryDeck( .fold("", { str, it -> str + "%02x".format(it) }) } - companion object : TelemetryManagerSignals { + companion object : TelemetryDeckClient { internal val defaultTelemetryProviders: List get() = listOf( SessionProvider(), diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt similarity index 98% rename from lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt rename to lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt index f401fba..1a58fef 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt @@ -2,7 +2,7 @@ package com.telemetrydeck.sdk import java.util.UUID -interface TelemetryManagerSignals { +interface TelemetryDeckClient { /** * All future signals belong to a new session. diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt index 72e5055..ddc8e27 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt @@ -3,7 +3,6 @@ package com.telemetrydeck.sdk import android.app.Application import android.content.Context import android.content.pm.ApplicationInfo -import com.telemetrydeck.sdk.TelemetryDeck.Companion import java.lang.ref.WeakReference import java.net.URL import java.security.MessageDigest @@ -17,7 +16,7 @@ class TelemetryManager( val providers: List = listOf( AppLifecycleTelemetryProvider() ), -) : TelemetryManagerSignals { +) : TelemetryDeckClient { var cache: SignalCache? = null var logger: DebugLogger? = null @@ -156,7 +155,7 @@ class TelemetryManager( .fold("", { str, it -> str + "%02x".format(it) }) } - companion object : TelemetryManagerSignals { + companion object : TelemetryDeckClient { internal val defaultTelemetryProviders: List get() = listOf( SessionProvider(), diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt index 013ac0b..769e007 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt @@ -10,7 +10,7 @@ interface TelemetryProvider { * Registers the provider with the telemetry manager. * The provider keeps a weak reference to telemetry manager in order to queue or send signals. */ - fun register(ctx: Application?, manager: TelemetryManagerSignals) + fun register(ctx: Application?, manager: TelemetryDeckClient) /** * Calling stop deactivates the provider and prevents future signals from being sent. diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt b/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt index f56bda2..8126992 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt @@ -4,7 +4,7 @@ import android.app.Application open class TestProvider : TelemetryProvider { var registered = false - override fun register(ctx: Application?, manager: TelemetryManagerSignals) { + override fun register(ctx: Application?, manager: TelemetryDeckClient) { registered = true } From 1bbf97561c581b0837e63515a002dbcaa0ffdc9a Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 12 Sep 2024 12:24:00 +0200 Subject: [PATCH 04/33] feat: rename manager to client in TelemetryProvider, update README --- README.md | 22 ++++++++++++++----- .../sdk/AppLifecycleTelemetryProvider.kt | 4 ++-- .../sdk/EnvironmentMetadataProvider.kt | 6 ++--- .../com/telemetrydeck/sdk/SessionProvider.kt | 4 ++-- .../telemetrydeck/sdk/TelemetryProvider.kt | 2 +- .../com/telemetrydeck/sdk/SignalsUnitTest.kt | 2 +- .../com/telemetrydeck/sdk/TestProvider.kt | 2 +- 7 files changed, 26 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index f5e13e9..248cc8d 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ In addition, the following optional properties are supported: - `com.telemetrydeck.sdk.testMode` - `com.telemetrydeck.sdk.defaultUser` -### Programatic Usage +### Programmatic Usage For greater control you can instead manually start the TelemetryDeck client @@ -94,13 +94,14 @@ TelemetryDeck.queue("appLaunchedRegularly") ## Custom Telemetry -Another way to send signals is to register a custom `TelemetryProvider` . A provider maintains a reference to the TelemetryDeck client in order to queue or send signals. +Another way to send signals is to register a custom `TelemetryProvider`. A provider maintains a reference to the TelemetryDeck client in order to queue or send signals based on environment or other triggers. + To create a provider, implement the `TelemetryProvider` interface: ```kotlin class CustomProvider: TelemetryProvider { - override fun register(ctx: Application?, manager: TelemetryDeckClient) { + override fun register(ctx: Application?, client: TelemetryDeckClient) { // configure and start the provider } @@ -117,7 +118,7 @@ Tips: - Do not retain a strong reference to the application context or the TelemetryDeck client instance. - You can use `WeakReference` if you need to be able to call the TelemetryDeck at a later time. -To use your custom provider, register it using the `TelemetryDeck.Builder` : +To use your custom provider, register it by calling `addProvider` using the `TelemetryDeck.Builder` : ```kotlin val builder = TelemetryDeck.Builder() @@ -145,11 +146,11 @@ override fun enrich( } ``` -TelemetryDeck also makes use of providers in order to provide lifecycle and environment integration out of the box. Feel free to examine how they work and inspire your own implementations. You can also completely disable or override the default providers with your own. +We use providers internally to provide lifecycle and environment integration out of the box. Feel free to examine how they work and inspire your own implementations. You can also completely disable or override the default providers with your own. - `SessionProvider` - Monitors the app lifecycle in order to broadcast the NewSessionBegan signal. This provider is tasked with resetting the sessionID when `sendNewSessionBeganSignal` is enabled. - `AppLifecycleTelemetryProvider` - Emits signals for application and activity lifecycle events. -- `EnvironmentMetadataProvider` - Adds environment and device information to outgoing Signals. This provider overrides the `enrich` method in order to append additional metdata for all signals before sending them. +- `EnvironmentMetadataProvider` - Adds environment and device information to outgoing Signals. This provider overrides the `enrich` method in order to append additional metadata for all signals before sending them. ```kotlin // Append a custom provider @@ -164,6 +165,15 @@ val builder = TelemetryDeck.Builder() .providers(listOf(CustomProvider(), AnotherProvider())) ``` + +### Migrating providers to 3.0+ + +To adapt to the updated `TelemetryProvider` interface, please perform the following changes: + +* Adapt the signature of the `register` method to `register(ctx: Application?, client: TelemetryDeckClient)` +* To access the logger, use `client.debugLogger` +* To access the signal cache, use `client.signalCache` + ## Requirements - Android API 21 or later diff --git a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt index 7bf0c5b..b9436cd 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt @@ -15,11 +15,11 @@ class AppLifecycleTelemetryProvider : TelemetryProvider, Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { private var manager: WeakReference? = null - override fun register(ctx: Application?, manager: TelemetryDeckClient) { + override fun register(ctx: Application?, client: TelemetryDeckClient) { if (ctx == null) { this.manager?.get()?.debugLogger?.error("AppLifecycleTelemetryProvider requires a context but received null. No signals will be sent.") } - this.manager = WeakReference(manager) + this.manager = WeakReference(client) ProcessLifecycleOwner.get().lifecycle.addObserver(this) ctx?.registerActivityLifecycleCallbacks(this) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt index cd1522c..cd5f2bb 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt @@ -11,7 +11,7 @@ class EnvironmentMetadataProvider : TelemetryProvider { private var enabled: Boolean = true private var metadata = mutableMapOf() - override fun register(ctx: Application?, manager: TelemetryDeckClient) { + override fun register(ctx: Application?, client: TelemetryDeckClient) { if (ctx != null) { val appVersion = ManifestMetadataReader.getAppVersion(ctx) if (!appVersion.isNullOrEmpty()) { @@ -21,10 +21,10 @@ class EnvironmentMetadataProvider : TelemetryProvider { metadata["buildNumber"] = buildNumber.toString() } } else { - manager.debugLogger?.error("EnvironmentMetadataProvider requires a context but received null. Signals will contain incomplete metadata.") + client.debugLogger?.error("EnvironmentMetadataProvider requires a context but received null. Signals will contain incomplete metadata.") } if (android.os.Build.VERSION.RELEASE.isNullOrEmpty()) { - manager.debugLogger?.error( + client.debugLogger?.error( "EnvironmentMetadataProvider found no platform version information (android.os.Build.VERSION.RELEASE). Signal payloads will not be enriched." ) } else { diff --git a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt index 5e78842..df52c7f 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt @@ -12,8 +12,8 @@ import java.lang.ref.WeakReference class SessionProvider: TelemetryProvider, DefaultLifecycleObserver { private var manager: WeakReference? = null - override fun register(ctx: Application?, manager: TelemetryDeckClient) { - this.manager = WeakReference(manager) + override fun register(ctx: Application?, client: TelemetryDeckClient) { + this.manager = WeakReference(client) ProcessLifecycleOwner.get().lifecycle.addObserver(this) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt index 769e007..63b95e4 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt @@ -10,7 +10,7 @@ interface TelemetryProvider { * Registers the provider with the telemetry manager. * The provider keeps a weak reference to telemetry manager in order to queue or send signals. */ - fun register(ctx: Application?, manager: TelemetryDeckClient) + fun register(ctx: Application?, client: TelemetryDeckClient) /** * Calling stop deactivates the provider and prevents future signals from being sent. diff --git a/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt index f858004..eed6f3d 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt @@ -43,7 +43,7 @@ class SignalsUnitTest { val decodedSignal = Json.decodeFromString(signalJson) // date equality comparison with precision up to milliseconds - assertEquals(receivedDate.time, decodedSignal.receivedAt?.time) + assertEquals(receivedDate.time, decodedSignal.receivedAt.time) } @Test diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt b/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt index 8126992..01431e9 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt @@ -4,7 +4,7 @@ import android.app.Application open class TestProvider : TelemetryProvider { var registered = false - override fun register(ctx: Application?, manager: TelemetryDeckClient) { + override fun register(ctx: Application?, client: TelemetryDeckClient) { registered = true } From 2f989790667245c82cf098bc1ff9125fc06f44f8 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 3 Oct 2024 11:57:43 +0200 Subject: [PATCH 05/33] feat: add github action for running unit tests in the library --- .github/workflows/tests.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..d880e72 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,27 @@ +name: Run Tests + +on: + push: +# branches: +# - main + pull_request: + +jobs: + test: + name: Test Kotlin SDK for TelemetryDeck + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Upload Artifacts + run: ./gradlew lib:test \ No newline at end of file From 3998e872287983b7b1a72b892081211dcbcb371d Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 3 Oct 2024 12:07:45 +0200 Subject: [PATCH 06/33] fix: improve serialization test --- .github/workflows/tests.yml | 7 ++++++- .../test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt | 10 +++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d880e72..20e298c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,6 +6,11 @@ on: # - main pull_request: +# prevent concurrent builds from running at the same time +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: name: Test Kotlin SDK for TelemetryDeck @@ -23,5 +28,5 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Upload Artifacts + - name: Run test run: ./gradlew lib:test \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt index eed6f3d..3be1865 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt @@ -1,16 +1,12 @@ package com.telemetrydeck.sdk -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.junit.Assert.assertEquals import org.junit.Test import java.net.URL -import java.util.* - -// TODO: Local cache -// TODO: BUG USE COPY for data classes -// TODO: Detect app start from lifecycle instead of first activity +import java.util.Date +import java.util.UUID /** * Example local unit test, which will execute on the development machine (host). @@ -43,7 +39,7 @@ class SignalsUnitTest { val decodedSignal = Json.decodeFromString(signalJson) // date equality comparison with precision up to milliseconds - assertEquals(receivedDate.time, decodedSignal.receivedAt.time) + assertEquals(receivedDate, decodedSignal.receivedAt) } @Test From fbee3dbd6958f57fa90d4ecf5a22b87837a23bf9 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 3 Oct 2024 12:10:17 +0200 Subject: [PATCH 07/33] fix: only run tests during a PR --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20e298c..4b45f2c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,8 +2,8 @@ name: Run Tests on: push: -# branches: -# - main + branches: + - main pull_request: # prevent concurrent builds from running at the same time From dfad719dbce4b5343e703eafba77b900e2df09f4 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 3 Oct 2024 19:19:42 +0200 Subject: [PATCH 08/33] feat: allow providers to be migrated, reduce breaking changes for existing users, improve test coverage --- .../telemetrydeck/sdk/app/FirstFragment.kt | 8 +- .../com/telemetrydeck/sdk/app/MainActivity.kt | 14 +- .../telemetrydeck/sdk/app/SecondFragment.kt | 12 +- .../sdk/AppLifecycleTelemetryProvider.kt | 9 +- .../sdk/EnvironmentMetadataProvider.kt | 9 +- .../sdk/ManifestMetadataReader.kt | 64 ++++++++- .../com/telemetrydeck/sdk/ManifestSettings.kt | 3 +- .../com/telemetrydeck/sdk/SessionProvider.kt | 7 +- .../sdk/TelemetryBroadcastTimer.kt | 4 +- .../com/telemetrydeck/sdk/TelemetryDeck.kt | 131 ++++++++++-------- .../telemetrydeck/sdk/TelemetryDeckClient.kt | 82 ++++++++--- .../sdk/TelemetryDeckInitProvider.kt | 20 ++- .../sdk/TelemetryDeckManifestSettings.kt | 12 ++ .../sdk/TelemetryDeckManifestVersion.kt | 7 + .../sdk/TelemetryDeckProvider.kt | 31 +++++ .../sdk/TelemetryDeckSignalProcessor.kt | 82 +++++++++++ .../com/telemetrydeck/sdk/TelemetryManager.kt | 54 ++++---- .../telemetrydeck/sdk/TelemetryProvider.kt | 3 +- .../providers/EnvironmentParameterProvider.kt | 92 ++++++++++++ .../sdk/providers/SessionActivityProvider.kt | 96 +++++++++++++ .../sdk/providers/SessionAppProvider.kt | 42 ++++++ .../telemetrydeck/sdk/TelemetryDeckTests.kt | 31 +++-- .../com/telemetrydeck/sdk/TestProvider.kt | 5 +- .../sdk/TestTelemetryDeckProvider.kt | 14 ++ .../EnvironmentParameterProviderTest.kt | 41 ++++++ .../sdk/providers/SessionAppProviderTest.kt | 113 +++++++++++++++ 26 files changed, 829 insertions(+), 157 deletions(-) create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckManifestSettings.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckManifestVersion.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckProvider.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckSignalProcessor.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt create mode 100644 lib/src/test/java/com/telemetrydeck/sdk/TestTelemetryDeckProvider.kt create mode 100644 lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt create mode 100644 lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt diff --git a/app/src/main/java/com/telemetrydeck/sdk/app/FirstFragment.kt b/app/src/main/java/com/telemetrydeck/sdk/app/FirstFragment.kt index d7f919f..76371e8 100644 --- a/app/src/main/java/com/telemetrydeck/sdk/app/FirstFragment.kt +++ b/app/src/main/java/com/telemetrydeck/sdk/app/FirstFragment.kt @@ -1,13 +1,13 @@ package com.telemetrydeck.sdk.app import android.os.Bundle -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController -import com.telemetrydeck.sdk.TelemetryManager +import com.telemetrydeck.sdk.TelemetryDeck import com.telemetrydeck.sdk.app.databinding.FragmentFirstBinding import kotlinx.coroutines.launch @@ -25,7 +25,7 @@ class FirstFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { _binding = FragmentFirstBinding.inflate(inflater, container, false) return binding.root @@ -37,7 +37,7 @@ class FirstFragment : Fragment() { binding.buttonFirst.setOnClickListener { lifecycleScope.launch { - TelemetryManager.send("firstButtonPressed") + TelemetryDeck.send("firstButtonPressed") } findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) } diff --git a/app/src/main/java/com/telemetrydeck/sdk/app/MainActivity.kt b/app/src/main/java/com/telemetrydeck/sdk/app/MainActivity.kt index 115af9a..8811d0a 100644 --- a/app/src/main/java/com/telemetrydeck/sdk/app/MainActivity.kt +++ b/app/src/main/java/com/telemetrydeck/sdk/app/MainActivity.kt @@ -9,7 +9,7 @@ import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.navigateUp import androidx.navigation.ui.setupActionBarWithNavController import com.google.android.material.snackbar.Snackbar -import com.telemetrydeck.sdk.TelemetryManager +import com.telemetrydeck.sdk.TelemetryDeck import com.telemetrydeck.sdk.app.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { @@ -20,13 +20,17 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - TelemetryManager.newDefaultUser("It's me :)") -// val builder = TelemetryManager.Builder() +// TelemetryDeck is automatically configured and started based on settings in the app manifest. +// You can also configure it programmatically instead: +// +// val builder = TelemetryDeck.Builder() // .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") // .showDebugLogs(true) // .defaultUser("Person Name") -// -// TelemetryManager.start(application, builder) +// TelemetryDeck.start(application, builder) + + // let's change the user hash + TelemetryDeck.newDefaultUser("It's me :)") binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) diff --git a/app/src/main/java/com/telemetrydeck/sdk/app/SecondFragment.kt b/app/src/main/java/com/telemetrydeck/sdk/app/SecondFragment.kt index 7dd6d9d..295e240 100644 --- a/app/src/main/java/com/telemetrydeck/sdk/app/SecondFragment.kt +++ b/app/src/main/java/com/telemetrydeck/sdk/app/SecondFragment.kt @@ -1,13 +1,13 @@ package com.telemetrydeck.sdk.app import android.os.Bundle -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController -import com.telemetrydeck.sdk.TelemetryManager +import com.telemetrydeck.sdk.TelemetryDeck import com.telemetrydeck.sdk.app.databinding.FragmentSecondBinding import kotlinx.coroutines.launch @@ -25,19 +25,17 @@ class SecondFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - + ): View { _binding = FragmentSecondBinding.inflate(inflater, container, false) return binding.root - } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - TelemetryManager.stop() + TelemetryDeck.stop() binding.buttonSecond.setOnClickListener { lifecycleScope.launch { - TelemetryManager.send("secondButtonPressed") + TelemetryDeck.send("secondButtonPressed") } findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt index b9436cd..a745837 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt @@ -11,15 +11,16 @@ import java.lang.ref.WeakReference /** * Emits signals for application and activity lifecycle events. */ +@Deprecated("Use SessionAppProvider", ReplaceWith("SessionAppProvider", "com.telemetrydeck.sdk.providers.SessionAppProvider")) class AppLifecycleTelemetryProvider : TelemetryProvider, Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { - private var manager: WeakReference? = null + private var manager: WeakReference? = null - override fun register(ctx: Application?, client: TelemetryDeckClient) { + override fun register(ctx: Application?, manager: TelemetryManager) { if (ctx == null) { - this.manager?.get()?.debugLogger?.error("AppLifecycleTelemetryProvider requires a context but received null. No signals will be sent.") + this.manager?.get()?.logger?.error("AppLifecycleTelemetryProvider requires a context but received null. No signals will be sent.") } - this.manager = WeakReference(client) + this.manager = WeakReference(manager) ProcessLifecycleOwner.get().lifecycle.addObserver(this) ctx?.registerActivityLifecycleCallbacks(this) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt index cd5f2bb..bd4f36b 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt @@ -7,11 +7,12 @@ import java.util.* /** * Adds environment and device information to outgoing Signals. */ +@Deprecated("Use EnvironmentParameterProvider", ReplaceWith("EnvironmentParameterProvider", "com.telemetrydeck.sdk.providers.EnvironmentParameterProvider")) class EnvironmentMetadataProvider : TelemetryProvider { private var enabled: Boolean = true private var metadata = mutableMapOf() - override fun register(ctx: Application?, client: TelemetryDeckClient) { + override fun register(ctx: Application?, manager: TelemetryManager) { if (ctx != null) { val appVersion = ManifestMetadataReader.getAppVersion(ctx) if (!appVersion.isNullOrEmpty()) { @@ -21,10 +22,10 @@ class EnvironmentMetadataProvider : TelemetryProvider { metadata["buildNumber"] = buildNumber.toString() } } else { - client.debugLogger?.error("EnvironmentMetadataProvider requires a context but received null. Signals will contain incomplete metadata.") + manager.logger?.error("EnvironmentMetadataProvider requires a context but received null. Signals will contain incomplete metadata.") } if (android.os.Build.VERSION.RELEASE.isNullOrEmpty()) { - client.debugLogger?.error( + manager.logger?.error( "EnvironmentMetadataProvider found no platform version information (android.os.Build.VERSION.RELEASE). Signal payloads will not be enriched." ) } else { @@ -76,4 +77,4 @@ class EnvironmentMetadataProvider : TelemetryProvider { } return signalPayload } -} +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt b/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt index 5af0948..ae105fa 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt @@ -9,13 +9,27 @@ import androidx.core.content.pm.PackageInfoCompat import java.net.URL import java.util.* + + +internal data class ManifestMetadata(val config: TelemetryManagerConfiguration, val version: TelemetryDeckManifestVersion) + internal class ManifestMetadataReader { companion object { - fun getConfigurationFromManifest(context: Context): TelemetryManagerConfiguration? { + fun getConfigurationFromManifest(context: Context): ManifestMetadata? { + // check if manifest configuration is available + // also determine if we're in post-grand rename mode or still sending older deprecated signals val bundle = getMetaData(context) if (bundle != null) { - return getConfigurationFromManifest(context, bundle) + val v1Config = getConfigurationFromManifest(context, bundle) + if (v1Config != null) { + return ManifestMetadata(v1Config, TelemetryDeckManifestVersion.V1) + } + + val config = getGrandRenameConfigurationFromManifest(context, bundle) + if (config != null) { + return ManifestMetadata(config, TelemetryDeckManifestVersion.V2) + } } return null } @@ -47,7 +61,51 @@ internal class ManifestMetadataReader { /** * Creates an instance of TelemetryManagerConfiguration by reading the manifest. - * + * This method is to be used after the grand rename. + */ + private fun getGrandRenameConfigurationFromManifest(context: Context, bundle: Bundle): TelemetryManagerConfiguration? { + val appID = bundle.getString(TelemetryDeckManifestSettings.AppID.key) ?: return null + val config = TelemetryManagerConfiguration(appID) + + if (bundle.containsKey(TelemetryDeckManifestSettings.ShowDebugLogs.key)) { + config.showDebugLogs = bundle.getBoolean(TelemetryDeckManifestSettings.ShowDebugLogs.key) + } + + val apiBaseUrl = bundle.getString(TelemetryDeckManifestSettings.ApiBaseURL.key) + if (apiBaseUrl != null) { + config.apiBaseURL = URL(apiBaseUrl) + } + + if (bundle.containsKey(TelemetryDeckManifestSettings.SendNewSessionBeganSignal.key)) { + config.sendNewSessionBeganSignal = bundle.getBoolean(TelemetryDeckManifestSettings.SendNewSessionBeganSignal.key) + } + + val sessionID = bundle.getString(TelemetryDeckManifestSettings.SessionID.key) + if (sessionID != null) { + config.sessionID = UUID.fromString(sessionID) + } + + if (bundle.containsKey(TelemetryDeckManifestSettings.TestMode.key)) { + config.testMode = bundle.getBoolean(TelemetryDeckManifestSettings.TestMode.key) + } else { + config.testMode = 0 != (context.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_DEBUGGABLE + } + + val defaultUser = bundle.getString(TelemetryDeckManifestSettings.DefaultUser.key) + if(defaultUser != null) { + config.defaultUser = defaultUser + } + + val salt = bundle.getString(TelemetryDeckManifestSettings.Salt.key) + if(salt != null) { + config.salt = salt + } + + return config + } + + /** + * Creates an instance of TelemetryManagerConfiguration by reading the manifest. */ private fun getConfigurationFromManifest(context: Context, bundle: Bundle): TelemetryManagerConfiguration? { val appID = bundle.getString(ManifestSettings.AppID.key) ?: return null diff --git a/lib/src/main/java/com/telemetrydeck/sdk/ManifestSettings.kt b/lib/src/main/java/com/telemetrydeck/sdk/ManifestSettings.kt index f012ab6..ea0d93a 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/ManifestSettings.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/ManifestSettings.kt @@ -9,4 +9,5 @@ internal enum class ManifestSettings(val key: String) { TestMode("com.telemetrydeck.sdk.testMode"), DefaultUser("com.telemetrydeck.sdk.defaultUser"), Salt("com.telemetrydeck.sdk.salt"), -} \ No newline at end of file +} + diff --git a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt index df52c7f..6f1e347 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt @@ -9,11 +9,12 @@ import java.lang.ref.WeakReference /** * Monitors the app lifecycle in order to broadcast the NewSessionBegan signal. */ +@Deprecated("Use SessionActivityProvider", ReplaceWith("SessionActivityProvider", "com.telemetrydeck.sdk.providers.SessionActivityProvider")) class SessionProvider: TelemetryProvider, DefaultLifecycleObserver { - private var manager: WeakReference? = null + private var manager: WeakReference? = null - override fun register(ctx: Application?, client: TelemetryDeckClient) { - this.manager = WeakReference(client) + override fun register(ctx: Application?, manager: TelemetryManager) { + this.manager = WeakReference(manager) ProcessLifecycleOwner.get().lifecycle.addObserver(this) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt index a53f7e5..5b6b2c5 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.channels.ticker import java.lang.ref.WeakReference -internal class TelemetryBroadcastTimer(private val manager: WeakReference, debugLogger: WeakReference) { +internal class TelemetryBroadcastTimer(private val manager: WeakReference, debugLogger: WeakReference) { // broadcast begins with a 10s delay after initialization and fires every 10s. private val timerChannel = ticker(delayMillis = 10_000, initialDelayMillis = 10_000) @@ -40,7 +40,7 @@ internal class TelemetryBroadcastTimer(private val manager: WeakReference = listOf( - AppLifecycleTelemetryProvider() - + val providers: List = listOf( + SessionAppProvider() ) -): TelemetryDeckClient { +): TelemetryDeckClient, TelemetryDeckSignalProcessor { var cache: SignalCache? = null var logger: DebugLogger? = null private val navigationStatus: NavigationStatus = MemoryNavigationStatus() @@ -35,22 +37,6 @@ class TelemetryDeck( this.configuration.defaultUser = user } - override fun queue( - signalType: String, - clientUser: String?, - additionalPayload: Map - ) { - cache?.add(createSignal(signalType, clientUser, additionalPayload)) - } - - override fun queue( - signalType: SignalType, - clientUser: String?, - additionalPayload: Map - ) { - queue(signalType.type, clientUser, additionalPayload) - } - override fun navigate(sourcePath: String, destinationPath: String, clientUser: String?) { navigationStatus.applyDestination(destinationPath) @@ -61,7 +47,7 @@ class TelemetryDeck( PayloadParameters.TelemetryDeckNavigationDestinationPath.type to destinationPath ) - queue(SignalType.TelemetryDeckNavigationPathChanged, clientUser, payload) + signal(SignalType.TelemetryDeckNavigationPathChanged.type, params = payload, customUserID = clientUser) } override fun navigate(destinationPath: String, clientUser: String?) { @@ -84,17 +70,40 @@ class TelemetryDeck( return send(signalType.type, clientUser, additionalPayload) } - override suspend fun signal(signals: List): Result { + override suspend fun sendAll(signals: List): Result { return send(signals) } - suspend fun send( + override fun signal( + signalName: String, + params: Map, + floatValue: Double?, + customUserID: String? + ) { + // TODO: floatValue + cache?.add(createSignal(signalType = signalName, clientUser = customUserID, additionalPayload = params)) + } + + override fun signal(signalName: String, customUserID: String?, params: Map) { + cache?.add(createSignal(signalType = signalName, clientUser = customUserID, additionalPayload = params)) + } + + override fun signal( + signalType: SignalType, + params: Map, + floatValue: Double?, + customUserID: String? + ) { + cache?.add(createSignal(signalType = signalType.type, clientUser = customUserID, additionalPayload = params)) + } + + private suspend fun send( signal: Signal ): Result { return send(listOf(signal)) } - suspend fun send( + private suspend fun send( signals: List ): Result { return try { @@ -107,7 +116,7 @@ class TelemetryDeck( client.send(signals) success(Unit) } catch (e: Exception) { - logger?.error("Failed to send signals due to an error ${e} ${e.stackTraceToString()}") + logger?.error("Failed to send signals due to an error $e ${e.stackTraceToString()}") failure(e) } } @@ -133,7 +142,7 @@ class TelemetryDeck( val userValue = clientUser ?: configuration.defaultUser ?: "" val userValueWithSalt = userValue + (configuration.salt ?: "") - val hashedUser = hashString(userValueWithSalt, "SHA-256") + val hashedUser = hashString(userValueWithSalt) val payload = SignalPayload(additionalPayload = enrichedPayload) val signal = Signal( @@ -148,18 +157,18 @@ class TelemetryDeck( return signal } - private fun hashString(input: String, algorithm: String): String { + private fun hashString(input: String, algorithm: String = "SHA-256"): String { return MessageDigest.getInstance(algorithm) .digest(input.toByteArray()) - .fold("", { str, it -> str + "%02x".format(it) }) + .fold("") { str, it -> str + "%02x".format(it) } } companion object : TelemetryDeckClient { - internal val defaultTelemetryProviders: List + internal val defaultTelemetryProviders: List get() = listOf( - SessionProvider(), - AppLifecycleTelemetryProvider(), - EnvironmentMetadataProvider() + SessionActivityProvider(), + SessionAppProvider(), + EnvironmentParameterProvider() ) // TelemetryManager singleton @@ -220,22 +229,6 @@ class TelemetryDeck( getInstance()?.newDefaultUser(user) } - override fun queue( - signalType: String, - clientUser: String?, - additionalPayload: Map - ) { - getInstance()?.queue(signalType, clientUser, additionalPayload) - } - - override fun queue( - signalType: SignalType, - clientUser: String?, - additionalPayload: Map - ) { - getInstance()?.queue(signalType, clientUser, additionalPayload) - } - override fun navigate(sourcePath: String, destinationPath: String, clientUser: String?) { getInstance()?.navigate(sourcePath, destinationPath, clientUser = clientUser) } @@ -268,14 +261,40 @@ class TelemetryDeck( return failure(NullPointerException()) } - override suspend fun signal(signals: List): Result { - val result = getInstance()?.signal(signals) + override suspend fun sendAll(signals: List): Result { + val result = getInstance()?.sendAll(signals) if (result != null) { return result } return failure(NullPointerException()) } + override fun signal( + signalName: String, + params: Map, + floatValue: Double?, + customUserID: String? + ) { + getInstance()?.signal(signalName, params, floatValue, customUserID) + } + + override fun signal( + signalName: String, + customUserID: String?, + params: Map + ) { + getInstance()?.signal(signalName = signalName, customUserID = customUserID, params = params) + } + + override fun signal( + signalType: SignalType, + params: Map, + floatValue: Double?, + customUserID: String? + ) { + getInstance()?.signal(signalType.type, params, floatValue, customUserID) + } + override val signalCache: SignalCache? get() = getInstance()?.signalCache @@ -289,8 +308,8 @@ class TelemetryDeck( data class Builder( private var configuration: TelemetryManagerConfiguration? = null, - private var providers: List? = null, - private var additionalProviders: MutableList? = null, + private var providers: List? = null, + private var additionalProviders: MutableList? = null, private var appID: UUID? = null, private var defaultUser: String? = null, private var sessionID: UUID? = null, @@ -313,13 +332,13 @@ class TelemetryDeck( /** * Override the default set of TelemetryProviders. */ - fun providers(providerList: List) = + fun providers(providerList: List) = apply { this.providers = providerList } /** - * Append a custom TelemetryProvider which can produce or enrich signals + * Append a custom [TelemetryDeckProvider] which can produce or enrich signals */ - fun addProvider(provider: TelemetryProvider) = apply { + fun addProvider(provider: TelemetryDeckProvider) = apply { if (additionalProviders == null) { additionalProviders = mutableListOf() } @@ -367,7 +386,7 @@ class TelemetryDeck( } /** - * Provide a custom logger implementation to be used by TelemetryManager. + * Provide a custom logger implementation to be used by [TelemetryDeck] when logging internal messages. */ fun logger(debugLogger: DebugLogger?) = apply { this.logger = debugLogger diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt index 1a58fef..fbc9ae4 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt @@ -19,25 +19,6 @@ interface TelemetryDeckClient { fun newDefaultUser(user: String?) - /** - * Queue a signal to be send as soon as possible - */ - fun queue( - signalType: String, - clientUser: String? = null, - additionalPayload: Map = emptyMap() - ) - - - /** - * Queue a signal to be send as soon as possible - */ - fun queue( - signalType: SignalType, - clientUser: String? = null, - additionalPayload: Map = emptyMap() - ) - /** * Send a signal that represents a navigation event with a source and a destination. * @@ -64,7 +45,7 @@ interface TelemetryDeckClient { /** - * Send a signal immediately + * Send a signal immediately. */ suspend fun send( signalType: SignalType, @@ -76,7 +57,65 @@ interface TelemetryDeckClient { /** * Send multiple signals immediately. */ - suspend fun signal(signals: List): Result + suspend fun sendAll(signals: List): Result + + + fun signal( + signalType: SignalType, + params: Map = emptyMap(), + floatValue: Double? = null, + customUserID: String? = null + ) + + /** + * Sends a telemetry signal with optional parameters to TelemetryDeck. + * + * + * Signals are first queued in cache (see [SignalCache]) before being sent to the server. + * In case of failure, we will try sending again approximately every 10 seconds while the app is running. + * + * When running in the context of an application, the signal cache is written to a local file so signals are saved when the app restarts (see [PersistentSignalCache]). + * When running without a context, the signal cache is stored in memory. All cached (unsent) signals are discarded when the TelemetryDeck SDK instance has been disposed (see [MemorySignalCache]). + * + * + * If you prefer to control the lifecycle of signals, use the [send] method instead. + * + * @param signalName The name of the signal to be sent. This is a string that identifies the type of event or action being reported. + * @param params A map of additional string key-value pairs that provide further context about the signal. + * @param floatValue An optional floating-point number that can be used to provide numerical data about the signal. + * @param customUserID An optional string specifying a custom user identifier. If provided, it will override the default user identifier from the configuration. + * + */ + fun signal( + signalName: String, + params: Map = emptyMap(), + floatValue: Double? = null, + customUserID: String? = null, + ) + + /** + * Sends a telemetry signal with optional parameters to TelemetryDeck. + * + * + * Signals are first queued in cache (see [SignalCache]) before being sent to the server. + * In case of failure, we will try sending again approximately every 10 seconds while the app is running. + * + * When running in the context of an application, the signal cache is written to a local file so signals are saved when the app restarts (see [PersistentSignalCache]). + * When running without a context, the signal cache is stored in memory. All cached (unsent) signals are discarded when the TelemetryDeck SDK instance has been disposed (see [MemorySignalCache]). + * + * + * If you prefer to control the lifecycle of signals, use the [send] method instead. + * + * @param signalName The name of the signal to be sent. This is a string that identifies the type of event or action being reported. + * @param customUserID An optional string specifying a custom user identifier. If provided, it will override the default user identifier from the configuration. + * @param params A map of additional string key-value pairs that provide further context about the signal. + * + */ + fun signal( + signalName: String, + customUserID: String? = null, + params: Map = emptyMap(), + ) val signalCache: SignalCache? @@ -84,4 +123,5 @@ interface TelemetryDeckClient { val debugLogger: DebugLogger? val configuration: TelemetryManagerConfiguration? + } \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckInitProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckInitProvider.kt index 4b975fd..9906954 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckInitProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckInitProvider.kt @@ -19,8 +19,8 @@ class TelemetryDeckInitProvider : ContentProvider() { } try { - val config = ManifestMetadataReader.getConfigurationFromManifest(appContext) - if (config == null) { + val metadata = ManifestMetadataReader.getConfigurationFromManifest(appContext) + if (metadata == null) { Log.e( tag, "No valid TelemetryDeck SDK configuration found in application manifest." @@ -28,9 +28,19 @@ class TelemetryDeckInitProvider : ContentProvider() { return false } - val builder = TelemetryManager.Builder() - builder.configuration(config) - TelemetryManager.start(appContext, builder) + when (metadata.version) { + TelemetryDeckManifestVersion.V1 -> { + val builder = TelemetryManager.Builder() + builder.configuration(metadata.config) + TelemetryManager.start(appContext, builder) + } + TelemetryDeckManifestVersion.V2 -> { + val builder = TelemetryDeck.Builder() + builder.configuration(metadata.config) + TelemetryDeck.start(appContext, builder) + } + } + } catch (e: Exception) { Log.e(tag, "Failed to parse TelemetryDeck SDK configuration:", e) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckManifestSettings.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckManifestSettings.kt new file mode 100644 index 0000000..3e62483 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckManifestSettings.kt @@ -0,0 +1,12 @@ +package com.telemetrydeck.sdk + +internal enum class TelemetryDeckManifestSettings(val key: String) { + AppID("com.telemetrydeck.appID"), + ShowDebugLogs("com.telemetrydeck.showDebugLogs"), + ApiBaseURL("com.telemetrydeck.apiBaseURL"), + SendNewSessionBeganSignal("com.telemetrydeck.sendNewSessionBeganSignal"), + SessionID("com.telemetrydeck.sessionID"), + TestMode("com.telemetrydeck.testMode"), + DefaultUser("com.telemetrydeck.defaultUser"), + Salt("com.telemetrydeck.salt"), +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckManifestVersion.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckManifestVersion.kt new file mode 100644 index 0000000..13f8afb --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckManifestVersion.kt @@ -0,0 +1,7 @@ +package com.telemetrydeck.sdk + +internal enum class TelemetryDeckManifestVersion { + // The manifest corresponds to configuration before the grand-rename + V1, + V2 +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckProvider.kt new file mode 100644 index 0000000..6e5f0aa --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckProvider.kt @@ -0,0 +1,31 @@ +package com.telemetrydeck.sdk + +import android.app.Application + +/** + * Generic interface for plugins which can enrich Signals + */ +interface TelemetryDeckProvider { + /** + * Registers the provider with the telemetry manager. + * The provider keeps a weak reference to telemetry manager in order to queue or send signals. + */ + fun register(ctx: Application?, client: TelemetryDeckClient) + + /** + * Calling stop deactivates the provider and prevents future signals from being sent. + */ + fun stop() + + /** + * A provider can override this method in order to append or remove telemetry metadata from Signals + * before they are enqueued for broadcast. + * + * TelemetryManager calls this method all providers in order of registration. + */ + fun enrich(signalType: String, + clientUser: String? = null, + additionalPayload: Map = emptyMap()): Map { + return additionalPayload + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckSignalProcessor.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckSignalProcessor.kt new file mode 100644 index 0000000..a567353 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckSignalProcessor.kt @@ -0,0 +1,82 @@ +package com.telemetrydeck.sdk + +import java.util.UUID + +interface TelemetryDeckSignalProcessor { + val signalCache: SignalCache? + + val debugLogger: DebugLogger? + + suspend fun sendAll(signals: List): Result +} + +interface TelemetryManagerSignals { + + /** + * All future signals belong to a new session. + * + * Calling this method sets a new SessionID for new Signals. Previously queued signals are not affected. + */ + fun newSession(sessionID: UUID = UUID.randomUUID()) + + + /** + * Set the default user for future signals + * + */ + fun newDefaultUser(user: String?) + + + /** + * Queue a signal to be send as soon as possible + */ + fun queue( + signalType: String, + clientUser: String? = null, + additionalPayload: Map = emptyMap() + ) + + + /** + * Queue a signal to be send as soon as possible + */ + fun queue( + signalType: SignalType, + clientUser: String? = null, + additionalPayload: Map = emptyMap() + ) + + /** + * Send a signal that represents a navigation event with a source and a destination. + * + * @see Navigation Signals + * */ + fun navigate(sourcePath: String, destinationPath: String, clientUser: String? = null) + + /** + * Send a signal that represents a navigation event with a destination and a default source. + * + * @see Navigation Signals + * */ + fun navigate(destinationPath: String, clientUser: String? = null) + + + /** + * Send a signal immediately + */ + suspend fun send( + signalType: String, + clientUser: String? = null, + additionalPayload: Map = emptyMap() + ): Result + + + /** + * Send a signal immediately + */ + suspend fun send( + signalType: SignalType, + clientUser: String? = null, + additionalPayload: Map = emptyMap() + ): Result +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt index ddc8e27..ebccba6 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt @@ -10,13 +10,13 @@ import java.util.UUID import kotlin.Result.Companion.failure import kotlin.Result.Companion.success -@Deprecated("Use TelemetryDeck instead", ReplaceWith("TelemetryDeck")) +@Deprecated("Use TelemetryDeck instead", ReplaceWith("TelemetryDeck", "com.telemetrydeck.sdk.TelemetryDeck")) class TelemetryManager( - override val configuration: TelemetryManagerConfiguration, + val configuration: TelemetryManagerConfiguration, val providers: List = listOf( AppLifecycleTelemetryProvider() - ), -) : TelemetryDeckClient { + ) +) : TelemetryManagerSignals, TelemetryDeckSignalProcessor { var cache: SignalCache? = null var logger: DebugLogger? = null @@ -28,6 +28,24 @@ class TelemetryManager( override val debugLogger: DebugLogger? get() = this.logger + override suspend fun sendAll( + signals: List + ): Result { + return try { + val client = TelemetryClient( + configuration.telemetryAppID, + configuration.apiBaseURL, + configuration.showDebugLogs, + logger + ) + client.send(signals) + success(Unit) + } catch (e: Exception) { + logger?.error("Failed to send signals due to an error ${e} ${e.stackTraceToString()}") + failure(e) + } + } + override fun newSession(sessionID: UUID) { this.configuration.sessionID = sessionID } @@ -85,10 +103,6 @@ class TelemetryManager( return send(signalType.type, clientUser, additionalPayload) } - override suspend fun signal(signals: List): Result { - return send(signals) - } - suspend fun send( signal: Signal ): Result { @@ -135,7 +149,7 @@ class TelemetryManager( val userValueWithSalt = userValue + (configuration.salt ?: "") val hashedUser = hashString(userValueWithSalt, "SHA-256") - + val payload = SignalPayload(additionalPayload = enrichedPayload) val signal = Signal( appID = configuration.telemetryAppID, @@ -155,7 +169,7 @@ class TelemetryManager( .fold("", { str, it -> str + "%02x".format(it) }) } - companion object : TelemetryDeckClient { + companion object : TelemetryManagerSignals { internal val defaultTelemetryProviders: List get() = listOf( SessionProvider(), @@ -268,27 +282,9 @@ class TelemetryManager( } return failure(NullPointerException()) } - - override suspend fun signal(signals: List): Result { - val result = getInstance()?.signal(signals) - if (result != null) { - return result - } - return failure(NullPointerException()) - } - - override val signalCache: SignalCache? - get() = getInstance()?.signalCache - - override val debugLogger: DebugLogger? - get() = getInstance()?.debugLogger - - override val configuration: TelemetryManagerConfiguration? - get() = getInstance()?.configuration } - @Deprecated("Use TelemetryDeck.Builder instead", ReplaceWith("TelemetryDeck.Builder")) data class Builder( private var configuration: TelemetryManagerConfiguration? = null, private var providers: List? = null, @@ -369,7 +365,7 @@ class TelemetryManager( } /** - * Provide a custom logger implementation to be used by TelemetryManager. + * Provide a custom logger implementation to be used by [TelemetryManager]. */ fun logger(debugLogger: DebugLogger?) = apply { this.logger = debugLogger diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt index 63b95e4..9d75c67 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt @@ -5,12 +5,13 @@ import android.app.Application /** * Generic interface for plugins which can create Signals */ +@Deprecated("Use TelemetryDeckProvider", ReplaceWith("TelemetryDeckProvider")) interface TelemetryProvider { /** * Registers the provider with the telemetry manager. * The provider keeps a weak reference to telemetry manager in order to queue or send signals. */ - fun register(ctx: Application?, client: TelemetryDeckClient) + fun register(ctx: Application?, manager: TelemetryManager) /** * Calling stop deactivates the provider and prevents future signals from being sent. diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt new file mode 100644 index 0000000..0699e53 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt @@ -0,0 +1,92 @@ +package com.telemetrydeck.sdk.providers + +import android.app.Application +import android.icu.util.VersionInfo +import com.telemetrydeck.sdk.BuildConfig +import com.telemetrydeck.sdk.ManifestMetadataReader +import com.telemetrydeck.sdk.TelemetryDeckClient +import com.telemetrydeck.sdk.TelemetryDeckProvider +import java.lang.ref.WeakReference +import java.util.Locale + +/** + * This provider enriches outgoing signals with additional parameters describing the current environment. + * + * - information about the specific app build, such as version, build number, or SDKs compiled with. + * - information about the device running the application, such as operating system, model name, or architecture. + * - information about the TelemetryDeck SDK, such as its name or version number. + */ +class EnvironmentParameterProvider : TelemetryDeckProvider { + private var enabled: Boolean = true + private var manager: WeakReference? = null + private var metadata = mutableMapOf() + + override fun register(ctx: Application?, client: TelemetryDeckClient) { + this.manager = WeakReference(client) + + if (ctx != null) { + val appVersion = ManifestMetadataReader.getAppVersion(ctx) + if (!appVersion.isNullOrEmpty()) { + metadata["appVersion"] = appVersion + } + ManifestMetadataReader.getBuildNumber(ctx)?.let { buildNumber -> + metadata["buildNumber"] = buildNumber.toString() + } + } else { + this.manager?.get()?.debugLogger?.error("EnvironmentParameterProvider requires a context but received null. Signals will contain incomplete metadata.") + } + + if (android.os.Build.VERSION.RELEASE.isNullOrEmpty()) { + this.manager?.get()?.debugLogger?.error( + "EnvironmentMetadataProvider found no platform version information (android.os.Build.VERSION.RELEASE). Signal payloads will not be enriched." + ) + } else { + val release = android.os.Build.VERSION.RELEASE + val sdkVersion = android.os.Build.VERSION.SDK_INT + metadata["systemVersion"] = "Android SDK: $sdkVersion ($release)" + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + val versionInfo = VersionInfo.getInstance(release) + metadata["majorSystemVersion"] = versionInfo.major.toString() + metadata["majorMinorSystemVersion"] = "${versionInfo.major}.${versionInfo.minor}" + } else { + val versionInfo = release.split(".") + metadata["majorSystemVersion"] = versionInfo.elementAtOrNull(0) ?: "0" + metadata["majorMinorSystemVersion"] = "${versionInfo.elementAtOrNull(0) ?: "0"}.${versionInfo.elementAtOrNull(1) ?: "0"}" + } + } + + metadata["locale"] = Locale.getDefault().displayName + if (android.os.Build.BRAND != null) { + metadata["brand"] = android.os.Build.BRAND + } + if (android.os.Build.DEVICE != null) { + metadata["targetEnvironment"] = android.os.Build.DEVICE + } + if (android.os.Build.MODEL != null && android.os.Build.PRODUCT != null) { + metadata["modelName"] = "${android.os.Build.MODEL} (${android.os.Build.PRODUCT})" + } + metadata["architecture"] = System.getProperty("os.arch") ?: "" + metadata["operatingSystem"] = "Android" + metadata["telemetryClientVersion"] = BuildConfig.LIBRARY_PACKAGE_NAME + this.enabled = true + } + + override fun stop() { + this.enabled = false + } + + override fun enrich( + signalType: String, + clientUser: String?, + additionalPayload: Map + ): Map { + val signalPayload = additionalPayload.toMutableMap() + for (item in metadata) { + if (!signalPayload.containsKey(item.key)) { + signalPayload[item.key] = item.value + } + } + return signalPayload + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt new file mode 100644 index 0000000..ffacc34 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt @@ -0,0 +1,96 @@ +package com.telemetrydeck.sdk.providers + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner +import com.telemetrydeck.sdk.SignalType +import com.telemetrydeck.sdk.TelemetryDeckClient +import com.telemetrydeck.sdk.TelemetryDeckProvider +import java.lang.ref.WeakReference + +/** + * Emits signals for application and activity lifecycle events. + */ +class SessionActivityProvider: TelemetryDeckProvider, + Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { + private var manager: WeakReference? = null + + override fun register(ctx: Application?, client: TelemetryDeckClient) { + this.manager = WeakReference(client) + if (ctx == null) { + this.manager?.get()?.debugLogger?.error("AppLifecycleTelemetryProvider requires a context but received null. No signals will be sent.") + } + ProcessLifecycleOwner.get().lifecycle.addObserver(this) + ctx?.registerActivityLifecycleCallbacks(this) + } + + override fun stop() { + ProcessLifecycleOwner.get().lifecycle.removeObserver(this) + manager?.clear() + manager = null + } + + override fun onActivityCreated(p0: Activity, p1: Bundle?) { + manager?.get()?.signal( + SignalType.ActivityCreated, + mapOf("activity" to p0.localClassName) + ) + } + + override fun onActivityStarted(p0: Activity) { + manager?.get()?.signal( + SignalType.ActivityStarted, + mapOf("activity" to p0.localClassName) + ) + } + + override fun onActivityResumed(p0: Activity) { + manager?.get()?.signal( + SignalType.ActivityResumed, + mapOf("activity" to p0.localClassName) + ) + } + + override fun onActivityPaused(p0: Activity) { + manager?.get()?.signal( + SignalType.ActivityPaused, + mapOf("activity" to p0.localClassName) + ) + } + + override fun onActivityStopped(p0: Activity) { + manager?.get()?.signal( + SignalType.ActivityStopped, + mapOf("activity" to p0.localClassName) + ) + } + + override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) { + manager?.get()?.signal( + SignalType.ActivitySaveInstanceState, + mapOf("activity" to p0.localClassName) + ) + } + + override fun onActivityDestroyed(p0: Activity) { + manager?.get()?.signal( + SignalType.ActivityDestroyed, + mapOf("activity" to p0.localClassName) + ) + } + + override fun onStart(owner: LifecycleOwner) { + manager?.get()?.signal( + SignalType.AppForeground + ) + } + + override fun onStop(owner: LifecycleOwner) { + manager?.get()?.signal( + SignalType.AppBackground + ) + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt new file mode 100644 index 0000000..97a1dc3 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt @@ -0,0 +1,42 @@ +package com.telemetrydeck.sdk.providers + + +import android.app.Application +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner +import com.telemetrydeck.sdk.SignalType +import com.telemetrydeck.sdk.TelemetryDeckClient +import com.telemetrydeck.sdk.TelemetryDeckProvider +import java.lang.ref.WeakReference + +/** + * Monitors the app lifecycle in order to broadcast the NewSessionBegan signal. + */ +class SessionAppProvider: TelemetryDeckProvider, DefaultLifecycleObserver { + private var manager: WeakReference? = null + + override fun register(ctx: Application?, client: TelemetryDeckClient) { + this.manager = WeakReference(client) + ProcessLifecycleOwner.get().lifecycle.addObserver(this) + } + + override fun stop() { + ProcessLifecycleOwner.get().lifecycle.removeObserver(this) + } + + override fun onStart(owner: LifecycleOwner) { + if (manager?.get()?.configuration?.sendNewSessionBeganSignal == true) { + manager?.get()?.signal( + SignalType.NewSessionBegan + ) + } + } + + override fun onStop(owner: LifecycleOwner) { + if (manager?.get()?.configuration?.sendNewSessionBeganSignal == true) { + // app is going into the background, reset the sessionID + manager?.get()?.newSession() + } + } +} \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt index 90ba6fa..8396609 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt @@ -22,7 +22,7 @@ class TelemetryDeckTests { val config = TelemetryManagerConfiguration(appID) val manager = TelemetryDeck.Builder().configuration(config).build(null) - manager.queue("type", "clientUser", emptyMap()) + manager.signal("type", "clientUser", emptyMap()) val queuedSignal = manager.cache?.empty()?.first() @@ -43,7 +43,7 @@ class TelemetryDeckTests { val config = TelemetryManagerConfiguration(appID) config.salt = "my salt" val manager = TelemetryDeck.Builder().configuration(config).build(null) - manager.queue("type", "clientUser", emptyMap()) + manager.signal("type", "clientUser", emptyMap()) val queuedSignal = manager.cache?.empty()?.first() Assert.assertEquals("9a68a3790deb1db66f80855b8e7c5a97df8002ef90d3039f9e16c94cfbd11d99", queuedSignal?.clientUser) } @@ -81,10 +81,21 @@ class TelemetryDeckTests { } @Test - fun telemetryDeck_builder_set_baseURL() { + fun telemetryDeck_builder_set_baseURL_From_String() { val sut = TelemetryDeck.Builder() val result = - sut.appID("32CB6574-6732-4238-879F-582FEBEB6536").baseURL("https://telemetrydeck.com") + sut.appID("32CB6574-6732-4238-879F-582FEBEB6536") + .baseURL("https://telemetrydeck.com") + .build(null) + Assert.assertEquals(URL("https://telemetrydeck.com"), result.configuration.apiBaseURL) + } + + @Test + fun telemetryDeck_builder_set_baseURL_FromUrl() { + val sut = TelemetryDeck.Builder() + val result = + sut.appID("32CB6574-6732-4238-879F-582FEBEB6536") + .baseURL(URL("https://telemetrydeck.com")) .build(null) Assert.assertEquals(URL("https://telemetrydeck.com"), result.configuration.apiBaseURL) } @@ -205,7 +216,7 @@ class TelemetryDeckTests { .appID("32CB6574-6732-4238-879F-582FEBEB6536") .testMode(true) .build(null) - sut.queue("type") + sut.signal("type") Assert.assertEquals("true", sut.cache?.empty()?.get(0)?.isTestMode) } @@ -217,7 +228,7 @@ class TelemetryDeckTests { .appID("32CB6574-6732-4238-879F-582FEBEB6536") .testMode(false) .build(null) - sut.queue("type") + sut.signal("type") Assert.assertEquals("false", sut.cache?.empty()?.get(0)?.isTestMode) } @@ -227,17 +238,17 @@ class TelemetryDeckTests { val builder = TelemetryDeck.Builder() val sut = builder .appID("32CB6574-6732-4238-879F-582FEBEB6536") - .addProvider(TestProvider()) + .addProvider(TestTelemetryDeckProvider()) .build(null) - sut.queue("type") + sut.signal("type") Assert.assertEquals(4, sut.providers.count()) - Assert.assertTrue(sut.providers[3] is TestProvider) + Assert.assertTrue(sut.providers[3] is TestTelemetryDeckProvider) } @Test fun telemetryDeck_addProvider_custom_provider_is_registered() { - val provider = TestProvider() + val provider = TestTelemetryDeckProvider() Assert.assertFalse(provider.registered) val builder = TelemetryDeck.Builder() diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt b/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt index 01431e9..254bfc6 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt @@ -4,11 +4,12 @@ import android.app.Application open class TestProvider : TelemetryProvider { var registered = false - override fun register(ctx: Application?, client: TelemetryDeckClient) { + override fun register(ctx: Application?, manager: TelemetryManager) { registered = true } override fun stop() { // } -} \ No newline at end of file +} + diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TestTelemetryDeckProvider.kt b/lib/src/test/java/com/telemetrydeck/sdk/TestTelemetryDeckProvider.kt new file mode 100644 index 0000000..914b937 --- /dev/null +++ b/lib/src/test/java/com/telemetrydeck/sdk/TestTelemetryDeckProvider.kt @@ -0,0 +1,14 @@ +package com.telemetrydeck.sdk + +import android.app.Application + +open class TestTelemetryDeckProvider : TelemetryDeckProvider { + var registered = false + override fun register(ctx: Application?, client: TelemetryDeckClient) { + registered = true + } + + override fun stop() { + // + } +} \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt new file mode 100644 index 0000000..37b167d --- /dev/null +++ b/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt @@ -0,0 +1,41 @@ +package com.telemetrydeck.sdk.providers + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.telemetrydeck.sdk.TelemetryDeck +import com.telemetrydeck.sdk.TelemetryManagerConfiguration +import org.junit.Assert +import org.junit.Rule +import org.junit.Test + +class EnvironmentParameterProviderTest { + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @Test + fun environmentMetadataProvider_sets_client_version() { + val appID = "32CB6574-6732-4238-879F-582FEBEB6536" + val config = TelemetryManagerConfiguration(appID) + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.signal("type", "clientUser", emptyMap()) + + val queuedSignal = manager.cache?.empty()?.first() + + Assert.assertNotNull(queuedSignal) + Assert.assertEquals(queuedSignal?.payload?.contains("telemetryClientVersion:com.telemetrydeck.sdk"), true) + } + + @Test + fun environmentMetadataProvider_allows_properties_to_be_set_in_advance() { + val appID = "32CB6574-6732-4238-879F-582FEBEB6536" + val config = TelemetryManagerConfiguration(appID) + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.signal("type", "clientUser", mapOf("telemetryClientVersion" to "my value")) + + val queuedSignal = manager.cache?.empty()?.first() + + Assert.assertNotNull(queuedSignal) + Assert.assertEquals(queuedSignal?.payload?.contains("telemetryClientVersion:my value"), true) + } +} \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt new file mode 100644 index 0000000..cb33700 --- /dev/null +++ b/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt @@ -0,0 +1,113 @@ +package com.telemetrydeck.sdk.providers + + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.LifecycleOwner +import com.telemetrydeck.sdk.SignalType +import com.telemetrydeck.sdk.TelemetryDeck +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito + +class SessionAppProviderTest { + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + + private fun testDefaultTelemetryManager(): TelemetryDeck { + val builder = TelemetryDeck.Builder() + return builder + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .build(null) + } + + private fun testTelemetryManager(sendNewSessionBeganSignal: Boolean): TelemetryDeck { + val builder = TelemetryDeck.Builder() + return builder + .appID("32CB6574-6732-4238-879F-582FEBEB6536") + .sendNewSessionBeganSignal(sendNewSessionBeganSignal) + .build(null) + } + + @Test + fun sessionProvider_default_configuration_onStart_sends_newSessionBegan() { + val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val sut = SessionAppProvider() + val manager = testDefaultTelemetryManager() + sut.register(null, manager) + + sut.onStart(lifecycleOwner) + + Assert.assertEquals(1, manager.cache?.count()) + Assert.assertEquals(SignalType.NewSessionBegan.type, manager.cache?.empty()?.get(0)?.type) + } + + @Test + fun sessionProvider_sendNewSessionBeganSignal_onStart_sends_newSessionBegan() { + val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val sut = SessionAppProvider() + val manager = testTelemetryManager(true) + sut.register(null, manager) + + sut.onStart(lifecycleOwner) + + Assert.assertEquals(1, manager.cache?.count()) + Assert.assertEquals(SignalType.NewSessionBegan.type, manager.cache?.empty()?.get(0)?.type) + } + + @Test + fun sessionProvider_not_sendNewSessionBeganSignal_onStart_no_signals() { + val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val sut = SessionAppProvider() + val manager = testTelemetryManager(false) + sut.register(null, manager) + + sut.onStart(lifecycleOwner) + + Assert.assertEquals(0, manager.cache?.count()) + } + + + @Test + fun sessionProvider_default_configuration_onStop_resets_the_sessionID() { + val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val sut = SessionAppProvider() + val manager = testDefaultTelemetryManager() + sut.register(null, manager) + + val initialSessionID = manager.configuration.sessionID + sut.onStop(lifecycleOwner) + val nextSessionID = manager.configuration.sessionID + + Assert.assertNotEquals(initialSessionID, nextSessionID) + } + + @Test + fun sessionProvider_sendNewSessionBeganSignal_onStop_resets_the_sessionID() { + val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val sut = SessionAppProvider() + val manager = testTelemetryManager(true) + sut.register(null, manager) + + val initialSessionID = manager.configuration.sessionID + sut.onStop(lifecycleOwner) + val nextSessionID = manager.configuration.sessionID + + Assert.assertNotEquals(initialSessionID, nextSessionID) + } + + @Test + fun sessionProvider_not_sendNewSessionBeganSignal_onStop_keeps_the_sessionID() { + val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val sut = SessionAppProvider() + val manager = testTelemetryManager(false) + sut.register(null, manager) + + val initialSessionID = manager.configuration.sessionID + sut.onStop(lifecycleOwner) + val nextSessionID = manager.configuration.sessionID + + Assert.assertEquals(initialSessionID, nextSessionID) + } +} \ No newline at end of file From 13ca578212a3e9ddca065a1d9cb30ed2251b82b6 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 4 Oct 2024 10:45:59 +0200 Subject: [PATCH 09/33] docs: update readme, fix: adapt default providers to exclude activity events --- README.md | 104 +++++++++++++----- .../com/telemetrydeck/sdk/TelemetryDeck.kt | 1 - 2 files changed, 79 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 248cc8d..8a64456 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ The TelemetryDeck can be initialized automatically by adding the application key ... - + ``` @@ -58,12 +58,12 @@ Replace `XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX` with your TelemetryDeck App ID. In addition, the following optional properties are supported: -- `com.telemetrydeck.sdk.showDebugLogs` -- `com.telemetrydeck.sdk.apiBaseURL` -- `com.telemetrydeck.sdk.sendNewSessionBeganSignal` -- `com.telemetrydeck.sdk.sessionID` -- `com.telemetrydeck.sdk.testMode` -- `com.telemetrydeck.sdk.defaultUser` +- `com.telemetrydeck.showDebugLogs` +- `com.telemetrydeck.apiBaseURL` +- `com.telemetrydeck.sendNewSessionBeganSignal` +- `com.telemetrydeck.sessionID` +- `com.telemetrydeck.testMode` +- `com.telemetrydeck.defaultUser` ### Programmatic Usage @@ -89,24 +89,26 @@ TelemetryDeck.send("appLaunchedRegularly") To enqueue a signal to be sent by TelemetryDeck at a later time ```kotlin -TelemetryDeck.queue("appLaunchedRegularly") +TelemetryDeck.signal("appLaunchedRegularly") ``` ## Custom Telemetry -Another way to send signals is to register a custom `TelemetryProvider`. A provider maintains a reference to the TelemetryDeck client in order to queue or send signals based on environment or other triggers. +Another way to send signals is to register a custom `TelemetryDeckProvider`. +A provider uses the TelemetryDeck client in order to queue or send signals based on environment or other triggers. -To create a provider, implement the `TelemetryProvider` interface: +To create a provider, implement the `TelemetryDeckProvider` interface: ```kotlin -class CustomProvider: TelemetryProvider { +class CustomProvider: TelemetryDeckProvider { override fun register(ctx: Application?, client: TelemetryDeckClient) { // configure and start the provider + // you may retain a WeakReference to client } override fun stop() { - // deactivate the provider + // deactivate the provider, perform cleanup work } } ``` @@ -115,18 +117,20 @@ Setup and start the provider during the `register` method. Tips: -- Do not retain a strong reference to the application context or the TelemetryDeck client instance. -- You can use `WeakReference` if you need to be able to call the TelemetryDeck at a later time. +- Do not retain a strong reference to the application context or TelemetryDeckClient instance. +- You can use `WeakReference` if you need to be able to call the TelemetryDeck at a later time. To use your custom provider, register it by calling `addProvider` using the `TelemetryDeck.Builder` : ```kotlin val builder = TelemetryDeck.Builder() - .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") - .addProvider(CustomProvider()) + // ... + .addProvider(CustomProvider()) // <-- Your custom provider ``` -In the implementation of your custom `TelemetryProvider`, we offer a callback function where you can append additional payload attributes to the signal. The `enrich` call is made right before sending the signal: +Every time the SDK is about to send signals to our servers, the `enrich` method of every provider will be invoked to give you the opportunity to append additional parameters. + +In the implementation of your custom `TelemetryDeckProvider`, you can override the `enrich` method: ```kotlin override fun enrich( @@ -146,34 +150,84 @@ override fun enrich( } ``` -We use providers internally to provide lifecycle and environment integration out of the box. Feel free to examine how they work and inspire your own implementations. You can also completely disable or override the default providers with your own. +We use providers internally to provide lifecycle and environment integration out of the box. +Feel free to examine how they work and inspire your own implementations. + +You can also completely disable or override the default providers with your own. -- `SessionProvider` - Monitors the app lifecycle in order to broadcast the NewSessionBegan signal. This provider is tasked with resetting the sessionID when `sendNewSessionBeganSignal` is enabled. -- `AppLifecycleTelemetryProvider` - Emits signals for application and activity lifecycle events. -- `EnvironmentMetadataProvider` - Adds environment and device information to outgoing Signals. This provider overrides the `enrich` method in order to append additional metadata for all signals before sending them. +- `SessionAppProvider` - Emits signals for application and activity lifecycle events. This provider is tasked with resetting the sessionID when `sendNewSessionBeganSignal` is enabled. +- `SessionActivityProvider` - Emits signals for application and activity lifecycle events. This provider is not enabled by default. +- `EnvironmentParameterProvider` - Adds environment and device information to outgoing Signals. This provider overrides the `enrich` method in order to append additional metadata for all signals before sending them. ```kotlin // Append a custom provider val builder = TelemetryDeck.Builder() - .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") + // ... .addProvider(CustomProvider()) // Replace all default providers val builder = TelemetryDeck.Builder() - .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") + // ... .providers(listOf(CustomProvider(), AnotherProvider())) ``` ### Migrating providers to 3.0+ -To adapt to the updated `TelemetryProvider` interface, please perform the following changes: +If you had TelemetryDeck SDK for Kotlin added to your app, you will notice that `TelemetryManager` and related classes have been deprecated. +You can read more about the motivation behind these changes [here](https://telemetrydeck.com/docs/articles/grand-rename/). + +To upgrade, please perform the following changes depending on how you use TelemetryDeck SDK. + +#### Using the application manifest + +* Adapt the manifest of your app and rename all keys from `com.telemetrydeck.sdk.*` to `com.telemetrydeck.*` for example: + +Before: +```xml + +``` + +After: +```xml + +``` + +* In your app sourcecode, rename all uses of `TelemetryManager` to `TelemetryDeck`. +* If you were using `send()` to send signals, no further changes are needed! +* If you were using `queue()` to send signals, you will need to rename the method to `TelemetryDeck.signal()`. + +#### Programmatic Usage + +* In your app sourcecode, rename all uses of `TelemetryManager` to `TelemetryDeck`. +* If you were using `send()` to send signals, no further changes are needed! +* If you were using `queue()` to send signals, you will need to rename the method to `TelemetryDeck.signal()`. +* If you had a custom provider configuration, please replace the corresponding providers as follows: + +| Provider (old name) | Provider (new, 3.0+) | +|---------------------------------|-------------------------------------------------| +| `AppLifecycleTelemetryProvider` | `SessionAppProvider`, `SessionActivityProvider` | +| `SessionProvider` | `SessionAppProvider` | +| `EnvironmentMetadataProvider` | `EnvironmentParameterProvider` | + + +#### Custom Telemetry + + +Your custom providers must replace `TelemetryProvider` with `TelemetryDeckProvider`. + +To adopt the new interface: * Adapt the signature of the `register` method to `register(ctx: Application?, client: TelemetryDeckClient)` -* To access the logger, use `client.debugLogger` + +You now have access to the entire `TelemetryDeckClient` interface: + +* To access the logger, use can use `client.debugLogger` * To access the signal cache, use `client.signalCache` + + ## Requirements - Android API 21 or later diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt index a0a1290..ef0ef15 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt @@ -166,7 +166,6 @@ class TelemetryDeck( companion object : TelemetryDeckClient { internal val defaultTelemetryProviders: List get() = listOf( - SessionActivityProvider(), SessionAppProvider(), EnvironmentParameterProvider() ) From da74f994aae087413acd337cdf9ccd28474a00f9 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 4 Oct 2024 11:46:11 +0200 Subject: [PATCH 10/33] docs: add missing custom logger documentation --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 8a64456..9e99de6 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,19 @@ val builder = TelemetryDeck.Builder() .providers(listOf(CustomProvider(), AnotherProvider())) ``` +## Custom Logging + +By default, TelemetryDeck SDK uses a simple `println` to output internal diagnostic messages when `showDebugLogs` is set to `true` in configuration. + +If your platform has custom logging needs, you can adopt the `DebugLogger` interface and provide it to the `TelemetryDeck` builder: + +```kotlin +val builder = TelemetryDeck.Builder() + // ... + .logger(CustomLogger()) +``` + +Please note that the logger implementation should be thread safe as it may be invoked in different queues and contexts. ### Migrating providers to 3.0+ From e934093f01d4ffee33d22de110cef52805a1ec7c Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 4 Oct 2024 13:26:38 +0200 Subject: [PATCH 11/33] feat: migrate to new signal names --- README.md | 74 +++++++++++++------ .../com/telemetrydeck/sdk/TelemetryDeck.kt | 1 - .../providers/EnvironmentParameterProvider.kt | 56 ++++++++++---- .../sdk/providers/SessionAppProvider.kt | 4 +- .../com/telemetrydeck/sdk/signals/AppInfo.kt | 8 ++ .../com/telemetrydeck/sdk/signals/Device.kt | 19 +++++ .../telemetrydeck/sdk/signals/RunContext.kt | 14 ++++ .../java/com/telemetrydeck/sdk/signals/SDK.kt | 7 ++ .../com/telemetrydeck/sdk/signals/Session.kt | 5 ++ .../telemetrydeck/sdk/TelemetryDeckTests.kt | 4 +- .../EnvironmentParameterProviderTest.kt | 2 +- .../sdk/providers/SessionAppProviderTest.kt | 6 +- 12 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/signals/AppInfo.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/signals/Device.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/signals/RunContext.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/signals/SDK.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/signals/Session.kt diff --git a/README.md b/README.md index 9e99de6..e7def66 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,9 @@ This package allows you to send signals to [TelemetryDeck](https://telemetrydeck ## Installation -The TelemetryDeck is distributed using [jitpack](https://jitpack.io/), so you'll need to add the jitpack dependency to your `settings.gradle` file: +### Dependencies -```groovy -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - maven { url 'https://jitpack.io' } // <-- add this line - } -} -``` - -After that is done, add the following to your `build.gradle` file, under `dependencies`: +The TelemetryDeck SDK for Kotlin is available from Maven Central and can be used as a dependency directly in `build.gradle` file: ```groovy dependencies { @@ -33,7 +22,7 @@ If needed, update your `gradle.settings` to reference Kotlin version compatible id "org.jetbrains.kotlin.android" version "1.9.25" apply false ``` -## Permission for internet access +### Permission for internet access Sending signals requires access to the internet so the following permission should be added to the app's `AndroidManifest.xml` @@ -41,6 +30,8 @@ Sending signals requires access to the internet so the following permission shou ``` +## Getting Started + ### Using the application manifest The TelemetryDeck can be initialized automatically by adding the application key to the `application` section of the app's `AndroidManifest.xml`: @@ -92,6 +83,36 @@ To enqueue a signal to be sent by TelemetryDeck at a later time TelemetryDeck.signal("appLaunchedRegularly") ``` +### Environment Parameters + +By default, TelemetryDeck will include the following environment parameters for each outgoing signal + + +| Signal name | Provider | +|------------------------------------------------|--------------------------------| +| `TelemetryDeck.Session.started` | `SessionAppProvider` | +| `TelemetryDeck.AppInfo.buildNumber` | `EnvironmentParameterProvider` | +| `TelemetryDeck.AppInfo.version` | `EnvironmentParameterProvider` | +| `TelemetryDeck.AppInfo.versionAndBuildNumber` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.architecture` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.modelName` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.operatingSystem` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.platform` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.systemMajorMinorVersion` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.systemMajorVersion` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.systemVersion` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.brand` | `EnvironmentParameterProvider` | +| `TelemetryDeck.AppInfo.buildNumber` | `EnvironmentParameterProvider` | +| `TelemetryDeck.AppInfo.version` | `EnvironmentParameterProvider` | +| `TelemetryDeck.AppInfo.versionAndBuildNumber` | `EnvironmentParameterProvider` | +| `TelemetryDeck.RunContext.locale` | `EnvironmentParameterProvider` | +| `TelemetryDeck.RunContext.targetEnvironment` | `EnvironmentParameterProvider` | +| `TelemetryDeck.SDK.name` | `EnvironmentParameterProvider` | +| `TelemetryDeck.SDK.version` | `EnvironmentParameterProvider` | +| `TelemetryDeck.SDK.nameAndVersion` | `EnvironmentParameterProvider` | + +See [Custom Telemetry](#custom-telemetry) on how to implement your own parameter enrichment. + ## Custom Telemetry Another way to send signals is to register a custom `TelemetryDeckProvider`. @@ -186,14 +207,22 @@ val builder = TelemetryDeck.Builder() Please note that the logger implementation should be thread safe as it may be invoked in different queues and contexts. -### Migrating providers to 3.0+ + + +## Requirements + +- Android API 21 or later +- Kotlin 1.9.25 or later + + +## Migrating providers to 3.0+ If you had TelemetryDeck SDK for Kotlin added to your app, you will notice that `TelemetryManager` and related classes have been deprecated. You can read more about the motivation behind these changes [here](https://telemetrydeck.com/docs/articles/grand-rename/). To upgrade, please perform the following changes depending on how you use TelemetryDeck SDK. -#### Using the application manifest +### If you're using the application manifest * Adapt the manifest of your app and rename all keys from `com.telemetrydeck.sdk.*` to `com.telemetrydeck.*` for example: @@ -211,7 +240,7 @@ After: * If you were using `send()` to send signals, no further changes are needed! * If you were using `queue()` to send signals, you will need to rename the method to `TelemetryDeck.signal()`. -#### Programmatic Usage +### Programmatic Usage * In your app sourcecode, rename all uses of `TelemetryManager` to `TelemetryDeck`. * If you were using `send()` to send signals, no further changes are needed! @@ -225,7 +254,11 @@ After: | `EnvironmentMetadataProvider` | `EnvironmentParameterProvider` | -#### Custom Telemetry +> [!TIP] +> You can rename all deprecated classes in your project using the Code Cleanup function in IntelliJ/Android Studio. + + +### Custom Telemetry Your custom providers must replace `TelemetryProvider` with `TelemetryDeckProvider`. @@ -240,8 +273,3 @@ You now have access to the entire `TelemetryDeckClient` interface: * To access the signal cache, use `client.signalCache` - -## Requirements - -- Android API 21 or later -- Kotlin 1.9.25 or later diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt index ef0ef15..356d7ea 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt @@ -4,7 +4,6 @@ import android.app.Application import android.content.Context import android.content.pm.ApplicationInfo import com.telemetrydeck.sdk.providers.EnvironmentParameterProvider -import com.telemetrydeck.sdk.providers.SessionActivityProvider import com.telemetrydeck.sdk.providers.SessionAppProvider import java.lang.ref.WeakReference import java.net.URL diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt index 0699e53..f4f0542 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt @@ -6,6 +6,10 @@ import com.telemetrydeck.sdk.BuildConfig import com.telemetrydeck.sdk.ManifestMetadataReader import com.telemetrydeck.sdk.TelemetryDeckClient import com.telemetrydeck.sdk.TelemetryDeckProvider +import com.telemetrydeck.sdk.signals.AppInfo +import com.telemetrydeck.sdk.signals.Device +import com.telemetrydeck.sdk.signals.RunContext +import com.telemetrydeck.sdk.signals.SDK import java.lang.ref.WeakReference import java.util.Locale @@ -20,6 +24,12 @@ class EnvironmentParameterProvider : TelemetryDeckProvider { private var enabled: Boolean = true private var manager: WeakReference? = null private var metadata = mutableMapOf() + // The approach from the SwiftSDK is not compatible here as we need to evaluate for platform capabilities + // In case of Kotlin Multiplatform, a per-platform value can be provided + // For now, we're defaulting to "Android" + private val platform: String = "Android" + private val os: String = "Android" + private val sdkName: String = "KotlinSDK" override fun register(ctx: Application?, client: TelemetryDeckClient) { this.manager = WeakReference(client) @@ -27,48 +37,66 @@ class EnvironmentParameterProvider : TelemetryDeckProvider { if (ctx != null) { val appVersion = ManifestMetadataReader.getAppVersion(ctx) if (!appVersion.isNullOrEmpty()) { - metadata["appVersion"] = appVersion + metadata[AppInfo.Version.signalName] = appVersion } ManifestMetadataReader.getBuildNumber(ctx)?.let { buildNumber -> - metadata["buildNumber"] = buildNumber.toString() + metadata[AppInfo.BuildNumber.signalName] = buildNumber.toString() + metadata[AppInfo.VersionAndBuildNumber.signalName] = "$appVersion (build $buildNumber)" } } else { this.manager?.get()?.debugLogger?.error("EnvironmentParameterProvider requires a context but received null. Signals will contain incomplete metadata.") } + + if (android.os.Build.VERSION.RELEASE.isNullOrEmpty()) { this.manager?.get()?.debugLogger?.error( "EnvironmentMetadataProvider found no platform version information (android.os.Build.VERSION.RELEASE). Signal payloads will not be enriched." ) } else { + // Device metadata + metadata[Device.Platform.signalName] = platform val release = android.os.Build.VERSION.RELEASE val sdkVersion = android.os.Build.VERSION.SDK_INT - metadata["systemVersion"] = "Android SDK: $sdkVersion ($release)" + metadata[Device.SystemVersion.signalName] = "$platform $release (SDK: $sdkVersion)" if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { val versionInfo = VersionInfo.getInstance(release) - metadata["majorSystemVersion"] = versionInfo.major.toString() - metadata["majorMinorSystemVersion"] = "${versionInfo.major}.${versionInfo.minor}" + metadata[Device.SystemMajorVersion.signalName] = "${versionInfo.major}" + metadata[Device.SystemMajorMinorVersion.signalName] = "${versionInfo.major}.${versionInfo.minor}" } else { val versionInfo = release.split(".") - metadata["majorSystemVersion"] = versionInfo.elementAtOrNull(0) ?: "0" - metadata["majorMinorSystemVersion"] = "${versionInfo.elementAtOrNull(0) ?: "0"}.${versionInfo.elementAtOrNull(1) ?: "0"}" + val major = versionInfo.elementAtOrNull(0) ?: "0" + val minor = versionInfo.elementAtOrNull(1) ?: "0" + metadata[Device.SystemMajorVersion.signalName] = major + metadata[Device.SystemMajorMinorVersion.signalName] = "$major.$minor" } } - metadata["locale"] = Locale.getDefault().displayName if (android.os.Build.BRAND != null) { - metadata["brand"] = android.os.Build.BRAND + metadata[Device.Brand.signalName] = android.os.Build.BRAND } if (android.os.Build.DEVICE != null) { - metadata["targetEnvironment"] = android.os.Build.DEVICE + metadata[RunContext.TargetEnvironment.signalName] = android.os.Build.DEVICE } if (android.os.Build.MODEL != null && android.os.Build.PRODUCT != null) { - metadata["modelName"] = "${android.os.Build.MODEL} (${android.os.Build.PRODUCT})" + metadata[Device.ModelName.signalName] = "${android.os.Build.MODEL} (${android.os.Build.PRODUCT})" } - metadata["architecture"] = System.getProperty("os.arch") ?: "" - metadata["operatingSystem"] = "Android" - metadata["telemetryClientVersion"] = BuildConfig.LIBRARY_PACKAGE_NAME + metadata[Device.Architecture.signalName] = System.getProperty("os.arch") ?: "" + metadata[Device.OperatingSystem.signalName] = os + + + // SDK Metadata + metadata[SDK.Name.signalName] = sdkName + // TODO: create a build property to pass the maven coordinates of the library + metadata[SDK.Version.signalName] = BuildConfig.LIBRARY_PACKAGE_NAME + metadata[SDK.NameAndVersion.signalName] = "$sdkName ${BuildConfig.LIBRARY_PACKAGE_NAME}" + + + // RunContext Metadata + metadata[RunContext.Locale.signalName] = Locale.getDefault().displayName + + this.enabled = true } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt index 97a1dc3..f34b216 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt @@ -5,9 +5,9 @@ import android.app.Application import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner -import com.telemetrydeck.sdk.SignalType import com.telemetrydeck.sdk.TelemetryDeckClient import com.telemetrydeck.sdk.TelemetryDeckProvider +import com.telemetrydeck.sdk.signals.Session import java.lang.ref.WeakReference /** @@ -28,7 +28,7 @@ class SessionAppProvider: TelemetryDeckProvider, DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { if (manager?.get()?.configuration?.sendNewSessionBeganSignal == true) { manager?.get()?.signal( - SignalType.NewSessionBegan + Session.Started.signalName ) } } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/AppInfo.kt b/lib/src/main/java/com/telemetrydeck/sdk/signals/AppInfo.kt new file mode 100644 index 0000000..392ace2 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/signals/AppInfo.kt @@ -0,0 +1,8 @@ +package com.telemetrydeck.sdk.signals + +internal enum class AppInfo(val signalName: String) { + BuildNumber("TelemetryDeck.AppInfo.buildNumber"), + Version("TelemetryDeck.AppInfo.version"), + VersionAndBuildNumber("TelemetryDeck.AppInfo.versionAndBuildNumber"), +} + diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/Device.kt b/lib/src/main/java/com/telemetrydeck/sdk/signals/Device.kt new file mode 100644 index 0000000..2dd873d --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/signals/Device.kt @@ -0,0 +1,19 @@ +package com.telemetrydeck.sdk.signals + + +// TODO: add more device parameters from the Swift SDK: +//"TelemetryDeck.Device.orientation": Self.orientation, +//"TelemetryDeck.Device.screenResolutionHeight": Self.screenResolutionHeight, +//"TelemetryDeck.Device.screenResolutionWidth": Self.screenResolutionWidth, +//"TelemetryDeck.Device.timeZone": Self.timeZone, + +internal enum class Device(val signalName: String) { + Architecture("TelemetryDeck.Device.architecture"), + ModelName("TelemetryDeck.Device.modelName"), + OperatingSystem("TelemetryDeck.Device.operatingSystem"), + Platform("TelemetryDeck.Device.platform"), + SystemMajorMinorVersion("TelemetryDeck.Device.systemMajorMinorVersion"), + SystemMajorVersion("TelemetryDeck.Device.systemMajorVersion"), + SystemVersion("TelemetryDeck.Device.systemVersion"), + Brand("TelemetryDeck.Device.brand"), +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/RunContext.kt b/lib/src/main/java/com/telemetrydeck/sdk/signals/RunContext.kt new file mode 100644 index 0000000..bd3b3e8 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/signals/RunContext.kt @@ -0,0 +1,14 @@ +package com.telemetrydeck.sdk.signals + + +//"TelemetryDeck.RunContext.isAppStore": "\(Self.isAppStore)", +//"TelemetryDeck.RunContext.isDebug": "\(Self.isDebug)", +//"TelemetryDeck.RunContext.isSimulator": "\(Self.isSimulator)", +//"TelemetryDeck.RunContext.isTestFlight": "\(Self.isTestFlight)", +//"TelemetryDeck.RunContext.language": Self.appLanguage, +//"TelemetryDeck.RunContext.targetEnvironment": Self.targetEnvironment, + +internal enum class RunContext(val signalName: String) { + Locale("TelemetryDeck.RunContext.locale"), + TargetEnvironment("TelemetryDeck.RunContext.targetEnvironment"), +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/SDK.kt b/lib/src/main/java/com/telemetrydeck/sdk/signals/SDK.kt new file mode 100644 index 0000000..a0e50d0 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/signals/SDK.kt @@ -0,0 +1,7 @@ +package com.telemetrydeck.sdk.signals + +internal enum class SDK(val signalName: String) { + Name("TelemetryDeck.SDK.name"), + Version("TelemetryDeck.SDK.version"), + NameAndVersion("TelemetryDeck.SDK.nameAndVersion"), +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/Session.kt b/lib/src/main/java/com/telemetrydeck/sdk/signals/Session.kt new file mode 100644 index 0000000..88cbec1 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/signals/Session.kt @@ -0,0 +1,5 @@ +package com.telemetrydeck.sdk.signals + +internal enum class Session(val signalName: String) { + Started("TelemetryDeck.Session.started"), +} \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt index 8396609..60e3660 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt @@ -242,8 +242,8 @@ class TelemetryDeckTests { .build(null) sut.signal("type") - Assert.assertEquals(4, sut.providers.count()) - Assert.assertTrue(sut.providers[3] is TestTelemetryDeckProvider) + Assert.assertEquals(3, sut.providers.count()) + Assert.assertTrue(sut.providers.last() is TestTelemetryDeckProvider) } @Test diff --git a/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt index 37b167d..259bc67 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt @@ -22,7 +22,7 @@ class EnvironmentParameterProviderTest { val queuedSignal = manager.cache?.empty()?.first() Assert.assertNotNull(queuedSignal) - Assert.assertEquals(queuedSignal?.payload?.contains("telemetryClientVersion:com.telemetrydeck.sdk"), true) + Assert.assertEquals(queuedSignal?.payload?.contains("TelemetryDeck.SDK.version:com.telemetrydeck.sdk"), true) } @Test diff --git a/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt index cb33700..999cb2d 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt @@ -3,8 +3,8 @@ package com.telemetrydeck.sdk.providers import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.LifecycleOwner -import com.telemetrydeck.sdk.SignalType import com.telemetrydeck.sdk.TelemetryDeck +import com.telemetrydeck.sdk.signals.Session import org.junit.Assert import org.junit.Rule import org.junit.Test @@ -40,7 +40,7 @@ class SessionAppProviderTest { sut.onStart(lifecycleOwner) Assert.assertEquals(1, manager.cache?.count()) - Assert.assertEquals(SignalType.NewSessionBegan.type, manager.cache?.empty()?.get(0)?.type) + Assert.assertEquals(Session.Started.signalName, manager.cache?.empty()?.get(0)?.type) } @Test @@ -53,7 +53,7 @@ class SessionAppProviderTest { sut.onStart(lifecycleOwner) Assert.assertEquals(1, manager.cache?.count()) - Assert.assertEquals(SignalType.NewSessionBegan.type, manager.cache?.empty()?.get(0)?.type) + Assert.assertEquals(Session.Started.signalName, manager.cache?.empty()?.get(0)?.type) } @Test From cc23e9dedd7e42fb7623f35a3409650fbe193a26 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 4 Oct 2024 15:26:43 +0200 Subject: [PATCH 12/33] feat: support floatValue, docs: additional deprecations on older signal names --- .../telemetrydeck/sdk/PayloadParameters.kt | 1 + .../main/java/com/telemetrydeck/sdk/Signal.kt | 7 +- .../java/com/telemetrydeck/sdk/SignalType.kt | 6 +- .../com/telemetrydeck/sdk/TelemetryClient.kt | 7 +- .../com/telemetrydeck/sdk/TelemetryDeck.kt | 84 +++++++------------ .../telemetrydeck/sdk/TelemetryDeckClient.kt | 20 +---- .../sdk/providers/SessionActivityProvider.kt | 18 ++-- .../telemetrydeck/sdk/signals/Navigation.kt | 8 ++ .../com/telemetrydeck/sdk/SignalsUnitTest.kt | 15 ++++ .../telemetrydeck/sdk/TelemetryDeckTests.kt | 18 ++++ 10 files changed, 101 insertions(+), 83 deletions(-) create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/signals/Navigation.kt diff --git a/lib/src/main/java/com/telemetrydeck/sdk/PayloadParameters.kt b/lib/src/main/java/com/telemetrydeck/sdk/PayloadParameters.kt index 2084430..cf99ff2 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/PayloadParameters.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/PayloadParameters.kt @@ -1,5 +1,6 @@ package com.telemetrydeck.sdk +@Deprecated("PayloadParameters is no longer part of our public API. You're free to create a custom enum for custom signal names.") enum class PayloadParameters(val type: String) { TelemetryDeckNavigationSchemaVersion("TelemetryDeck.Navigation.schemaVersion"), TelemetryDeckNavigationIdentifier("TelemetryDeck.Navigation.identifier"), diff --git a/lib/src/main/java/com/telemetrydeck/sdk/Signal.kt b/lib/src/main/java/com/telemetrydeck/sdk/Signal.kt index c39d1c5..07edd6f 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/Signal.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/Signal.kt @@ -43,7 +43,12 @@ data class Signal( /** * If "true", mark the signal as a testing signal and only show it in a dedicated test mode UI */ - var isTestMode: String = "false" + var isTestMode: String = "false", + + /** + * An optional floating-point value to include with the signal. Default is `nil`. + */ + var floatValue: Double? = null ) { constructor(appID: UUID, signalType: String, clientUser: String, payload: SignalPayload) : this(appID=appID, type=signalType, clientUser = clientUser, payload = payload.asMultiValueDimension) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/SignalType.kt b/lib/src/main/java/com/telemetrydeck/sdk/SignalType.kt index 604f343..38775c4 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/SignalType.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/SignalType.kt @@ -1,5 +1,6 @@ package com.telemetrydeck.sdk +@Deprecated("SignalType is no longer part of our public API. You're free to create a custom enum for custom signal names.") enum class SignalType(val type: String) { ActivityCreated("ActivityCreated"), ActivityStarted("ActivityStarted"), ActivityResumed("ActivityResumed"), ActivityPaused( "ActivityPaused" @@ -7,7 +8,10 @@ enum class SignalType(val type: String) { ActivityStopped("ActivityStopped"), ActivitySaveInstanceState("ActivitySaveInstanceState"), ActivityDestroyed( "ActivityDestroyed" ), - AppBackground("AppBackground"), AppForeground("AppForeground"), NewSessionBegan("NewSessionBegan"), TelemetryDeckNavigationPathChanged( + AppBackground("AppBackground"), + AppForeground("AppForeground"), + NewSessionBegan("NewSessionBegan"), + TelemetryDeckNavigationPathChanged( "TelemetryDeck.Navigation.pathChanged" ) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryClient.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryClient.kt index 87d53c7..673cb64 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryClient.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryClient.kt @@ -17,7 +17,12 @@ import java.util.* /** * The HTTP client to communicate with TelemetryDeck's API */ -internal class TelemetryClient(private val telemetryAppID: UUID, private val apiBaseURL: URL, private val showDebugLogs: Boolean, private val debugLogger: DebugLogger?) { +internal class TelemetryClient( + private val telemetryAppID: UUID, + private val apiBaseURL: URL, + private val showDebugLogs: Boolean, + private val debugLogger: DebugLogger? +) { private val client: HttpClient = HttpClient(OkHttp) { install(ContentNegotiation) { json() diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt index 356d7ea..39dd874 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.pm.ApplicationInfo import com.telemetrydeck.sdk.providers.EnvironmentParameterProvider import com.telemetrydeck.sdk.providers.SessionAppProvider +import com.telemetrydeck.sdk.signals.Navigation import java.lang.ref.WeakReference import java.net.URL import java.security.MessageDigest @@ -39,14 +40,14 @@ class TelemetryDeck( override fun navigate(sourcePath: String, destinationPath: String, clientUser: String?) { navigationStatus.applyDestination(destinationPath) - val payload: Map = mapOf( - PayloadParameters.TelemetryDeckNavigationSchemaVersion.type to "1", - PayloadParameters.TelemetryDeckNavigationIdentifier.type to "$sourcePath -> $destinationPath", - PayloadParameters.TelemetryDeckNavigationSourcePath.type to sourcePath, - PayloadParameters.TelemetryDeckNavigationDestinationPath.type to destinationPath + val params: Map = mapOf( + Navigation.SchemaVersion.signalName to "1", + Navigation.Identifier.signalName to "$sourcePath -> $destinationPath", + Navigation.SourcePath.signalName to sourcePath, + Navigation.DestinationPath.signalName to destinationPath ) - signal(SignalType.TelemetryDeckNavigationPathChanged.type, params = payload, customUserID = clientUser) + signal(SignalType.TelemetryDeckNavigationPathChanged.type, params = params, customUserID = clientUser) } override fun navigate(destinationPath: String, clientUser: String?) { @@ -56,17 +57,10 @@ class TelemetryDeck( override suspend fun send( signalType: String, clientUser: String?, - additionalPayload: Map + additionalPayload: Map, + floatValue: Double? ): Result { - return send(createSignal(signalType, clientUser, additionalPayload)) - } - - override suspend fun send( - signalType: SignalType, - clientUser: String?, - additionalPayload: Map - ): Result { - return send(signalType.type, clientUser, additionalPayload) + return send(createSignal(signalType, clientUser, additionalPayload, floatValue)) } override suspend fun sendAll(signals: List): Result { @@ -79,21 +73,23 @@ class TelemetryDeck( floatValue: Double?, customUserID: String? ) { - // TODO: floatValue - cache?.add(createSignal(signalType = signalName, clientUser = customUserID, additionalPayload = params)) + cache?.add( + createSignal( + signalType = signalName, + clientUser = customUserID, + additionalPayload = params, + floatValue = floatValue + ) + ) } override fun signal(signalName: String, customUserID: String?, params: Map) { - cache?.add(createSignal(signalType = signalName, clientUser = customUserID, additionalPayload = params)) - } - - override fun signal( - signalType: SignalType, - params: Map, - floatValue: Double?, - customUserID: String? - ) { - cache?.add(createSignal(signalType = signalType.type, clientUser = customUserID, additionalPayload = params)) + cache?.add(createSignal( + signalType = signalName, + clientUser = customUserID, + additionalPayload = params, + floatValue = null + )) } private suspend fun send( @@ -132,7 +128,8 @@ class TelemetryDeck( private fun createSignal( signalType: String, clientUser: String? = null, - additionalPayload: Map = emptyMap() + additionalPayload: Map = emptyMap(), + floatValue: Double? ): Signal { var enrichedPayload = additionalPayload for (provider in this.providers) { @@ -149,7 +146,8 @@ class TelemetryDeck( type = signalType, clientUser = hashedUser, payload = payload.asMultiValueDimension, - isTestMode = configuration.testMode.toString().lowercase() + isTestMode = configuration.testMode.toString().lowercase(), + floatValue = floatValue ) signal.sessionID = this.configuration.sessionID.toString() logger?.debug("Created a signal ${signal.type}, session ${signal.sessionID}, test ${signal.isTestMode}") @@ -238,21 +236,10 @@ class TelemetryDeck( override suspend fun send( signalType: String, clientUser: String?, - additionalPayload: Map - ): Result { - val result = getInstance()?.send(signalType, clientUser, additionalPayload) - if (result != null) { - return result - } - return failure(NullPointerException()) - } - - override suspend fun send( - signalType: SignalType, - clientUser: String?, - additionalPayload: Map + additionalPayload: Map, + floatValue: Double? ): Result { - val result = getInstance()?.send(signalType, clientUser, additionalPayload) + val result = getInstance()?.send(signalType, clientUser, additionalPayload, floatValue) if (result != null) { return result } @@ -284,15 +271,6 @@ class TelemetryDeck( getInstance()?.signal(signalName = signalName, customUserID = customUserID, params = params) } - override fun signal( - signalType: SignalType, - params: Map, - floatValue: Double?, - customUserID: String? - ) { - getInstance()?.signal(signalType.type, params, floatValue, customUserID) - } - override val signalCache: SignalCache? get() = getInstance()?.signalCache diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt index fbc9ae4..116917c 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt @@ -40,17 +40,8 @@ interface TelemetryDeckClient { suspend fun send( signalType: String, clientUser: String? = null, - additionalPayload: Map = emptyMap() - ): Result - - - /** - * Send a signal immediately. - */ - suspend fun send( - signalType: SignalType, - clientUser: String? = null, - additionalPayload: Map = emptyMap() + additionalPayload: Map = emptyMap(), + floatValue: Double? = null, ): Result @@ -60,13 +51,6 @@ interface TelemetryDeckClient { suspend fun sendAll(signals: List): Result - fun signal( - signalType: SignalType, - params: Map = emptyMap(), - floatValue: Double? = null, - customUserID: String? = null - ) - /** * Sends a telemetry signal with optional parameters to TelemetryDeck. * diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt index ffacc34..c889a99 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt @@ -35,62 +35,62 @@ class SessionActivityProvider: TelemetryDeckProvider, override fun onActivityCreated(p0: Activity, p1: Bundle?) { manager?.get()?.signal( - SignalType.ActivityCreated, + SignalType.ActivityCreated.type, mapOf("activity" to p0.localClassName) ) } override fun onActivityStarted(p0: Activity) { manager?.get()?.signal( - SignalType.ActivityStarted, + SignalType.ActivityStarted.type, mapOf("activity" to p0.localClassName) ) } override fun onActivityResumed(p0: Activity) { manager?.get()?.signal( - SignalType.ActivityResumed, + SignalType.ActivityResumed.type, mapOf("activity" to p0.localClassName) ) } override fun onActivityPaused(p0: Activity) { manager?.get()?.signal( - SignalType.ActivityPaused, + SignalType.ActivityPaused.type, mapOf("activity" to p0.localClassName) ) } override fun onActivityStopped(p0: Activity) { manager?.get()?.signal( - SignalType.ActivityStopped, + SignalType.ActivityStopped.type, mapOf("activity" to p0.localClassName) ) } override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) { manager?.get()?.signal( - SignalType.ActivitySaveInstanceState, + SignalType.ActivitySaveInstanceState.type, mapOf("activity" to p0.localClassName) ) } override fun onActivityDestroyed(p0: Activity) { manager?.get()?.signal( - SignalType.ActivityDestroyed, + SignalType.ActivityDestroyed.type, mapOf("activity" to p0.localClassName) ) } override fun onStart(owner: LifecycleOwner) { manager?.get()?.signal( - SignalType.AppForeground + SignalType.AppForeground.type ) } override fun onStop(owner: LifecycleOwner) { manager?.get()?.signal( - SignalType.AppBackground + SignalType.AppBackground.type ) } } \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/Navigation.kt b/lib/src/main/java/com/telemetrydeck/sdk/signals/Navigation.kt new file mode 100644 index 0000000..c2ae821 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/signals/Navigation.kt @@ -0,0 +1,8 @@ +package com.telemetrydeck.sdk.signals + +internal enum class Navigation(val signalName: String) { + SchemaVersion("TelemetryDeck.Navigation.schemaVersion"), + Identifier("TelemetryDeck.Navigation.identifier"), + SourcePath("TelemetryDeck.Navigation.sourcePath"), + DestinationPath("TelemetryDeck.Navigation.destinationPath"), +} \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt index 3be1865..1f79aa3 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt @@ -89,4 +89,19 @@ class SignalsUnitTest { endpointUrl.toString() ) } + + + @Test + fun signal_serialize_floatValue() { + val float: Double = 3.444444444444445 + + val signal = Signal(UUID.randomUUID(), "type", "clientUser", SignalPayload()) + signal.floatValue = float + + val signalJson = Json.encodeToString(signal) + val decodedSignal = Json.decodeFromString(signalJson) + + // date equality comparison with precision up to milliseconds + assertEquals(float, decodedSignal.floatValue) + } } \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt index 60e3660..b34d868 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt @@ -416,6 +416,24 @@ class TelemetryDeckTests { ) } + + @Test + fun telemetryDeck_signal_with_floatValue() { + val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536") + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.signal("test", floatValue = 1.0) + + val queuedSignal = manager.cache?.empty()?.first() + + Assert.assertNotNull(queuedSignal) + + // validate the signal type + Assert.assertEquals(queuedSignal?.type, "test") + + Assert.assertEquals(queuedSignal?.floatValue, 1.0) + } + private fun filterOldSignals(signals: List): List { val now = Date().time return signals.filter { From a8686c42e1715b8f466e611a3e0393f371584131 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 4 Oct 2024 17:19:35 +0200 Subject: [PATCH 13/33] feat: add parameters from platform context --- README.md | 17 +++-- .../com/telemetrydeck/sdk/TelemetryDeck.kt | 20 +++--- .../sdk/{signals => params}/AppInfo.kt | 4 +- .../sdk/{signals => params}/Device.kt | 6 +- .../telemetrydeck/sdk/params/Navigation.kt | 8 +++ .../sdk/{signals => params}/RunContext.kt | 7 +- .../sdk/{signals => params}/SDK.kt | 4 +- .../sdk/platform/AppInstallationInfo.kt | 7 ++ .../telemetrydeck/sdk/platform/PackageInfo.kt | 43 +++++++++++++ .../providers/EnvironmentParameterProvider.kt | 64 ++++++++++--------- .../sdk/providers/PlatformContextProvider.kt | 61 ++++++++++++++++++ .../telemetrydeck/sdk/signals/Navigation.kt | 5 +- .../telemetrydeck/sdk/TelemetryDeckTests.kt | 2 +- 13 files changed, 185 insertions(+), 63 deletions(-) rename lib/src/main/java/com/telemetrydeck/sdk/{signals => params}/AppInfo.kt (65%) rename lib/src/main/java/com/telemetrydeck/sdk/{signals => params}/Device.kt (83%) create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/params/Navigation.kt rename lib/src/main/java/com/telemetrydeck/sdk/{signals => params}/RunContext.kt (66%) rename lib/src/main/java/com/telemetrydeck/sdk/{signals => params}/SDK.kt (60%) create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/platform/AppInstallationInfo.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/platform/PackageInfo.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/providers/PlatformContextProvider.kt diff --git a/README.md b/README.md index e7def66..66bbfd6 100644 --- a/README.md +++ b/README.md @@ -102,14 +102,17 @@ By default, TelemetryDeck will include the following environment parameters for | `TelemetryDeck.Device.systemMajorVersion` | `EnvironmentParameterProvider` | | `TelemetryDeck.Device.systemVersion` | `EnvironmentParameterProvider` | | `TelemetryDeck.Device.brand` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.timeZone` | `EnvironmentParameterProvider` | | `TelemetryDeck.AppInfo.buildNumber` | `EnvironmentParameterProvider` | | `TelemetryDeck.AppInfo.version` | `EnvironmentParameterProvider` | | `TelemetryDeck.AppInfo.versionAndBuildNumber` | `EnvironmentParameterProvider` | -| `TelemetryDeck.RunContext.locale` | `EnvironmentParameterProvider` | -| `TelemetryDeck.RunContext.targetEnvironment` | `EnvironmentParameterProvider` | | `TelemetryDeck.SDK.name` | `EnvironmentParameterProvider` | | `TelemetryDeck.SDK.version` | `EnvironmentParameterProvider` | | `TelemetryDeck.SDK.nameAndVersion` | `EnvironmentParameterProvider` | +| `TelemetryDeck.RunContext.locale` | `PlatformContextProvider` | +| `TelemetryDeck.RunContext.targetEnvironment` | `PlatformContextProvider` | +| `TelemetryDeck.RunContext.isSideLoaded` | `PlatformContextProvider` | +| `TelemetryDeck.RunContext.sourceMarketplace` | `PlatformContextProvider` | See [Custom Telemetry](#custom-telemetry) on how to implement your own parameter enrichment. @@ -247,11 +250,11 @@ After: * If you were using `queue()` to send signals, you will need to rename the method to `TelemetryDeck.signal()`. * If you had a custom provider configuration, please replace the corresponding providers as follows: -| Provider (old name) | Provider (new, 3.0+) | -|---------------------------------|-------------------------------------------------| -| `AppLifecycleTelemetryProvider` | `SessionAppProvider`, `SessionActivityProvider` | -| `SessionProvider` | `SessionAppProvider` | -| `EnvironmentMetadataProvider` | `EnvironmentParameterProvider` | +| Provider (old name) | Provider (new, 3.0+) | +|---------------------------------|-----------------------------------------------------------| +| `AppLifecycleTelemetryProvider` | `SessionAppProvider`, `SessionActivityProvider` | +| `SessionProvider` | `SessionAppProvider` | +| `EnvironmentMetadataProvider` | `EnvironmentParameterProvider`, `PlatformContextProvider` | > [!TIP] diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt index 39dd874..24e43e3 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeck.kt @@ -5,7 +5,8 @@ import android.content.Context import android.content.pm.ApplicationInfo import com.telemetrydeck.sdk.providers.EnvironmentParameterProvider import com.telemetrydeck.sdk.providers.SessionAppProvider -import com.telemetrydeck.sdk.signals.Navigation +import com.telemetrydeck.sdk.params.Navigation +import com.telemetrydeck.sdk.providers.PlatformContextProvider import java.lang.ref.WeakReference import java.net.URL import java.security.MessageDigest @@ -15,9 +16,7 @@ import kotlin.Result.Companion.success class TelemetryDeck( override val configuration: TelemetryManagerConfiguration, - val providers: List = listOf( - SessionAppProvider() - ) + val providers: List ): TelemetryDeckClient, TelemetryDeckSignalProcessor { var cache: SignalCache? = null var logger: DebugLogger? = null @@ -41,13 +40,13 @@ class TelemetryDeck( navigationStatus.applyDestination(destinationPath) val params: Map = mapOf( - Navigation.SchemaVersion.signalName to "1", - Navigation.Identifier.signalName to "$sourcePath -> $destinationPath", - Navigation.SourcePath.signalName to sourcePath, - Navigation.DestinationPath.signalName to destinationPath + Navigation.SchemaVersion.paramName to "1", + Navigation.Identifier.paramName to "$sourcePath -> $destinationPath", + Navigation.SourcePath.paramName to sourcePath, + Navigation.DestinationPath.paramName to destinationPath ) - signal(SignalType.TelemetryDeckNavigationPathChanged.type, params = params, customUserID = clientUser) + signal(com.telemetrydeck.sdk.signals.Navigation.PathChanged.signalName, params = params, customUserID = clientUser) } override fun navigate(destinationPath: String, clientUser: String?) { @@ -164,7 +163,8 @@ class TelemetryDeck( internal val defaultTelemetryProviders: List get() = listOf( SessionAppProvider(), - EnvironmentParameterProvider() + EnvironmentParameterProvider(), + PlatformContextProvider() ) // TelemetryManager singleton diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/AppInfo.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/AppInfo.kt similarity index 65% rename from lib/src/main/java/com/telemetrydeck/sdk/signals/AppInfo.kt rename to lib/src/main/java/com/telemetrydeck/sdk/params/AppInfo.kt index 392ace2..f89a295 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/signals/AppInfo.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/params/AppInfo.kt @@ -1,6 +1,6 @@ -package com.telemetrydeck.sdk.signals +package com.telemetrydeck.sdk.params -internal enum class AppInfo(val signalName: String) { +internal enum class AppInfo(val paramName: String) { BuildNumber("TelemetryDeck.AppInfo.buildNumber"), Version("TelemetryDeck.AppInfo.version"), VersionAndBuildNumber("TelemetryDeck.AppInfo.versionAndBuildNumber"), diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/Device.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt similarity index 83% rename from lib/src/main/java/com/telemetrydeck/sdk/signals/Device.kt rename to lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt index 2dd873d..22e8775 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/signals/Device.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt @@ -1,13 +1,12 @@ -package com.telemetrydeck.sdk.signals +package com.telemetrydeck.sdk.params // TODO: add more device parameters from the Swift SDK: //"TelemetryDeck.Device.orientation": Self.orientation, //"TelemetryDeck.Device.screenResolutionHeight": Self.screenResolutionHeight, //"TelemetryDeck.Device.screenResolutionWidth": Self.screenResolutionWidth, -//"TelemetryDeck.Device.timeZone": Self.timeZone, -internal enum class Device(val signalName: String) { +internal enum class Device(val paramName: String) { Architecture("TelemetryDeck.Device.architecture"), ModelName("TelemetryDeck.Device.modelName"), OperatingSystem("TelemetryDeck.Device.operatingSystem"), @@ -16,4 +15,5 @@ internal enum class Device(val signalName: String) { SystemMajorVersion("TelemetryDeck.Device.systemMajorVersion"), SystemVersion("TelemetryDeck.Device.systemVersion"), Brand("TelemetryDeck.Device.brand"), + TimeZone("TelemetryDeck.Device.timeZone"), } \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/params/Navigation.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/Navigation.kt new file mode 100644 index 0000000..8ea8904 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/params/Navigation.kt @@ -0,0 +1,8 @@ +package com.telemetrydeck.sdk.params + +internal enum class Navigation(val paramName: String) { + SchemaVersion("TelemetryDeck.Navigation.schemaVersion"), + Identifier("TelemetryDeck.Navigation.identifier"), + SourcePath("TelemetryDeck.Navigation.sourcePath"), + DestinationPath("TelemetryDeck.Navigation.destinationPath"), +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/RunContext.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/RunContext.kt similarity index 66% rename from lib/src/main/java/com/telemetrydeck/sdk/signals/RunContext.kt rename to lib/src/main/java/com/telemetrydeck/sdk/params/RunContext.kt index bd3b3e8..6250e44 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/signals/RunContext.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/params/RunContext.kt @@ -1,14 +1,15 @@ -package com.telemetrydeck.sdk.signals +package com.telemetrydeck.sdk.params -//"TelemetryDeck.RunContext.isAppStore": "\(Self.isAppStore)", //"TelemetryDeck.RunContext.isDebug": "\(Self.isDebug)", //"TelemetryDeck.RunContext.isSimulator": "\(Self.isSimulator)", //"TelemetryDeck.RunContext.isTestFlight": "\(Self.isTestFlight)", //"TelemetryDeck.RunContext.language": Self.appLanguage, //"TelemetryDeck.RunContext.targetEnvironment": Self.targetEnvironment, -internal enum class RunContext(val signalName: String) { +internal enum class RunContext(val paramName: String) { Locale("TelemetryDeck.RunContext.locale"), TargetEnvironment("TelemetryDeck.RunContext.targetEnvironment"), + IsSideLoaded("TelemetryDeck.RunContext.isSideLoaded"), + SourceMarketPlace("TelemetryDeck.RunContext.sourceMarketplace"), } \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/SDK.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/SDK.kt similarity index 60% rename from lib/src/main/java/com/telemetrydeck/sdk/signals/SDK.kt rename to lib/src/main/java/com/telemetrydeck/sdk/params/SDK.kt index a0e50d0..77c2235 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/signals/SDK.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/params/SDK.kt @@ -1,6 +1,6 @@ -package com.telemetrydeck.sdk.signals +package com.telemetrydeck.sdk.params -internal enum class SDK(val signalName: String) { +internal enum class SDK(val paramName: String) { Name("TelemetryDeck.SDK.name"), Version("TelemetryDeck.SDK.version"), NameAndVersion("TelemetryDeck.SDK.nameAndVersion"), diff --git a/lib/src/main/java/com/telemetrydeck/sdk/platform/AppInstallationInfo.kt b/lib/src/main/java/com/telemetrydeck/sdk/platform/AppInstallationInfo.kt new file mode 100644 index 0000000..f6bd618 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/platform/AppInstallationInfo.kt @@ -0,0 +1,7 @@ +package com.telemetrydeck.sdk.platform + +internal data class AppInstallationInfo( + val packageName: String, + val isSideLoaded: Boolean, + val sourceMarketPlace: String? +) \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/platform/PackageInfo.kt b/lib/src/main/java/com/telemetrydeck/sdk/platform/PackageInfo.kt new file mode 100644 index 0000000..6e6214c --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/platform/PackageInfo.kt @@ -0,0 +1,43 @@ +package com.telemetrydeck.sdk.platform + +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Build +import java.util.Calendar +import java.util.TimeZone + + +internal fun getAppInstallationInfo(context: Context): AppInstallationInfo? { + val packageInfo = getPackageInfo(context) + ?: // we can't obtain further details without package information + return null + + val sideLoaded = context.packageManager.getInstallerPackageName(packageInfo.packageName) == null + return AppInstallationInfo( + packageName = packageInfo.packageName, + isSideLoaded = sideLoaded, + sourceMarketPlace = null + ) +} + +internal fun getPackageInfo(context: Context): PackageInfo? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return context + .packageManager + .getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(0)); + } else { + @Suppress("DEPRECATION") + return context.packageManager.getPackageInfo(context.packageName, 0); + } +} + +internal fun getTimeZone(context: Context): TimeZone? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (!context.resources.configuration.locales.isEmpty) { + val locale = context.resources.configuration.locales[0] + return Calendar.getInstance(locale).getTimeZone() + } + } + return Calendar.getInstance().getTimeZone() +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt index f4f0542..13060cb 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt @@ -6,12 +6,11 @@ import com.telemetrydeck.sdk.BuildConfig import com.telemetrydeck.sdk.ManifestMetadataReader import com.telemetrydeck.sdk.TelemetryDeckClient import com.telemetrydeck.sdk.TelemetryDeckProvider -import com.telemetrydeck.sdk.signals.AppInfo -import com.telemetrydeck.sdk.signals.Device -import com.telemetrydeck.sdk.signals.RunContext -import com.telemetrydeck.sdk.signals.SDK +import com.telemetrydeck.sdk.platform.getTimeZone +import com.telemetrydeck.sdk.params.AppInfo +import com.telemetrydeck.sdk.params.Device +import com.telemetrydeck.sdk.params.SDK import java.lang.ref.WeakReference -import java.util.Locale /** * This provider enriches outgoing signals with additional parameters describing the current environment. @@ -20,10 +19,11 @@ import java.util.Locale * - information about the device running the application, such as operating system, model name, or architecture. * - information about the TelemetryDeck SDK, such as its name or version number. */ -class EnvironmentParameterProvider : TelemetryDeckProvider { +class EnvironmentParameterProvider : TelemetryDeckProvider { private var enabled: Boolean = true private var manager: WeakReference? = null private var metadata = mutableMapOf() + // The approach from the SwiftSDK is not compatible here as we need to evaluate for platform capabilities // In case of Kotlin Multiplatform, a per-platform value can be provided // For now, we're defaulting to "Android" @@ -37,12 +37,20 @@ class EnvironmentParameterProvider : TelemetryDeckProvider { if (ctx != null) { val appVersion = ManifestMetadataReader.getAppVersion(ctx) if (!appVersion.isNullOrEmpty()) { - metadata[AppInfo.Version.signalName] = appVersion + metadata[AppInfo.Version.paramName] = appVersion } ManifestMetadataReader.getBuildNumber(ctx)?.let { buildNumber -> - metadata[AppInfo.BuildNumber.signalName] = buildNumber.toString() - metadata[AppInfo.VersionAndBuildNumber.signalName] = "$appVersion (build $buildNumber)" + metadata[AppInfo.BuildNumber.paramName] = buildNumber.toString() + metadata[AppInfo.VersionAndBuildNumber.paramName] = + "$appVersion (build $buildNumber)" + } + + // determine the current time zone + val timeZoneInfo = getTimeZone(ctx.applicationContext) + if (timeZoneInfo != null) { + metadata[Device.TimeZone.paramName] = timeZoneInfo.displayName } + } else { this.manager?.get()?.debugLogger?.error("EnvironmentParameterProvider requires a context but received null. Signals will contain incomplete metadata.") } @@ -55,47 +63,41 @@ class EnvironmentParameterProvider : TelemetryDeckProvider { ) } else { // Device metadata - metadata[Device.Platform.signalName] = platform + metadata[Device.Platform.paramName] = platform val release = android.os.Build.VERSION.RELEASE val sdkVersion = android.os.Build.VERSION.SDK_INT - metadata[Device.SystemVersion.signalName] = "$platform $release (SDK: $sdkVersion)" + metadata[Device.SystemVersion.paramName] = "$platform $release (SDK: $sdkVersion)" if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { val versionInfo = VersionInfo.getInstance(release) - metadata[Device.SystemMajorVersion.signalName] = "${versionInfo.major}" - metadata[Device.SystemMajorMinorVersion.signalName] = "${versionInfo.major}.${versionInfo.minor}" + metadata[Device.SystemMajorVersion.paramName] = "${versionInfo.major}" + metadata[Device.SystemMajorMinorVersion.paramName] = + "${versionInfo.major}.${versionInfo.minor}" } else { val versionInfo = release.split(".") val major = versionInfo.elementAtOrNull(0) ?: "0" val minor = versionInfo.elementAtOrNull(1) ?: "0" - metadata[Device.SystemMajorVersion.signalName] = major - metadata[Device.SystemMajorMinorVersion.signalName] = "$major.$minor" + metadata[Device.SystemMajorVersion.paramName] = major + metadata[Device.SystemMajorMinorVersion.paramName] = "$major.$minor" } } if (android.os.Build.BRAND != null) { - metadata[Device.Brand.signalName] = android.os.Build.BRAND + metadata[Device.Brand.paramName] = android.os.Build.BRAND } - if (android.os.Build.DEVICE != null) { - metadata[RunContext.TargetEnvironment.signalName] = android.os.Build.DEVICE + if (android.os.Build.MODEL != null && android.os.Build.PRODUCT != null) { + metadata[Device.ModelName.paramName] = + "${android.os.Build.MODEL} (${android.os.Build.PRODUCT})" } - if (android.os.Build.MODEL != null && android.os.Build.PRODUCT != null) { - metadata[Device.ModelName.signalName] = "${android.os.Build.MODEL} (${android.os.Build.PRODUCT})" - } - metadata[Device.Architecture.signalName] = System.getProperty("os.arch") ?: "" - metadata[Device.OperatingSystem.signalName] = os + metadata[Device.Architecture.paramName] = System.getProperty("os.arch") ?: "" + metadata[Device.OperatingSystem.paramName] = os // SDK Metadata - metadata[SDK.Name.signalName] = sdkName + metadata[SDK.Name.paramName] = sdkName // TODO: create a build property to pass the maven coordinates of the library - metadata[SDK.Version.signalName] = BuildConfig.LIBRARY_PACKAGE_NAME - metadata[SDK.NameAndVersion.signalName] = "$sdkName ${BuildConfig.LIBRARY_PACKAGE_NAME}" - - - // RunContext Metadata - metadata[RunContext.Locale.signalName] = Locale.getDefault().displayName - + metadata[SDK.Version.paramName] = BuildConfig.LIBRARY_PACKAGE_NAME + metadata[SDK.NameAndVersion.paramName] = "$sdkName ${BuildConfig.LIBRARY_PACKAGE_NAME}" this.enabled = true } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/PlatformContextProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/PlatformContextProvider.kt new file mode 100644 index 0000000..26ab8a4 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/PlatformContextProvider.kt @@ -0,0 +1,61 @@ +package com.telemetrydeck.sdk.providers + +import android.app.Application +import com.telemetrydeck.sdk.TelemetryDeckClient +import com.telemetrydeck.sdk.TelemetryDeckProvider +import com.telemetrydeck.sdk.platform.getAppInstallationInfo +import com.telemetrydeck.sdk.params.RunContext +import java.lang.ref.WeakReference +import java.util.Locale + +class PlatformContextProvider: TelemetryDeckProvider { + private var enabled: Boolean = true + private var manager: WeakReference? = null + private var metadata = mutableMapOf() + + override fun register(ctx: Application?, client: TelemetryDeckClient) { + this.manager = WeakReference(client) + if (ctx == null) { + this.manager?.get()?.debugLogger?.error("RunContextProvider requires a context but received null. Signals will contain incomplete metadata.") + this.enabled = false + return + } + + if (android.os.Build.DEVICE != null) { + metadata[RunContext.TargetEnvironment.paramName] = android.os.Build.DEVICE + } + + // read the default locale + metadata[RunContext.Locale.paramName] = Locale.getDefault().displayName + + // determine if the app was installed by a trusted marketplace + val appInfo = getAppInstallationInfo(ctx) + if (appInfo != null) { + metadata[RunContext.IsSideLoaded.paramName] = "${appInfo.isSideLoaded}" + if (appInfo.sourceMarketPlace != null) { + metadata[RunContext.SourceMarketPlace.paramName] = "${appInfo.sourceMarketPlace}" + } + } + + + this.enabled = true + } + + override fun stop() { + this.enabled = false + } + + override fun enrich( + signalType: String, + clientUser: String?, + additionalPayload: Map + ): Map { + val signalPayload = additionalPayload.toMutableMap() + for (item in metadata) { + if (!signalPayload.containsKey(item.key)) { + signalPayload[item.key] = item.value + } + } + return signalPayload + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/signals/Navigation.kt b/lib/src/main/java/com/telemetrydeck/sdk/signals/Navigation.kt index c2ae821..eacd7ea 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/signals/Navigation.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/signals/Navigation.kt @@ -1,8 +1,5 @@ package com.telemetrydeck.sdk.signals internal enum class Navigation(val signalName: String) { - SchemaVersion("TelemetryDeck.Navigation.schemaVersion"), - Identifier("TelemetryDeck.Navigation.identifier"), - SourcePath("TelemetryDeck.Navigation.sourcePath"), - DestinationPath("TelemetryDeck.Navigation.destinationPath"), + PathChanged("TelemetryDeck.Navigation.pathChanged"), } \ No newline at end of file diff --git a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt index b34d868..aca0c26 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt @@ -242,7 +242,7 @@ class TelemetryDeckTests { .build(null) sut.signal("type") - Assert.assertEquals(3, sut.providers.count()) + Assert.assertEquals(4, sut.providers.count()) Assert.assertTrue(sut.providers.last() is TestTelemetryDeckProvider) } From 934c6084dd9247f50b6787da30aa60294deba18e Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 4 Oct 2024 17:54:15 +0200 Subject: [PATCH 14/33] feat: add orientation and screen metrics to params, improve locale detection --- README.md | 6 + .../com/telemetrydeck/sdk/params/Device.kt | 4 + .../sdk/platform/DeviceOrientation.kt | 8 ++ .../telemetrydeck/sdk/platform/PackageInfo.kt | 43 ------- .../sdk/platform/PlatformReader.kt | 109 ++++++++++++++++++ .../sdk/platform/ScreenMetrics.kt | 3 + .../providers/EnvironmentParameterProvider.kt | 2 +- .../sdk/providers/PlatformContextProvider.kt | 57 +++++++-- 8 files changed, 181 insertions(+), 51 deletions(-) create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/platform/DeviceOrientation.kt delete mode 100644 lib/src/main/java/com/telemetrydeck/sdk/platform/PackageInfo.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt create mode 100644 lib/src/main/java/com/telemetrydeck/sdk/platform/ScreenMetrics.kt diff --git a/README.md b/README.md index 66bbfd6..4e92601 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,10 @@ By default, TelemetryDeck will include the following environment parameters for | `TelemetryDeck.Device.systemMajorMinorVersion` | `EnvironmentParameterProvider` | | `TelemetryDeck.Device.systemMajorVersion` | `EnvironmentParameterProvider` | | `TelemetryDeck.Device.systemVersion` | `EnvironmentParameterProvider` | +| `TelemetryDeck.Device.orientation` | `PlatformContextProvider` | +| `TelemetryDeck.Device.screenDensity` | `PlatformContextProvider` | +| `TelemetryDeck.Device.screenResolutionHeight` | `PlatformContextProvider` | +| `TelemetryDeck.Device.screenResolutionWidth` | `PlatformContextProvider` | | `TelemetryDeck.Device.brand` | `EnvironmentParameterProvider` | | `TelemetryDeck.Device.timeZone` | `EnvironmentParameterProvider` | | `TelemetryDeck.AppInfo.buildNumber` | `EnvironmentParameterProvider` | @@ -114,6 +118,7 @@ By default, TelemetryDeck will include the following environment parameters for | `TelemetryDeck.RunContext.isSideLoaded` | `PlatformContextProvider` | | `TelemetryDeck.RunContext.sourceMarketplace` | `PlatformContextProvider` | + See [Custom Telemetry](#custom-telemetry) on how to implement your own parameter enrichment. ## Custom Telemetry @@ -182,6 +187,7 @@ You can also completely disable or override the default providers with your own. - `SessionAppProvider` - Emits signals for application and activity lifecycle events. This provider is tasked with resetting the sessionID when `sendNewSessionBeganSignal` is enabled. - `SessionActivityProvider` - Emits signals for application and activity lifecycle events. This provider is not enabled by default. - `EnvironmentParameterProvider` - Adds environment and device information to outgoing Signals. This provider overrides the `enrich` method in order to append additional metadata for all signals before sending them. +- `PlatformContextProvider` - Adds environment and device information which may change over time like the current timezone and screen metrics. ```kotlin // Append a custom provider diff --git a/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt index 22e8775..ffc0f22 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt @@ -16,4 +16,8 @@ internal enum class Device(val paramName: String) { SystemVersion("TelemetryDeck.Device.systemVersion"), Brand("TelemetryDeck.Device.brand"), TimeZone("TelemetryDeck.Device.timeZone"), + Orientation("TelemetryDeck.Device.orientation"), + ScreenDensity("TelemetryDeck.Device.screenDensity"), + ScreenHeight("TelemetryDeck.Device.screenResolutionHeight"), + ScreenWidth("TelemetryDeck.Device.screenResolutionWidth") } \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/platform/DeviceOrientation.kt b/lib/src/main/java/com/telemetrydeck/sdk/platform/DeviceOrientation.kt new file mode 100644 index 0000000..6065310 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/platform/DeviceOrientation.kt @@ -0,0 +1,8 @@ +package com.telemetrydeck.sdk.platform + +internal enum class DeviceOrientation(val orientationName: String) { + Portrait("Portrait"), + Landscape("Landscape"), + Square("Square"), + Unknown("Unknown"), +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/platform/PackageInfo.kt b/lib/src/main/java/com/telemetrydeck/sdk/platform/PackageInfo.kt deleted file mode 100644 index 6e6214c..0000000 --- a/lib/src/main/java/com/telemetrydeck/sdk/platform/PackageInfo.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.telemetrydeck.sdk.platform - -import android.content.Context -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.os.Build -import java.util.Calendar -import java.util.TimeZone - - -internal fun getAppInstallationInfo(context: Context): AppInstallationInfo? { - val packageInfo = getPackageInfo(context) - ?: // we can't obtain further details without package information - return null - - val sideLoaded = context.packageManager.getInstallerPackageName(packageInfo.packageName) == null - return AppInstallationInfo( - packageName = packageInfo.packageName, - isSideLoaded = sideLoaded, - sourceMarketPlace = null - ) -} - -internal fun getPackageInfo(context: Context): PackageInfo? { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - return context - .packageManager - .getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(0)); - } else { - @Suppress("DEPRECATION") - return context.packageManager.getPackageInfo(context.packageName, 0); - } -} - -internal fun getTimeZone(context: Context): TimeZone? { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (!context.resources.configuration.locales.isEmpty) { - val locale = context.resources.configuration.locales[0] - return Calendar.getInstance(locale).getTimeZone() - } - } - return Calendar.getInstance().getTimeZone() -} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt b/lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt new file mode 100644 index 0000000..ecb0738 --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt @@ -0,0 +1,109 @@ +package com.telemetrydeck.sdk.platform + +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Build +import com.telemetrydeck.sdk.DebugLogger +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone + + +internal fun getAppInstallationInfo(context: Context, logger: DebugLogger?): AppInstallationInfo? { + try { + val packageInfo = getPackageInfo(context, logger) + ?: // we can't obtain further details without package information + return null + + @Suppress("DEPRECATION") + val sideLoaded = + context.packageManager.getInstallerPackageName(packageInfo.packageName) == null + return AppInstallationInfo( + packageName = packageInfo.packageName, + isSideLoaded = sideLoaded, + sourceMarketPlace = null + ) + } catch (e: Exception) { + logger?.error("getAppInstallationInfo failed: $e ${e.stackTraceToString()}") + return null + } +} + +internal fun getPackageInfo(context: Context, logger: DebugLogger?): PackageInfo? { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return context + .packageManager + .getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(0)) + } else { + @Suppress("DEPRECATION") + return context.packageManager.getPackageInfo(context.packageName, 0) + } + } catch (e: Exception) { + logger?.error("getPackageInfo failed: $e ${e.stackTraceToString()}") + return null + } +} + +internal fun getTimeZone(context: Context, logger: DebugLogger?): TimeZone? { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (!context.resources.configuration.locales.isEmpty) { + val locale = context.resources.configuration.locales[0] + return Calendar.getInstance(locale).getTimeZone() + } + } + return Calendar.getInstance().getTimeZone() + } catch (e: Exception) { + logger?.error("getTimeZone failed: $e ${e.stackTraceToString()}") + return null + } + +} + +internal fun getDeviceOrientation(context: Context, logger: DebugLogger?): DeviceOrientation? { + try { + val orientation = context.resources.configuration.orientation + return when (orientation) { + android.content.res.Configuration.ORIENTATION_LANDSCAPE -> DeviceOrientation.Landscape + android.content.res.Configuration.ORIENTATION_PORTRAIT -> DeviceOrientation.Portrait + @Suppress("DEPRECATION") + android.content.res.Configuration.ORIENTATION_SQUARE -> DeviceOrientation.Square + else -> DeviceOrientation.Unknown + } + } catch (e: Exception) { + logger?.error("getDeviceOrientation failed: $e ${e.stackTraceToString()}") + return null + } +} + +internal fun getDisplayMetrics(context: Context, logger: DebugLogger?): ScreenMetrics? { + try { + val metrics = context.resources.displayMetrics + return ScreenMetrics( + width = metrics.widthPixels, + height = metrics.heightPixels, + density = metrics.densityDpi + ) + } catch (e: Exception) { + logger?.error("getDisplayMetrics failed: $e ${e.stackTraceToString()}") + return null + } +} + +internal fun getLocaleName(context: Context, logger: DebugLogger?): String? { + try { + val currentLocale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + context.resources.configuration.locales[0] + } else { + @Suppress("DEPRECATION") + context.resources.configuration.locale + } + + return currentLocale.displayName + } catch (e: Exception) { + logger?.error("getLocaleName failed: $e ${e.stackTraceToString()}") + return null + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/platform/ScreenMetrics.kt b/lib/src/main/java/com/telemetrydeck/sdk/platform/ScreenMetrics.kt new file mode 100644 index 0000000..99ccaea --- /dev/null +++ b/lib/src/main/java/com/telemetrydeck/sdk/platform/ScreenMetrics.kt @@ -0,0 +1,3 @@ +package com.telemetrydeck.sdk.platform + +internal data class ScreenMetrics(val width: Int, val height: Int, val density: Int) \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt index 13060cb..763934a 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt @@ -46,7 +46,7 @@ class EnvironmentParameterProvider : TelemetryDeckProvider { } // determine the current time zone - val timeZoneInfo = getTimeZone(ctx.applicationContext) + val timeZoneInfo = getTimeZone(ctx.applicationContext, this.manager?.get()?.debugLogger) if (timeZoneInfo != null) { metadata[Device.TimeZone.paramName] = timeZoneInfo.displayName } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/PlatformContextProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/PlatformContextProvider.kt index 26ab8a4..304878d 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/providers/PlatformContextProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/PlatformContextProvider.kt @@ -1,20 +1,27 @@ package com.telemetrydeck.sdk.providers import android.app.Application +import android.content.Context import com.telemetrydeck.sdk.TelemetryDeckClient import com.telemetrydeck.sdk.TelemetryDeckProvider -import com.telemetrydeck.sdk.platform.getAppInstallationInfo +import com.telemetrydeck.sdk.params.Device import com.telemetrydeck.sdk.params.RunContext +import com.telemetrydeck.sdk.platform.getAppInstallationInfo +import com.telemetrydeck.sdk.platform.getDeviceOrientation +import com.telemetrydeck.sdk.platform.getDisplayMetrics +import com.telemetrydeck.sdk.platform.getLocaleName import java.lang.ref.WeakReference -import java.util.Locale -class PlatformContextProvider: TelemetryDeckProvider { +class PlatformContextProvider : TelemetryDeckProvider { private var enabled: Boolean = true private var manager: WeakReference? = null + private var appContext: WeakReference? = null private var metadata = mutableMapOf() override fun register(ctx: Application?, client: TelemetryDeckClient) { this.manager = WeakReference(client) + this.appContext = WeakReference(ctx?.applicationContext) + if (ctx == null) { this.manager?.get()?.debugLogger?.error("RunContextProvider requires a context but received null. Signals will contain incomplete metadata.") this.enabled = false @@ -25,11 +32,8 @@ class PlatformContextProvider: TelemetryDeckProvider { metadata[RunContext.TargetEnvironment.paramName] = android.os.Build.DEVICE } - // read the default locale - metadata[RunContext.Locale.paramName] = Locale.getDefault().displayName - // determine if the app was installed by a trusted marketplace - val appInfo = getAppInstallationInfo(ctx) + val appInfo = getAppInstallationInfo(ctx, this.manager?.get()?.debugLogger) if (appInfo != null) { metadata[RunContext.IsSideLoaded.paramName] = "${appInfo.isSideLoaded}" if (appInfo.sourceMarketPlace != null) { @@ -56,6 +60,45 @@ class PlatformContextProvider: TelemetryDeckProvider { signalPayload[item.key] = item.value } } + + for (item in getDynamicAttributes()) { + if (!signalPayload.containsKey(item.key)) { + signalPayload[item.key] = item.value + } + } return signalPayload } + + // TODO: Use onConfigurationChanged instead + + private fun getDynamicAttributes(): Map { + val ctx = this.appContext?.get() + ?: // can't read without a context! + return emptyMap() + + val attributes = mutableMapOf() + + // get current orientation + val deviceOrientation = getDeviceOrientation(ctx, this.manager?.get()?.debugLogger) + if (deviceOrientation != null) { + attributes[Device.Orientation.paramName] = deviceOrientation.orientationName + } + + // get current display metrics + val displayMetrics = getDisplayMetrics(ctx, this.manager?.get()?.debugLogger) + if (displayMetrics != null) { + attributes[Device.ScreenWidth.paramName] = "$displayMetrics.width" + attributes[Device.ScreenHeight.paramName] = "$displayMetrics.height" + attributes[Device.ScreenDensity.paramName] = "$displayMetrics.density" + } + + // read the default locale + val localeName: String? = getLocaleName(ctx, this.manager?.get()?.debugLogger) + if (localeName != null) { + attributes[RunContext.Locale.paramName] = localeName + } + + return attributes + } + } \ No newline at end of file From 0790389a3fab208b3087659cbd6f18877bad8729 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 4 Oct 2024 18:07:26 +0200 Subject: [PATCH 15/33] chore: reformat code --- README.md | 3 +- lib/src/main/AndroidManifest.xml | 12 +++--- .../sdk/AppLifecycleTelemetryProvider.kt | 5 ++- .../com/telemetrydeck/sdk/DateSerializer.kt | 6 ++- .../sdk/EnvironmentMetadataProvider.kt | 13 ++++-- .../sdk/ManifestMetadataReader.kt | 43 ++++++++++++------- .../telemetrydeck/sdk/MemorySignalCache.kt | 3 +- .../com/telemetrydeck/sdk/SessionProvider.kt | 10 ++++- .../main/java/com/telemetrydeck/sdk/Signal.kt | 10 ++++- .../sdk/TelemetryBroadcastTimer.kt | 13 ++++-- .../com/telemetrydeck/sdk/TelemetryClient.kt | 13 +++--- .../com/telemetrydeck/sdk/TelemetryDeck.kt | 32 +++++++++----- .../sdk/TelemetryDeckInitProvider.kt | 1 + .../sdk/TelemetryDeckProvider.kt | 8 ++-- .../com/telemetrydeck/sdk/TelemetryManager.kt | 5 ++- .../sdk/TelemetryManagerConfiguration.kt | 4 +- .../sdk/TelemetryManagerDebugLogger.kt | 2 +- .../telemetrydeck/sdk/TelemetryProvider.kt | 8 ++-- .../com/telemetrydeck/sdk/UUIDSerializer.kt | 4 +- .../com/telemetrydeck/sdk/params/Device.kt | 7 +-- .../telemetrydeck/sdk/params/RunContext.kt | 12 +++--- .../java/com/telemetrydeck/sdk/params/SDK.kt | 1 + .../sdk/platform/PlatformReader.kt | 1 + .../providers/EnvironmentParameterProvider.kt | 7 +-- .../sdk/providers/SessionActivityProvider.kt | 2 +- .../sdk/providers/SessionAppProvider.kt | 2 +- .../EnvironmentParameterProviderTest.kt | 14 ++++++ 27 files changed, 161 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 4e92601..9cb590b 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ TelemetryDeck.signal("appLaunchedRegularly") ### Environment Parameters -By default, TelemetryDeck will include the following environment parameters for each outgoing signal +By default, TelemetryDeck SDK for Kotlin will include the following environment parameters for each outgoing signal | Signal name | Provider | @@ -113,6 +113,7 @@ By default, TelemetryDeck will include the following environment parameters for | `TelemetryDeck.SDK.name` | `EnvironmentParameterProvider` | | `TelemetryDeck.SDK.version` | `EnvironmentParameterProvider` | | `TelemetryDeck.SDK.nameAndVersion` | `EnvironmentParameterProvider` | +| `TelemetryDeck.SDK.buildType` | `EnvironmentParameterProvider` | | `TelemetryDeck.RunContext.locale` | `PlatformContextProvider` | | `TelemetryDeck.RunContext.targetEnvironment` | `PlatformContextProvider` | | `TelemetryDeck.RunContext.isSideLoaded` | `PlatformContextProvider` | diff --git a/lib/src/main/AndroidManifest.xml b/lib/src/main/AndroidManifest.xml index f2db845..39b4f09 100644 --- a/lib/src/main/AndroidManifest.xml +++ b/lib/src/main/AndroidManifest.xml @@ -1,12 +1,12 @@ - + - - + + \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt index a745837..72fe185 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt @@ -11,7 +11,10 @@ import java.lang.ref.WeakReference /** * Emits signals for application and activity lifecycle events. */ -@Deprecated("Use SessionAppProvider", ReplaceWith("SessionAppProvider", "com.telemetrydeck.sdk.providers.SessionAppProvider")) +@Deprecated( + "Use SessionAppProvider", + ReplaceWith("SessionAppProvider", "com.telemetrydeck.sdk.providers.SessionAppProvider") +) class AppLifecycleTelemetryProvider : TelemetryProvider, Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { private var manager: WeakReference? = null diff --git a/lib/src/main/java/com/telemetrydeck/sdk/DateSerializer.kt b/lib/src/main/java/com/telemetrydeck/sdk/DateSerializer.kt index ed0f2d2..473b2ff 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/DateSerializer.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/DateSerializer.kt @@ -6,10 +6,12 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import java.text.DateFormat import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale +import java.util.TimeZone @Serializer(forClass = Date::class) -internal object DateSerializer: KSerializer { +internal object DateSerializer : KSerializer { private val df: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) init { diff --git a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt index bd4f36b..ee861c9 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt @@ -2,12 +2,18 @@ package com.telemetrydeck.sdk import android.app.Application import android.icu.util.VersionInfo -import java.util.* +import java.util.Locale /** * Adds environment and device information to outgoing Signals. */ -@Deprecated("Use EnvironmentParameterProvider", ReplaceWith("EnvironmentParameterProvider", "com.telemetrydeck.sdk.providers.EnvironmentParameterProvider")) +@Deprecated( + "Use EnvironmentParameterProvider", + ReplaceWith( + "EnvironmentParameterProvider", + "com.telemetrydeck.sdk.providers.EnvironmentParameterProvider" + ) +) class EnvironmentMetadataProvider : TelemetryProvider { private var enabled: Boolean = true private var metadata = mutableMapOf() @@ -40,7 +46,8 @@ class EnvironmentMetadataProvider : TelemetryProvider { } else { val versionInfo = release.split(".") metadata["majorSystemVersion"] = versionInfo.elementAtOrNull(0) ?: "0" - metadata["majorMinorSystemVersion"] = "${versionInfo.elementAtOrNull(0) ?: "0"}.${versionInfo.elementAtOrNull(1) ?: "0"}" + metadata["majorMinorSystemVersion"] = + "${versionInfo.elementAtOrNull(0) ?: "0"}.${versionInfo.elementAtOrNull(1) ?: "0"}" } } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt b/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt index ae105fa..2059cf0 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt @@ -7,11 +7,13 @@ import android.content.pm.PackageManager import android.os.Bundle import androidx.core.content.pm.PackageInfoCompat import java.net.URL -import java.util.* +import java.util.UUID - -internal data class ManifestMetadata(val config: TelemetryManagerConfiguration, val version: TelemetryDeckManifestVersion) +internal data class ManifestMetadata( + val config: TelemetryManagerConfiguration, + val version: TelemetryDeckManifestVersion +) internal class ManifestMetadataReader { companion object { @@ -44,7 +46,7 @@ internal class ManifestMetadataReader { private fun getPackageInfo(context: Context): PackageInfo? { return try { - context.packageManager.getPackageInfo(context.packageName, 0) + context.packageManager.getPackageInfo(context.packageName, 0) } catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() null @@ -63,12 +65,16 @@ internal class ManifestMetadataReader { * Creates an instance of TelemetryManagerConfiguration by reading the manifest. * This method is to be used after the grand rename. */ - private fun getGrandRenameConfigurationFromManifest(context: Context, bundle: Bundle): TelemetryManagerConfiguration? { + private fun getGrandRenameConfigurationFromManifest( + context: Context, + bundle: Bundle + ): TelemetryManagerConfiguration? { val appID = bundle.getString(TelemetryDeckManifestSettings.AppID.key) ?: return null val config = TelemetryManagerConfiguration(appID) if (bundle.containsKey(TelemetryDeckManifestSettings.ShowDebugLogs.key)) { - config.showDebugLogs = bundle.getBoolean(TelemetryDeckManifestSettings.ShowDebugLogs.key) + config.showDebugLogs = + bundle.getBoolean(TelemetryDeckManifestSettings.ShowDebugLogs.key) } val apiBaseUrl = bundle.getString(TelemetryDeckManifestSettings.ApiBaseURL.key) @@ -77,7 +83,8 @@ internal class ManifestMetadataReader { } if (bundle.containsKey(TelemetryDeckManifestSettings.SendNewSessionBeganSignal.key)) { - config.sendNewSessionBeganSignal = bundle.getBoolean(TelemetryDeckManifestSettings.SendNewSessionBeganSignal.key) + config.sendNewSessionBeganSignal = + bundle.getBoolean(TelemetryDeckManifestSettings.SendNewSessionBeganSignal.key) } val sessionID = bundle.getString(TelemetryDeckManifestSettings.SessionID.key) @@ -88,16 +95,17 @@ internal class ManifestMetadataReader { if (bundle.containsKey(TelemetryDeckManifestSettings.TestMode.key)) { config.testMode = bundle.getBoolean(TelemetryDeckManifestSettings.TestMode.key) } else { - config.testMode = 0 != (context.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_DEBUGGABLE + config.testMode = + 0 != (context.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_DEBUGGABLE } val defaultUser = bundle.getString(TelemetryDeckManifestSettings.DefaultUser.key) - if(defaultUser != null) { + if (defaultUser != null) { config.defaultUser = defaultUser } val salt = bundle.getString(TelemetryDeckManifestSettings.Salt.key) - if(salt != null) { + if (salt != null) { config.salt = salt } @@ -107,7 +115,10 @@ internal class ManifestMetadataReader { /** * Creates an instance of TelemetryManagerConfiguration by reading the manifest. */ - private fun getConfigurationFromManifest(context: Context, bundle: Bundle): TelemetryManagerConfiguration? { + private fun getConfigurationFromManifest( + context: Context, + bundle: Bundle + ): TelemetryManagerConfiguration? { val appID = bundle.getString(ManifestSettings.AppID.key) ?: return null val config = TelemetryManagerConfiguration(appID) @@ -121,7 +132,8 @@ internal class ManifestMetadataReader { } if (bundle.containsKey(ManifestSettings.SendNewSessionBeganSignal.key)) { - config.sendNewSessionBeganSignal = bundle.getBoolean(ManifestSettings.SendNewSessionBeganSignal.key) + config.sendNewSessionBeganSignal = + bundle.getBoolean(ManifestSettings.SendNewSessionBeganSignal.key) } val sessionID = bundle.getString(ManifestSettings.SessionID.key) @@ -132,16 +144,17 @@ internal class ManifestMetadataReader { if (bundle.containsKey(ManifestSettings.TestMode.key)) { config.testMode = bundle.getBoolean(ManifestSettings.TestMode.key) } else { - config.testMode = 0 != (context.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_DEBUGGABLE + config.testMode = + 0 != (context.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_DEBUGGABLE } val defaultUser = bundle.getString(ManifestSettings.DefaultUser.key) - if(defaultUser != null) { + if (defaultUser != null) { config.defaultUser = defaultUser } val salt = bundle.getString(ManifestSettings.Salt.key) - if(salt != null) { + if (salt != null) { config.salt = salt } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/MemorySignalCache.kt b/lib/src/main/java/com/telemetrydeck/sdk/MemorySignalCache.kt index 2981c79..01a41ea 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/MemorySignalCache.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/MemorySignalCache.kt @@ -1,6 +1,7 @@ package com.telemetrydeck.sdk -class MemorySignalCache(private var signalQueue: MutableList = mutableListOf()): SignalCache { +class MemorySignalCache(private var signalQueue: MutableList = mutableListOf()) : + SignalCache { override fun add(signal: Signal) { synchronized(this) { diff --git a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt index 6f1e347..2e0d53e 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt @@ -9,8 +9,14 @@ import java.lang.ref.WeakReference /** * Monitors the app lifecycle in order to broadcast the NewSessionBegan signal. */ -@Deprecated("Use SessionActivityProvider", ReplaceWith("SessionActivityProvider", "com.telemetrydeck.sdk.providers.SessionActivityProvider")) -class SessionProvider: TelemetryProvider, DefaultLifecycleObserver { +@Deprecated( + "Use SessionActivityProvider", + ReplaceWith( + "SessionActivityProvider", + "com.telemetrydeck.sdk.providers.SessionActivityProvider" + ) +) +class SessionProvider : TelemetryProvider, DefaultLifecycleObserver { private var manager: WeakReference? = null override fun register(ctx: Application?, manager: TelemetryManager) { diff --git a/lib/src/main/java/com/telemetrydeck/sdk/Signal.kt b/lib/src/main/java/com/telemetrydeck/sdk/Signal.kt index 07edd6f..04bd2a0 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/Signal.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/Signal.kt @@ -1,7 +1,8 @@ package com.telemetrydeck.sdk import kotlinx.serialization.Serializable -import java.util.* +import java.util.Date +import java.util.UUID @Serializable data class Signal( @@ -50,5 +51,10 @@ data class Signal( */ var floatValue: Double? = null ) { - constructor(appID: UUID, signalType: String, clientUser: String, payload: SignalPayload) : this(appID=appID, type=signalType, clientUser = clientUser, payload = payload.asMultiValueDimension) + constructor(appID: UUID, signalType: String, clientUser: String, payload: SignalPayload) : this( + appID = appID, + type = signalType, + clientUser = clientUser, + payload = payload.asMultiValueDimension + ) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt index 5b6b2c5..25ad131 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt @@ -1,10 +1,16 @@ package com.telemetrydeck.sdk -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.ticker +import kotlinx.coroutines.launch import java.lang.ref.WeakReference -internal class TelemetryBroadcastTimer(private val manager: WeakReference, debugLogger: WeakReference) { +internal class TelemetryBroadcastTimer( + private val manager: WeakReference, + debugLogger: WeakReference +) { // broadcast begins with a 10s delay after initialization and fires every 10s. private val timerChannel = ticker(delayMillis = 10_000, initialDelayMillis = 10_000) @@ -13,6 +19,7 @@ internal class TelemetryBroadcastTimer(private val manager: WeakReference -): TelemetryDeckClient, TelemetryDeckSignalProcessor { +) : TelemetryDeckClient, TelemetryDeckSignalProcessor { var cache: SignalCache? = null var logger: DebugLogger? = null private val navigationStatus: NavigationStatus = MemoryNavigationStatus() @@ -46,7 +46,11 @@ class TelemetryDeck( Navigation.DestinationPath.paramName to destinationPath ) - signal(com.telemetrydeck.sdk.signals.Navigation.PathChanged.signalName, params = params, customUserID = clientUser) + signal( + com.telemetrydeck.sdk.signals.Navigation.PathChanged.signalName, + params = params, + customUserID = clientUser + ) } override fun navigate(destinationPath: String, clientUser: String?) { @@ -83,12 +87,14 @@ class TelemetryDeck( } override fun signal(signalName: String, customUserID: String?, params: Map) { - cache?.add(createSignal( - signalType = signalName, - clientUser = customUserID, - additionalPayload = params, - floatValue = null - )) + cache?.add( + createSignal( + signalType = signalName, + clientUser = customUserID, + additionalPayload = params, + floatValue = null + ) + ) } private suspend fun send( @@ -268,7 +274,11 @@ class TelemetryDeck( customUserID: String?, params: Map ) { - getInstance()?.signal(signalName = signalName, customUserID = customUserID, params = params) + getInstance()?.signal( + signalName = signalName, + customUserID = customUserID, + params = params + ) } override val signalCache: SignalCache? diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckInitProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckInitProvider.kt index 9906954..9b51087 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckInitProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckInitProvider.kt @@ -34,6 +34,7 @@ class TelemetryDeckInitProvider : ContentProvider() { builder.configuration(metadata.config) TelemetryManager.start(appContext, builder) } + TelemetryDeckManifestVersion.V2 -> { val builder = TelemetryDeck.Builder() builder.configuration(metadata.config) diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckProvider.kt index 6e5f0aa..eeb0b29 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckProvider.kt @@ -23,9 +23,11 @@ interface TelemetryDeckProvider { * * TelemetryManager calls this method all providers in order of registration. */ - fun enrich(signalType: String, - clientUser: String? = null, - additionalPayload: Map = emptyMap()): Map { + fun enrich( + signalType: String, + clientUser: String? = null, + additionalPayload: Map = emptyMap() + ): Map { return additionalPayload } } \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt index ebccba6..9f5adea 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt @@ -10,7 +10,10 @@ import java.util.UUID import kotlin.Result.Companion.failure import kotlin.Result.Companion.success -@Deprecated("Use TelemetryDeck instead", ReplaceWith("TelemetryDeck", "com.telemetrydeck.sdk.TelemetryDeck")) +@Deprecated( + "Use TelemetryDeck instead", + ReplaceWith("TelemetryDeck", "com.telemetrydeck.sdk.TelemetryDeck") +) class TelemetryManager( val configuration: TelemetryManagerConfiguration, val providers: List = listOf( diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerConfiguration.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerConfiguration.kt index 2818bf8..744eb59 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerConfiguration.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerConfiguration.kt @@ -1,7 +1,7 @@ package com.telemetrydeck.sdk import java.net.URL -import java.util.* +import java.util.UUID data class TelemetryManagerConfiguration( @@ -65,5 +65,5 @@ data class TelemetryManagerConfiguration( * */ var salt: String? = null, ) { - constructor(telemetryAppID: String): this(telemetryAppID=UUID.fromString(telemetryAppID)) + constructor(telemetryAppID: String) : this(telemetryAppID = UUID.fromString(telemetryAppID)) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerDebugLogger.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerDebugLogger.kt index 01f0559..adddee8 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerDebugLogger.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerDebugLogger.kt @@ -5,7 +5,7 @@ package com.telemetrydeck.sdk */ internal class TelemetryManagerDebugLogger { - companion object: DebugLogger { + companion object : DebugLogger { private const val tag: String = "TELEMETRYDECK" private var enabled: Boolean = true override fun error(message: String) { diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt index 9d75c67..147dfc4 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt @@ -24,9 +24,11 @@ interface TelemetryProvider { * * TelemetryManager calls this method all providers in order of registration. */ - fun enrich(signalType: String, - clientUser: String? = null, - additionalPayload: Map = emptyMap()): Map { + fun enrich( + signalType: String, + clientUser: String? = null, + additionalPayload: Map = emptyMap() + ): Map { return additionalPayload } } \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/UUIDSerializer.kt b/lib/src/main/java/com/telemetrydeck/sdk/UUIDSerializer.kt index eba7b82..bf20b44 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/UUIDSerializer.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/UUIDSerializer.kt @@ -5,11 +5,11 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializer import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import java.util.* +import java.util.UUID @Serializer(forClass = UUID::class) -internal object UUIDSerializer: KSerializer { +internal object UUIDSerializer : KSerializer { override fun serialize(encoder: Encoder, value: UUID) { encoder.encodeString(value.toString()) } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt index ffc0f22..0660555 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt @@ -1,11 +1,6 @@ package com.telemetrydeck.sdk.params -// TODO: add more device parameters from the Swift SDK: -//"TelemetryDeck.Device.orientation": Self.orientation, -//"TelemetryDeck.Device.screenResolutionHeight": Self.screenResolutionHeight, -//"TelemetryDeck.Device.screenResolutionWidth": Self.screenResolutionWidth, - internal enum class Device(val paramName: String) { Architecture("TelemetryDeck.Device.architecture"), ModelName("TelemetryDeck.Device.modelName"), @@ -16,7 +11,7 @@ internal enum class Device(val paramName: String) { SystemVersion("TelemetryDeck.Device.systemVersion"), Brand("TelemetryDeck.Device.brand"), TimeZone("TelemetryDeck.Device.timeZone"), - Orientation("TelemetryDeck.Device.orientation"), + Orientation("TelemetryDeck.Device.orientation"), // iOS compatibility note: on Android, there are additional orientations ScreenDensity("TelemetryDeck.Device.screenDensity"), ScreenHeight("TelemetryDeck.Device.screenResolutionHeight"), ScreenWidth("TelemetryDeck.Device.screenResolutionWidth") diff --git a/lib/src/main/java/com/telemetrydeck/sdk/params/RunContext.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/RunContext.kt index 6250e44..9c59a6e 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/params/RunContext.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/params/RunContext.kt @@ -1,11 +1,13 @@ package com.telemetrydeck.sdk.params -//"TelemetryDeck.RunContext.isDebug": "\(Self.isDebug)", -//"TelemetryDeck.RunContext.isSimulator": "\(Self.isSimulator)", -//"TelemetryDeck.RunContext.isTestFlight": "\(Self.isTestFlight)", -//"TelemetryDeck.RunContext.language": Self.appLanguage, -//"TelemetryDeck.RunContext.targetEnvironment": Self.targetEnvironment, +// The following are not provided by the Kotlin SDK: +// TelemetryDeck.RunContext.isDebug +// TelemetryDeck.RunContext.isSimulator +// TelemetryDeck.RunContext.isTestFlight +// TelemetryDeck.RunContext.language +// TelemetryDeck.UserPreference.language +// TelemetryDeck.UserPreference.region internal enum class RunContext(val paramName: String) { Locale("TelemetryDeck.RunContext.locale"), diff --git a/lib/src/main/java/com/telemetrydeck/sdk/params/SDK.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/SDK.kt index 77c2235..56feec9 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/params/SDK.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/params/SDK.kt @@ -4,4 +4,5 @@ internal enum class SDK(val paramName: String) { Name("TelemetryDeck.SDK.name"), Version("TelemetryDeck.SDK.version"), NameAndVersion("TelemetryDeck.SDK.nameAndVersion"), + BuildType("TelemetryDeck.SDK.buildType"), } \ No newline at end of file diff --git a/lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt b/lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt index ecb0738..1703e34 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt @@ -70,6 +70,7 @@ internal fun getDeviceOrientation(context: Context, logger: DebugLogger?): Devic android.content.res.Configuration.ORIENTATION_PORTRAIT -> DeviceOrientation.Portrait @Suppress("DEPRECATION") android.content.res.Configuration.ORIENTATION_SQUARE -> DeviceOrientation.Square + else -> DeviceOrientation.Unknown } } catch (e: Exception) { diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt index 763934a..1bedafc 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt @@ -6,10 +6,10 @@ import com.telemetrydeck.sdk.BuildConfig import com.telemetrydeck.sdk.ManifestMetadataReader import com.telemetrydeck.sdk.TelemetryDeckClient import com.telemetrydeck.sdk.TelemetryDeckProvider -import com.telemetrydeck.sdk.platform.getTimeZone import com.telemetrydeck.sdk.params.AppInfo import com.telemetrydeck.sdk.params.Device import com.telemetrydeck.sdk.params.SDK +import com.telemetrydeck.sdk.platform.getTimeZone import java.lang.ref.WeakReference /** @@ -85,7 +85,7 @@ class EnvironmentParameterProvider : TelemetryDeckProvider { if (android.os.Build.BRAND != null) { metadata[Device.Brand.paramName] = android.os.Build.BRAND } - if (android.os.Build.MODEL != null && android.os.Build.PRODUCT != null) { + if (android.os.Build.MODEL != null && android.os.Build.PRODUCT != null) { metadata[Device.ModelName.paramName] = "${android.os.Build.MODEL} (${android.os.Build.PRODUCT})" } @@ -95,9 +95,10 @@ class EnvironmentParameterProvider : TelemetryDeckProvider { // SDK Metadata metadata[SDK.Name.paramName] = sdkName - // TODO: create a build property to pass the maven coordinates of the library + metadata[SDK.Version.paramName] = BuildConfig.LIBRARY_PACKAGE_NAME metadata[SDK.NameAndVersion.paramName] = "$sdkName ${BuildConfig.LIBRARY_PACKAGE_NAME}" + metadata[SDK.BuildType.paramName] = BuildConfig.BUILD_TYPE this.enabled = true } diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt index c889a99..ca8980f 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt @@ -14,7 +14,7 @@ import java.lang.ref.WeakReference /** * Emits signals for application and activity lifecycle events. */ -class SessionActivityProvider: TelemetryDeckProvider, +class SessionActivityProvider : TelemetryDeckProvider, Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { private var manager: WeakReference? = null diff --git a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt index f34b216..8a732e2 100644 --- a/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt +++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt @@ -13,7 +13,7 @@ import java.lang.ref.WeakReference /** * Monitors the app lifecycle in order to broadcast the NewSessionBegan signal. */ -class SessionAppProvider: TelemetryDeckProvider, DefaultLifecycleObserver { +class SessionAppProvider : TelemetryDeckProvider, DefaultLifecycleObserver { private var manager: WeakReference? = null override fun register(ctx: Application?, client: TelemetryDeckClient) { diff --git a/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt index 259bc67..3da57fb 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt @@ -38,4 +38,18 @@ class EnvironmentParameterProviderTest { Assert.assertNotNull(queuedSignal) Assert.assertEquals(queuedSignal?.payload?.contains("telemetryClientVersion:my value"), true) } + + @Test + fun environmentMetadataProvider_sets_sdk_build_type() { + val appID = "32CB6574-6732-4238-879F-582FEBEB6536" + val config = TelemetryManagerConfiguration(appID) + val manager = TelemetryDeck.Builder().configuration(config).build(null) + + manager.signal("type", "clientUser", emptyMap()) + + val queuedSignal = manager.cache?.empty()?.first() + + Assert.assertNotNull(queuedSignal) + Assert.assertEquals(queuedSignal?.payload?.contains("TelemetryDeck.SDK.buildType:debug"), true) + } } \ No newline at end of file From 2f65a086bec10174945e08a78abcd083db71b3bf Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 4 Oct 2024 18:09:57 +0200 Subject: [PATCH 16/33] chore: idea config files --- .idea/androidTestResultsUserPreferences.xml | 22 +++++++++++++++++++++ .idea/compiler.xml | 2 +- .idea/gradle.xml | 1 + .idea/misc.xml | 3 ++- .idea/runConfigurations.xml | 13 ++++++++++++ 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .idea/androidTestResultsUserPreferences.xml create mode 100644 .idea/runConfigurations.xml diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml new file mode 100644 index 0000000..500e9fc --- /dev/null +++ b/.idea/androidTestResultsUserPreferences.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b589d56..b86273d 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index c34ccc3..ca237bd 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,6 +4,7 @@ diff --git a/build.gradle b/build.gradle deleted file mode 100644 index a4f4931..0000000 --- a/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - google() - mavenCentral() - maven { url 'https://jitpack.io' } - } - dependencies { - classpath 'com.android.tools.build:gradle:8.7.0' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22' - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a021959 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + alias(libs.plugins.androidLibrary) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.vanniktech.publish) apply false +} + +//// Top-level build file where you can add configuration options common to all sub-projects/modules. +//buildscript { +// repositories { +// google() +// mavenCentral() +// maven { url 'https://jitpack.io' } +// } +// dependencies { +// classpath 'com.android.tools.build:gradle:8.7.0' +// classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22' +// } +//} +// +//task clean(type: Delete) { +// delete rootProject.buildDir +//} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 66bbf65..288ca82 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,6 @@ android.enableJetifier=true kotlin.code.style=official -Dorg.gradle.warning.mode=all -android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false android.nonFinalResIds=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..b399b92 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,60 @@ +[versions] +agp = "8.7.1" +kotlin = "2.0.20" +coreKtx = "1.13.1" +junit = "4.13.2" +junitVersion = "1.2.1" +espressoCore = "3.6.1" +lifecycleRuntimeKtx = "2.8.6" +activityCompose = "1.9.3" +composeBom = "2024.10.00" +ktor = "3.0.0" +logback = "1.5.6" +kotlinx = "1.7.3" +coroutines = "1.9.0" +vanniktech-publish = "0.29.0" +appcompat = "1.7.0" +mockito = "5.14.2" +mockito_kotlin = "4.0.0" +robolectric = "4.7.3" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleRuntimeKtx" } +androidx-lifecycle-extensions = { group = "androidx.lifecycle", name = "lifecycle-extensions", version.ref = "lifecycleRuntimeKtx" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-arch-core = {group = "androidx.arch.core", name = "core-testing", version = "2.2.0" } +ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } +ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } +ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } +ktor-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } +ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } +logback = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx" } +kotlinx-serialization-properties = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-properties", version.ref = "kotlinx" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } +kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } +mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" } +mockito-android = { group = "org.mockito", name = "mockito-android", version.ref = "mockito" } +mockito-kotlin = { group = "org.mockito", name = "mockito-kotlin", version.ref = "mockito_kotlin" } +robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } + +[plugins] +androidLibrary = { id = "com.android.library", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +vanniktech-publish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktech-publish" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle deleted file mode 100644 index 225a24c..0000000 --- a/lib/build.gradle +++ /dev/null @@ -1,122 +0,0 @@ -import com.vanniktech.maven.publish.SonatypeHost - -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jetbrains.kotlin.plugin.serialization' version "1.6.10" - id "com.vanniktech.maven.publish" version "0.29.0" -} - -android { - compileSdk 33 - - defaultConfig { - minSdk 21 - targetSdk 33 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - -// https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md#avoiding-including-the-debug-infrastructure-in-the-resulting-apk - packagingOptions { - resources.excludes += "DebugProbesKt.bin" - } - namespace 'com.telemetrydeck.sdk' -} - -def ktor_version = '2.3.7' -def logback_version = '1.2.10' -def kotlinx_coroutines_version = '1.6.0' -dependencies { - - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.0' - implementation 'com.google.android.material:material:1.4.0' -// HTTP Client - implementation "io.ktor:ktor-client-core:$ktor_version" - implementation "io.ktor:ktor-client-okhttp:$ktor_version" -// HTTP Serialization - implementation "io.ktor:ktor-client-serialization:$ktor_version" - implementation "io.ktor:ktor-client-content-negotiation:$ktor_version" - implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version" - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1" - implementation "org.jetbrains.kotlinx:kotlinx-serialization-properties:1.5.1" -// HTTP Logging - implementation "ch.qos.logback:logback-classic:$logback_version" - implementation "io.ktor:ktor-client-logging:$ktor_version" - - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version" - implementation "androidx.lifecycle:lifecycle-process:2.4.0" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" - implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" - - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - testImplementation "androidx.test:runner:1.4.0" - testImplementation "androidx.arch.core:core-testing:2.1.0" - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - testImplementation 'org.mockito:mockito-core:4.2.0' - androidTestImplementation 'org.mockito:mockito-android:4.2.0' - testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" - testImplementation "org.robolectric:robolectric:4.7.3" -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { - kotlinOptions { - freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" - freeCompilerArgs += "-opt-in=kotlinx.coroutines.ObsoleteCoroutinesApi" - } -} - -mavenPublishing { - coordinates("com.telemetrydeck", "kotlin-sdk", "2.2.0") - - pom { - name = 'TelemetryDeck SDK' - description = "Kotlin SDK for TelemetryDeck, a privacy-conscious analytics service for apps and websites" - url = "https://telemetrydeck.com" - - licenses { - license { - name = "MIT License" - url = "https://raw.githubusercontent.com/TelemetryDeck/KotlinSDK/main/LICENSE" - } - } - - developers { - developer { - id = "winsmith" - name = "Daniel Jilg" - url = "https://github.com/winsmith" - organization = "TelemetryDeck GmbH" - } - } - - scm { - url = "https://github.com/TelemetryDeck/KotlinSDK" - } - } - -// publishToMavenCentral and signAllPublications are configured in gradle.properties -// // Configure publishing to Maven Central -// publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) -// -// // Enable GPG signing for all publications -// signAllPublications() -} \ No newline at end of file diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts new file mode 100644 index 0000000..1c96c63 --- /dev/null +++ b/lib/build.gradle.kts @@ -0,0 +1,174 @@ +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode +import com.vanniktech.maven.publish.SonatypeHost + +plugins { + alias(libs.plugins.androidLibrary) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.vanniktech.publish) +} + +android { + compileSdk = 34 + + defaultConfig { + minSdk = 21 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + lint { + targetSdk = 34 + } + + testOptions { + targetSdk = 34 + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + buildConfig = true + } + + // https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md#avoiding-including-the-debug-infrastructure-in-the-resulting-apk + packaging { + resources.excludes += "DebugProbesKt.bin" + } + namespace = "com.telemetrydeck.sdk" +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.process) +// implementation(libs.androidx.lifecycle.extensions) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.appcompat) + implementation(libs.ktor.client.core) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.ktor.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.ktor.client.okhttp) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.serialization.properties) + implementation(libs.ktor.client.logging) + implementation(libs.logback) + + + testImplementation(libs.junit) + testImplementation(libs.androidx.arch.core) + + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) + + testImplementation(libs.mockito.core) + testImplementation(libs.mockito.android) + testImplementation(libs.mockito.kotlin) + testImplementation(libs.robolectric) +} + +// +//def ktor_version = '2.3.7' +//def logback_version = '1.2.10' +//def kotlinx_coroutines_version = '1.6.0' +//dependencies { +// +// implementation 'androidx.core:core-ktx:1.7.0' +// implementation 'androidx.appcompat:appcompat:1.4.0' +// implementation 'com.google.android.material:material:1.4.0' +//// HTTP Client +// implementation "io.ktor:ktor-client-core:$ktor_version" +// implementation "io.ktor:ktor-client-okhttp:$ktor_version" +//// HTTP Serialization +// implementation "io.ktor:ktor-client-serialization:$ktor_version" +// implementation "io.ktor:ktor-client-content-negotiation:$ktor_version" +// implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version" +// implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1" +// implementation "org.jetbrains.kotlinx:kotlinx-serialization-properties:1.5.1" +//// HTTP Logging +// implementation "ch.qos.logback:logback-classic:$logback_version" +// implementation "io.ktor:ktor-client-logging:$ktor_version" +// +// implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version" +// implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version" +// implementation "androidx.lifecycle:lifecycle-process:2.4.0" +// implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" +// implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" +// +// testImplementation 'junit:junit:4.13.2' +// androidTestImplementation 'androidx.test.ext:junit:1.1.3' +// testImplementation "androidx.test:runner:1.4.0" +// testImplementation "androidx.arch.core:core-testing:2.1.0" +// androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + +// testImplementation 'org.mockito:mockito-core:4.2.0' +// androidTestImplementation 'org.mockito:mockito-android:4.2.0' +// testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" +// testImplementation "org.robolectric:robolectric:4.7.3" +//} +// +//tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { +// kotlinOptions { +// freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" +// freeCompilerArgs += "-opt-in=kotlinx.coroutines.ObsoleteCoroutinesApi" +// } +//} +// +//mavenPublishing { +// coordinates("com.telemetrydeck", "kotlin-sdk", "2.2.0") +// +// pom { +// name = 'TelemetryDeck SDK' +// description = "Kotlin SDK for TelemetryDeck, a privacy-conscious analytics service for apps and websites" +// url = "https://telemetrydeck.com" +// +// licenses { +// license { +// name = "MIT License" +// url = "https://raw.githubusercontent.com/TelemetryDeck/KotlinSDK/main/LICENSE" +// } +// } +// +// developers { +// developer { +// id = "winsmith" +// name = "Daniel Jilg" +// url = "https://github.com/winsmith" +// organization = "TelemetryDeck GmbH" +// } +// } +// +// scm { +// url = "https://github.com/TelemetryDeck/KotlinSDK" +// } +// } +// +//// publishToMavenCentral and signAllPublications are configured in gradle.properties +//// // Configure publishing to Maven Central +//// publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) +//// +//// // Enable GPG signing for all publications +//// signAllPublications() +//} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 9d50d99..0000000 --- a/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - jcenter() // Warning: this repository is going to shut down soon - } -} -rootProject.name = "TelemetryDeck SDK" -include ':app' -include ':lib' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..2399ab0 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "TelemetryDeck SDK" +include(":lib") From 067ffd08751950d67b044651802f4150df32f79a Mon Sep 17 00:00:00 2001 From: Konstantin Date: Sun, 27 Oct 2024 10:27:59 +0100 Subject: [PATCH 27/33] feat: drop Mockito in favour of Mockk for more streamlined integration --- gradle/libs.versions.toml | 9 ++++----- lib/build.gradle.kts | 12 +++++++++--- .../com/telemetrydeck/sdk/SessionProviderTest.kt | 14 +++++++------- .../sdk/providers/SessionAppProviderTest.kt | 14 +++++++------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b399b92..d3e1c92 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,8 +14,7 @@ kotlinx = "1.7.3" coroutines = "1.9.0" vanniktech-publish = "0.29.0" appcompat = "1.7.0" -mockito = "5.14.2" -mockito_kotlin = "4.0.0" +mockk = "1.13.13" robolectric = "4.7.3" [libraries] @@ -47,10 +46,10 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- kotlinx-serialization-properties = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-properties", version.ref = "kotlinx" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } -mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" } -mockito-android = { group = "org.mockito", name = "mockito-android", version.ref = "mockito" } -mockito-kotlin = { group = "org.mockito", name = "mockito-kotlin", version.ref = "mockito_kotlin" } robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } +mockk = {group = "io.mockk", name = "mockk", version.ref = "mockk" } +mockk-agent = {group = "io.mockk", name = "mockk-agent", version.ref = "mockk" } +mockk-android = {group = "io.mockk", name = "mockk-android", version.ref = "mockk" } [plugins] androidLibrary = { id = "com.android.library", version.ref = "agp" } diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 1c96c63..5d4fd10 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -50,6 +50,9 @@ android { // https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md#avoiding-including-the-debug-infrastructure-in-the-resulting-apk packaging { resources.excludes += "DebugProbesKt.bin" + resources { + pickFirsts += "META-INF/INDEX.LIST" + } } namespace = "com.telemetrydeck.sdk" } @@ -83,9 +86,12 @@ dependencies { debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) - testImplementation(libs.mockito.core) - testImplementation(libs.mockito.android) - testImplementation(libs.mockito.kotlin) +// testImplementation(libs.mockito.core) +// testImplementation(libs.mockito.android) +// testImplementation(libs.mockito.kotlin) + testImplementation(libs.mockk) + testImplementation(libs.mockk.android) + testImplementation(libs.mockk.agent) testImplementation(libs.robolectric) } diff --git a/lib/src/test/java/com/telemetrydeck/sdk/SessionProviderTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/SessionProviderTest.kt index d40e879..139f521 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/SessionProviderTest.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/SessionProviderTest.kt @@ -2,10 +2,10 @@ package com.telemetrydeck.sdk import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.LifecycleOwner +import io.mockk.mockk import org.junit.Assert import org.junit.Rule import org.junit.Test -import org.mockito.Mockito class SessionProviderTest { @get:Rule @@ -29,7 +29,7 @@ class SessionProviderTest { @Test fun sessionProvider_default_configuration_onStart_sends_newSessionBegan() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionProvider() val manager = testDefaultTelemetryManager() sut.register(null, manager) @@ -42,7 +42,7 @@ class SessionProviderTest { @Test fun sessionProvider_sendNewSessionBeganSignal_onStart_sends_newSessionBegan() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionProvider() val manager = testTelemetryManager(true) sut.register(null, manager) @@ -55,7 +55,7 @@ class SessionProviderTest { @Test fun sessionProvider_not_sendNewSessionBeganSignal_onStart_no_signals() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionProvider() val manager = testTelemetryManager(false) sut.register(null, manager) @@ -68,7 +68,7 @@ class SessionProviderTest { @Test fun sessionProvider_default_configuration_onStop_resets_the_sessionID() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionProvider() val manager = testDefaultTelemetryManager() sut.register(null, manager) @@ -82,7 +82,7 @@ class SessionProviderTest { @Test fun sessionProvider_sendNewSessionBeganSignal_onStop_resets_the_sessionID() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionProvider() val manager = testTelemetryManager(true) sut.register(null, manager) @@ -96,7 +96,7 @@ class SessionProviderTest { @Test fun sessionProvider_not_sendNewSessionBeganSignal_onStop_keeps_the_sessionID() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionProvider() val manager = testTelemetryManager(false) sut.register(null, manager) diff --git a/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt index 999cb2d..cfb347d 100644 --- a/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt +++ b/lib/src/test/java/com/telemetrydeck/sdk/providers/SessionAppProviderTest.kt @@ -5,10 +5,10 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.LifecycleOwner import com.telemetrydeck.sdk.TelemetryDeck import com.telemetrydeck.sdk.signals.Session +import io.mockk.mockk import org.junit.Assert import org.junit.Rule import org.junit.Test -import org.mockito.Mockito class SessionAppProviderTest { @get:Rule @@ -32,7 +32,7 @@ class SessionAppProviderTest { @Test fun sessionProvider_default_configuration_onStart_sends_newSessionBegan() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionAppProvider() val manager = testDefaultTelemetryManager() sut.register(null, manager) @@ -45,7 +45,7 @@ class SessionAppProviderTest { @Test fun sessionProvider_sendNewSessionBeganSignal_onStart_sends_newSessionBegan() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionAppProvider() val manager = testTelemetryManager(true) sut.register(null, manager) @@ -58,7 +58,7 @@ class SessionAppProviderTest { @Test fun sessionProvider_not_sendNewSessionBeganSignal_onStart_no_signals() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionAppProvider() val manager = testTelemetryManager(false) sut.register(null, manager) @@ -71,7 +71,7 @@ class SessionAppProviderTest { @Test fun sessionProvider_default_configuration_onStop_resets_the_sessionID() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionAppProvider() val manager = testDefaultTelemetryManager() sut.register(null, manager) @@ -85,7 +85,7 @@ class SessionAppProviderTest { @Test fun sessionProvider_sendNewSessionBeganSignal_onStop_resets_the_sessionID() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionAppProvider() val manager = testTelemetryManager(true) sut.register(null, manager) @@ -99,7 +99,7 @@ class SessionAppProviderTest { @Test fun sessionProvider_not_sendNewSessionBeganSignal_onStop_keeps_the_sessionID() { - val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java) + val lifecycleOwner: LifecycleOwner = mockk() val sut = SessionAppProvider() val manager = testTelemetryManager(false) sut.register(null, manager) From bf6dc7f0c996bb88a8ee48875f02a90f772bfc08 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Sun, 27 Oct 2024 10:34:34 +0100 Subject: [PATCH 28/33] chore: update .gitignore --- .gitignore | 3 +++ .idea/androidTestResultsUserPreferences.xml | 22 --------------------- 2 files changed, 3 insertions(+), 22 deletions(-) delete mode 100644 .idea/androidTestResultsUserPreferences.xml diff --git a/.gitignore b/.gitignore index aa724b7..0c01575 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,12 @@ /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml +/.idea/androidTestResultsUserPreferences.xml +/.idea/deploymentTargetSelector.xml .DS_Store /build /captures .externalNativeBuild .cxx local.properties +/.kotlin \ No newline at end of file diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml deleted file mode 100644 index 500e9fc..0000000 --- a/.idea/androidTestResultsUserPreferences.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - \ No newline at end of file From d234f7ff48db3bbb60250049533873d0e4fac076 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Sun, 27 Oct 2024 10:37:09 +0100 Subject: [PATCH 29/33] chore: remove old example app --- app/.gitignore | 1 - app/build.gradle | 50 ------ app/proguard-rules.pro | 21 --- .../sdk/app/ExampleInstrumentedTest.kt | 24 --- app/src/main/AndroidManifest.xml | 28 --- .../telemetrydeck/sdk/app/FirstFragment.kt | 50 ------ .../com/telemetrydeck/sdk/app/MainActivity.kt | 71 -------- .../telemetrydeck/sdk/app/SecondFragment.kt | 48 ----- .../drawable-v24/ic_launcher_foreground.xml | 30 ---- .../res/drawable/ic_launcher_background.xml | 170 ------------------ app/src/main/res/layout/activity_main.xml | 34 ---- app/src/main/res/layout/content_main.xml | 19 -- app/src/main/res/layout/fragment_first.xml | 28 --- app/src/main/res/layout/fragment_second.xml | 27 --- app/src/main/res/menu/menu_main.xml | 10 -- .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 1404 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 2898 -> 0 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 982 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 1772 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 1900 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 3918 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 2884 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 5914 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 3844 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 7778 -> 0 bytes app/src/main/res/navigation/nav_graph.xml | 28 --- app/src/main/res/values-land/dimens.xml | 3 - app/src/main/res/values-night/themes.xml | 16 -- app/src/main/res/values-w1240dp/dimens.xml | 3 - app/src/main/res/values-w600dp/dimens.xml | 3 - app/src/main/res/values/colors.xml | 10 -- app/src/main/res/values/dimens.xml | 3 - app/src/main/res/values/strings.xml | 12 -- app/src/main/res/values/themes.xml | 25 --- .../telemetrydeck/sdk/app/ExampleUnitTest.kt | 17 -- 37 files changed, 741 deletions(-) delete mode 100644 app/.gitignore delete mode 100644 app/build.gradle delete mode 100644 app/proguard-rules.pro delete mode 100644 app/src/androidTest/java/com/telemetrydeck/sdk/app/ExampleInstrumentedTest.kt delete mode 100644 app/src/main/AndroidManifest.xml delete mode 100644 app/src/main/java/com/telemetrydeck/sdk/app/FirstFragment.kt delete mode 100644 app/src/main/java/com/telemetrydeck/sdk/app/MainActivity.kt delete mode 100644 app/src/main/java/com/telemetrydeck/sdk/app/SecondFragment.kt delete mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml delete mode 100644 app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 app/src/main/res/layout/activity_main.xml delete mode 100644 app/src/main/res/layout/content_main.xml delete mode 100644 app/src/main/res/layout/fragment_first.xml delete mode 100644 app/src/main/res/layout/fragment_second.xml delete mode 100644 app/src/main/res/menu/menu_main.xml delete mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/navigation/nav_graph.xml delete mode 100644 app/src/main/res/values-land/dimens.xml delete mode 100644 app/src/main/res/values-night/themes.xml delete mode 100644 app/src/main/res/values-w1240dp/dimens.xml delete mode 100644 app/src/main/res/values-w600dp/dimens.xml delete mode 100644 app/src/main/res/values/colors.xml delete mode 100644 app/src/main/res/values/dimens.xml delete mode 100644 app/src/main/res/values/strings.xml delete mode 100644 app/src/main/res/values/themes.xml delete mode 100644 app/src/test/java/com/telemetrydeck/sdk/app/ExampleUnitTest.kt diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 42afabf..0000000 --- a/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index ecde9ae..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,50 +0,0 @@ -plugins { - id 'com.android.application' - id 'kotlin-android' -} - -android { - compileSdk 31 - - defaultConfig { - applicationId "com.telemetrydeck.sdk.app" - minSdk 21 - targetSdk 31 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - buildFeatures { - viewBinding true - } - namespace 'com.telemetrydeck.sdk.app' -} - -dependencies { - - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.1' - implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' - implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' - implementation project(':lib') - testImplementation 'junit:junit:4.+' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index 481bb43..0000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/telemetrydeck/sdk/app/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/telemetrydeck/sdk/app/ExampleInstrumentedTest.kt deleted file mode 100644 index aec64f1..0000000 --- a/app/src/androidTest/java/com/telemetrydeck/sdk/app/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.telemetrydeck.sdk.app - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.telemetrydeck.sdk.app", appContext.packageName) - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index 56212e0..0000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/java/com/telemetrydeck/sdk/app/FirstFragment.kt b/app/src/main/java/com/telemetrydeck/sdk/app/FirstFragment.kt deleted file mode 100644 index 76371e8..0000000 --- a/app/src/main/java/com/telemetrydeck/sdk/app/FirstFragment.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.telemetrydeck.sdk.app - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import com.telemetrydeck.sdk.TelemetryDeck -import com.telemetrydeck.sdk.app.databinding.FragmentFirstBinding -import kotlinx.coroutines.launch - -/** - * A simple [Fragment] subclass as the default destination in the navigation. - */ -class FirstFragment : Fragment() { - - private var _binding: FragmentFirstBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - - _binding = FragmentFirstBinding.inflate(inflater, container, false) - return binding.root - - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.buttonFirst.setOnClickListener { - lifecycleScope.launch { - TelemetryDeck.send("firstButtonPressed") - } - findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/telemetrydeck/sdk/app/MainActivity.kt b/app/src/main/java/com/telemetrydeck/sdk/app/MainActivity.kt deleted file mode 100644 index 8811d0a..0000000 --- a/app/src/main/java/com/telemetrydeck/sdk/app/MainActivity.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.telemetrydeck.sdk.app - -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import androidx.appcompat.app.AppCompatActivity -import androidx.navigation.findNavController -import androidx.navigation.ui.AppBarConfiguration -import androidx.navigation.ui.navigateUp -import androidx.navigation.ui.setupActionBarWithNavController -import com.google.android.material.snackbar.Snackbar -import com.telemetrydeck.sdk.TelemetryDeck -import com.telemetrydeck.sdk.app.databinding.ActivityMainBinding - -class MainActivity : AppCompatActivity() { - - private lateinit var appBarConfiguration: AppBarConfiguration - private lateinit var binding: ActivityMainBinding - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - -// TelemetryDeck is automatically configured and started based on settings in the app manifest. -// You can also configure it programmatically instead: -// -// val builder = TelemetryDeck.Builder() -// .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") -// .showDebugLogs(true) -// .defaultUser("Person Name") -// TelemetryDeck.start(application, builder) - - // let's change the user hash - TelemetryDeck.newDefaultUser("It's me :)") - - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - setSupportActionBar(binding.toolbar) - - val navController = findNavController(R.id.nav_host_fragment_content_main) - appBarConfiguration = AppBarConfiguration(navController.graph) - setupActionBarWithNavController(navController, appBarConfiguration) - - binding.fab.setOnClickListener { view -> - Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) - .setAction("Action", null).show() - } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - // Inflate the menu; this adds items to the action bar if it is present. - menuInflater.inflate(R.menu.menu_main, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - return when (item.itemId) { - R.id.action_settings -> true - else -> super.onOptionsItemSelected(item) - } - } - - override fun onSupportNavigateUp(): Boolean { - val navController = findNavController(R.id.nav_host_fragment_content_main) - return navController.navigateUp(appBarConfiguration) - || super.onSupportNavigateUp() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/telemetrydeck/sdk/app/SecondFragment.kt b/app/src/main/java/com/telemetrydeck/sdk/app/SecondFragment.kt deleted file mode 100644 index 295e240..0000000 --- a/app/src/main/java/com/telemetrydeck/sdk/app/SecondFragment.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.telemetrydeck.sdk.app - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import com.telemetrydeck.sdk.TelemetryDeck -import com.telemetrydeck.sdk.app.databinding.FragmentSecondBinding -import kotlinx.coroutines.launch - -/** - * A simple [Fragment] subclass as the second destination in the navigation. - */ -class SecondFragment : Fragment() { - - private var _binding: FragmentSecondBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentSecondBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - TelemetryDeck.stop() - binding.buttonSecond.setOnClickListener { - lifecycleScope.launch { - TelemetryDeck.send("secondButtonPressed") - } - findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment) - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d1..0000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9..0000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 4999565..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml deleted file mode 100644 index 4f68632..0000000 --- a/app/src/main/res/layout/content_main.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_first.xml b/app/src/main/res/layout/fragment_first.xml deleted file mode 100644 index fb44a3d..0000000 --- a/app/src/main/res/layout/fragment_first.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - -