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