diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..4b45f2c
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,32 @@
+name: Run Tests
+
+on:
+ push:
+ branches:
+ - 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
+ 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: Run test
+ run: ./gradlew lib:test
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 8512d29..734bf56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,8 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
+/.idea/androidTestResultsUserPreferences.xml
+/.idea/deploymentTargetSelector.xml
.DS_Store
/build
/captures
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..9c6ca34
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 9a55c2d..d4b7acc 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..f988785
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 40c6ee7..7e39cd5 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,12 @@
-# KotlinClient
+# Kotlin SDK for TelemetryDeck
This package allows you to send signals to [TelemetryDeck](https://telemetrydeck.com) from your Android applications. Sign up for a free account at [telemetrydeck.com](https://telemetrydeck.com)
## 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 Kotlin SDK for TelemetryDeck 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,15 +30,17 @@ Sending signals requires access to the internet so the following permission shou
```
+## Getting Started
+
### 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`:
+A quick way to start is by adding your App ID to the `application` section of the app's `AndroidManifest.xml`:
```xml
...
-
+
```
@@ -58,24 +49,24 @@ 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`
-### Programatic Usage
+### Programmatic Usage
-For greater control you can instead manually start the TelemetryManager client
+For greater control you can set up and configure `TelemetryDeck` using the provided builder:
```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,29 +74,71 @@ 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.signal("appLaunchedRegularly")
```
+### Environment Parameters
+
+By default, Kotlin SDK for 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.orientation` | `PlatformContextProvider` |
+| `TelemetryDeck.Device.screenDensity` | `PlatformContextProvider` |
+| `TelemetryDeck.Device.screenResolutionHeight` | `PlatformContextProvider` |
+| `TelemetryDeck.Device.screenResolutionWidth` | `PlatformContextProvider` |
+| `TelemetryDeck.Device.brand` | `EnvironmentParameterProvider` |
+| `TelemetryDeck.Device.timeZone` | `PlatformContextProvider` |
+| `TelemetryDeck.AppInfo.buildNumber` | `EnvironmentParameterProvider` |
+| `TelemetryDeck.AppInfo.version` | `EnvironmentParameterProvider` |
+| `TelemetryDeck.AppInfo.versionAndBuildNumber` | `EnvironmentParameterProvider` |
+| `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` |
+| `TelemetryDeck.RunContext.sourceMarketplace` | `PlatformContextProvider` |
+
+
+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 `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 `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 {
- override fun register(ctx: Application?, manager: TelemetryManager) {
+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
}
}
```
@@ -114,18 +147,20 @@ 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 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 using the `TelemetryManager.Builder` :
+To use your custom provider, register it by calling `addProvider` using the `TelemetryDeck.Builder` :
```kotlin
-val builder = TelemetryManager.Builder()
- .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
- .addProvider(CustomProvider())
+val builder = TelemetryDeck.Builder()
+ // ...
+ .addProvider(CustomProvider()) // <-- Your custom provider
```
-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.
+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(
@@ -133,35 +168,123 @@ 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
}
```
-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.
+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.
-- `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.
+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
-val builder = TelemetryManager.Builder()
- .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
+val builder = TelemetryDeck.Builder()
+ // ...
.addProvider(CustomProvider())
// Replace all default providers
-val builder = TelemetryManager.Builder()
- .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
+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.
+
+
+
## Requirements
- Android API 21 or later
- Kotlin 1.9.25 or later
+
+
+## Migrating providers to 3.0+
+
+If you had Kotlin SDK for TelemetryDeck 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.
+
+### 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:
+
+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`, `PlatformContextProvider` |
+
+
+
+> [!TIP]
+> You can rename all deprecated classes in your project using the Code Cleanup function in IntelliJ/Android Studio.
+
+
+> [!WARNING]
+> Do not mix usage of `TelemetryManager` and `TelemetryDeck`. Once you're ready to migrate, adapt all uses at the same time.
+
+
+### 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: TelemetryDeckSignalProcessor)`
+
+The `TelemetryDeckSignalProcessor` interface offers a subset of the `TelemetryDeck` client API which gives you access to:
+
+* To access the logger, use can use `client.debugLogger`
+* To access the signal cache, use `client.signalCache`
+
+
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 d7f919f..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 androidx.fragment.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.lifecycle.lifecycleScope
-import androidx.navigation.fragment.findNavController
-import com.telemetrydeck.sdk.TelemetryManager
-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 {
- TelemetryManager.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 115af9a..0000000
--- a/app/src/main/java/com/telemetrydeck/sdk/app/MainActivity.kt
+++ /dev/null
@@ -1,67 +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.TelemetryManager
-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)
-
- TelemetryManager.newDefaultUser("It's me :)")
-// val builder = TelemetryManager.Builder()
-// .appID("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
-// .showDebugLogs(true)
-// .defaultUser("Person Name")
-//
-// TelemetryManager.start(application, builder)
-
- 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 7dd6d9d..0000000
--- a/app/src/main/java/com/telemetrydeck/sdk/app/SecondFragment.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-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.lifecycle.lifecycleScope
-import androidx.navigation.fragment.findNavController
-import com.telemetrydeck.sdk.TelemetryManager
-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)
- TelemetryManager.stop()
- binding.buttonSecond.setOnClickListener {
- lifecycleScope.launch {
- TelemetryManager.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 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_second.xml b/app/src/main/res/layout/fragment_second.xml
deleted file mode 100644
index bd90524..0000000
--- a/app/src/main/res/layout/fragment_second.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
deleted file mode 100644
index 68e0c1a..0000000
--- a/app/src/main/res/menu/menu_main.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index eca70cf..0000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index eca70cf..0000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index b2dfe3d..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d6..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 62b611d..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a307..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 1b9a695..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9287f50..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d642..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9126ae3..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
deleted file mode 100644
index d13a317..0000000
--- a/app/src/main/res/navigation/nav_graph.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
deleted file mode 100644
index 22d7f00..0000000
--- a/app/src/main/res/values-land/dimens.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- 48dp
-
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
deleted file mode 100644
index 68eedf4..0000000
--- a/app/src/main/res/values-night/themes.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values-w1240dp/dimens.xml b/app/src/main/res/values-w1240dp/dimens.xml
deleted file mode 100644
index d73f4a3..0000000
--- a/app/src/main/res/values-w1240dp/dimens.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- 200dp
-
\ No newline at end of file
diff --git a/app/src/main/res/values-w600dp/dimens.xml b/app/src/main/res/values-w600dp/dimens.xml
deleted file mode 100644
index 22d7f00..0000000
--- a/app/src/main/res/values-w600dp/dimens.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- 48dp
-
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
deleted file mode 100644
index f8c6127..0000000
--- a/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
-
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
deleted file mode 100644
index 125df87..0000000
--- a/app/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- 16dp
-
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
deleted file mode 100644
index 718b053..0000000
--- a/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
- Telemetry Deck Demo
- Settings
-
- First Fragment
- Second Fragment
- Next
- Previous
-
- Hello first fragment
- Hello second fragment. Arg: %1$s
-
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
deleted file mode 100644
index 2756872..0000000
--- a/app/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/test/java/com/telemetrydeck/sdk/app/ExampleUnitTest.kt b/app/src/test/java/com/telemetrydeck/sdk/app/ExampleUnitTest.kt
deleted file mode 100644
index f123e5d..0000000
--- a/app/src/test/java/com/telemetrydeck/sdk/app/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.telemetrydeck.sdk.app
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 3cda710..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,19 +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.2.2'
- classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-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..c30bd33
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,7 @@
+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
+}
\ 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..d3e1c92
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,59 @@
+[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"
+mockk = "1.13.13"
+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" }
+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" }
+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/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7454180..2c35211 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 15de902..df97d72 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 1b6c787..f5feea6 100755
--- a/gradlew
+++ b/gradlew
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,12 @@ do
esac
done
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
+# This is normally unused
+# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +134,29 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then
done
fi
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +217,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/gradlew.bat b/gradlew.bat
index ac1b06f..9b42019 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
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..f82e3f5
--- /dev/null
+++ b/lib/build.gradle.kts
@@ -0,0 +1,139 @@
+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
+ }
+
+ @Suppress("UnstableApiUsage")
+ 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"
+ resources {
+ pickFirsts += "META-INF/INDEX.LIST"
+ }
+ }
+ namespace = "com.telemetrydeck.sdk"
+}
+
+kotlin {
+ jvmToolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.lifecycle.process)
+ // explicit dependency on compose seems to be required to use the compose compiler
+ // this is not used by the library atm
+ implementation(libs.androidx.activity.compose)
+ 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.mockk)
+ testImplementation(libs.mockk.android)
+ testImplementation(libs.mockk.agent)
+ testImplementation(libs.robolectric)
+}
+
+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/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 106d3f0..72fe185 100644
--- a/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt
+++ b/lib/src/main/java/com/telemetrydeck/sdk/AppLifecycleTelemetryProvider.kt
@@ -11,6 +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")
+)
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 46f2d49..ee861c9 100644
--- a/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt
+++ b/lib/src/main/java/com/telemetrydeck/sdk/EnvironmentMetadataProvider.kt
@@ -2,11 +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"
+ )
+)
class EnvironmentMetadataProvider : TelemetryProvider {
private var enabled: Boolean = true
private var metadata = mutableMapOf()
@@ -39,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"}"
}
}
@@ -76,4 +84,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..2059cf0 100644
--- a/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt
+++ b/lib/src/main/java/com/telemetrydeck/sdk/ManifestMetadataReader.kt
@@ -7,15 +7,31 @@ 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 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
}
@@ -30,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
@@ -47,9 +63,62 @@ 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? {
+ private fun getConfigurationFromManifest(
+ context: Context,
+ bundle: Bundle
+ ): TelemetryManagerConfiguration? {
val appID = bundle.getString(ManifestSettings.AppID.key) ?: return null
val config = TelemetryManagerConfiguration(appID)
@@ -63,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)
@@ -74,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/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/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/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/SessionProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt
index 851506c..2e0d53e 100644
--- a/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt
+++ b/lib/src/main/java/com/telemetrydeck/sdk/SessionProvider.kt
@@ -9,7 +9,14 @@ import java.lang.ref.WeakReference
/**
* Monitors the app lifecycle in order to broadcast the NewSessionBegan signal.
*/
-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 c39d1c5..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(
@@ -43,7 +44,17 @@ 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)
+ 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/TelemetryBroadcastTimer.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt
index 91c0656..25ad131 100644
--- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt
+++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryBroadcastTimer.kt
@@ -1,12 +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
-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)
@@ -15,17 +19,8 @@ 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
- }
- }
- }
+ private var job: Job? = null
fun start() {
CoroutineScope(Dispatchers.IO).launch {
@@ -37,18 +32,25 @@ internal class TelemetryBroadcastTimer(private val manager: WeakReference
+) : TelemetryDeckClient, TelemetryDeckSignalProcessor {
+ 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 navigate(sourcePath: String, destinationPath: String, clientUser: String?) {
+ navigationStatus.applyDestination(destinationPath)
+
+ val params: Map = mapOf(
+ Navigation.SchemaVersion.paramName to "1",
+ Navigation.Identifier.paramName to "$sourcePath -> $destinationPath",
+ Navigation.SourcePath.paramName to sourcePath,
+ Navigation.DestinationPath.paramName to destinationPath
+ )
+
+ signal(
+ com.telemetrydeck.sdk.signals.Navigation.PathChanged.signalName,
+ params = params,
+ customUserID = clientUser
+ )
+ }
+
+ override fun navigate(destinationPath: String, customUserID: String?) {
+ navigate(navigationStatus.getLastDestination(), destinationPath, customUserID)
+ }
+
+ override suspend fun send(
+ signalType: String,
+ clientUser: String?,
+ additionalPayload: Map,
+ floatValue: Double?
+ ): Result {
+ return send(createSignal(signalType, clientUser, additionalPayload, floatValue))
+ }
+
+ override suspend fun sendAll(signals: List): Result {
+ return send(signals)
+ }
+
+ override fun processSignal(
+ signalName: String,
+ params: Map,
+ floatValue: Double?,
+ customUserID: String?
+ ) {
+ signal(signalName, params, floatValue, customUserID)
+ }
+
+ override fun signal(
+ signalName: String,
+ params: Map,
+ floatValue: Double?,
+ customUserID: String?
+ ) {
+ cache?.add(
+ createSignal(
+ signalType = signalName,
+ clientUser = customUserID,
+ additionalPayload = params,
+ floatValue = floatValue
+ )
+ )
+ }
+
+ override fun resetSession(sessionID: UUID) {
+ newSession(sessionID)
+ }
+
+ override fun signal(signalName: String, customUserID: String?, params: Map) {
+ cache?.add(
+ createSignal(
+ signalType = signalName,
+ clientUser = customUserID,
+ additionalPayload = params,
+ floatValue = null
+ )
+ )
+ }
+
+ private suspend fun send(
+ signal: Signal
+ ): Result {
+ return send(listOf(signal))
+ }
+
+ private suspend fun send(
+ signals: List
+ ): Result {
+ return try {
+ val client = TelemetryClient(
+ 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(),
+ floatValue: Double?
+ ): 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)
+
+ val payload = SignalPayload(additionalPayload = enrichedPayload)
+ val signal = Signal(
+ appID = configuration.telemetryAppID,
+ type = signalType,
+ clientUser = hashedUser,
+ payload = payload.asMultiValueDimension,
+ 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}")
+ return signal
+ }
+
+ private fun hashString(input: String, algorithm: String = "SHA-256"): String {
+ return MessageDigest.getInstance(algorithm)
+ .digest(input.toByteArray())
+ .fold("") { str, it -> str + "%02x".format(it) }
+ }
+
+ companion object : TelemetryDeckClient, TelemetryDeckSignalProcessor {
+ internal val defaultTelemetryProviders: List
+ get() = listOf(
+ SessionAppProvider(),
+ EnvironmentParameterProvider(),
+ PlatformContextProvider()
+ )
+
+ // 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 navigate(sourcePath: String, destinationPath: String, clientUser: String?) {
+ getInstance()?.navigate(sourcePath, destinationPath, clientUser = clientUser)
+ }
+
+ override fun navigate(destinationPath: String, customUserID: String?) {
+ getInstance()?.navigate(destinationPath, customUserID = customUserID)
+ }
+
+ override suspend fun send(
+ signalType: String,
+ clientUser: String?,
+ additionalPayload: Map,
+ floatValue: Double?
+ ): Result {
+ val result = getInstance()?.send(signalType, clientUser, additionalPayload, floatValue)
+ if (result != null) {
+ return result
+ }
+ return failure(NullPointerException())
+ }
+
+ override suspend fun sendAll(signals: List): Result {
+ val result = getInstance()?.sendAll(signals)
+ if (result != null) {
+ return result
+ }
+ return failure(NullPointerException())
+ }
+
+ override fun processSignal(
+ signalName: String,
+ params: Map,
+ floatValue: Double?,
+ customUserID: String?
+ ) {
+ signal(signalName, params, floatValue, customUserID)
+ }
+
+ override fun resetSession(sessionID: UUID) {
+ newSession(sessionID)
+ }
+
+ 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 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 [TelemetryDeckProvider] which can produce or enrich signals
+ */
+ fun addProvider(provider: TelemetryDeckProvider) = 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 [TelemetryDeck] when logging internal messages.
+ */
+ 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/TelemetryDeckClient.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt
new file mode 100644
index 0000000..d517f3a
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckClient.kt
@@ -0,0 +1,99 @@
+package com.telemetrydeck.sdk
+
+import java.util.UUID
+
+interface TelemetryDeckClient {
+
+ /**
+ * 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?)
+
+
+ /**
+ * 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, customUserID: String? = null)
+
+
+ /**
+ * Send a signal immediately
+ */
+ suspend fun send(
+ signalType: String,
+ clientUser: String? = null,
+ additionalPayload: Map = emptyMap(),
+ floatValue: Double? = null,
+ ): Result
+
+
+ /**
+ * 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 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..9b51087 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,20 @@ 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..01b52ae
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckProvider.kt
@@ -0,0 +1,33 @@
+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: TelemetryDeckSignalProcessor)
+
+ /**
+ * 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..9bffa07
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryDeckSignalProcessor.kt
@@ -0,0 +1,59 @@
+package com.telemetrydeck.sdk
+
+import java.util.UUID
+
+interface TelemetryDeckSignalProcessor {
+ /**
+ * Return the cache used by the SDK.
+ * */
+ val signalCache: SignalCache?
+
+ /**
+ * Return the logger used by the SDK.
+ * */
+ val debugLogger: DebugLogger?
+
+ /**
+ * Return the configuration used by the SDK.
+ * */
+ val configuration: TelemetryManagerConfiguration?
+
+ /**
+ * Send multiple signals in bulk. Such signals are sent "as is", bypassing any enrichment.
+ * */
+ suspend fun sendAll(signals: List): Result
+
+ /**
+ * 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 processSignal(
+ signalName: String,
+ params: Map = emptyMap(),
+ floatValue: Double? = null,
+ customUserID: String? = null,
+ )
+
+ /**
+ * 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 resetSession(sessionID: UUID = UUID.randomUUID())
+}
+
diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt
index 4d352e0..1070ac6 100644
--- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt
+++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt
@@ -3,6 +3,9 @@ package com.telemetrydeck.sdk
import android.app.Application
import android.content.Context
import android.content.pm.ApplicationInfo
+import com.telemetrydeck.sdk.providers.EnvironmentParameterProvider
+import com.telemetrydeck.sdk.providers.PlatformContextProvider
+import com.telemetrydeck.sdk.providers.SessionAppProvider
import java.lang.ref.WeakReference
import java.net.URL
import java.security.MessageDigest
@@ -10,17 +13,56 @@ import java.util.UUID
import kotlin.Result.Companion.failure
import kotlin.Result.Companion.success
-
+@Deprecated(
+ "Use TelemetryDeck instead",
+ ReplaceWith("TelemetryDeck", "com.telemetrydeck.sdk.TelemetryDeck")
+)
class TelemetryManager(
- val configuration: TelemetryManagerConfiguration,
- val providers: List = listOf(
- AppLifecycleTelemetryProvider()
- )
-) : TelemetryManagerSignals {
+ override val configuration: TelemetryManagerConfiguration,
+ val providers: List,
+
+ ) : TelemetryManagerSignals, TelemetryDeckSignalProcessor {
var cache: SignalCache? = null
var logger: DebugLogger? = null
private val navigationStatus: NavigationStatus = MemoryNavigationStatus()
+ private var fallbackProviders: List = emptyList()
+
+ override val signalCache: SignalCache?
+ get() = this.cache
+
+ override val debugLogger: DebugLogger?
+ get() = this.logger
+
+ override suspend fun sendAll(
+ signals: List
+ ): Result {
+ return try {
+ val client = TelemetryClient(
+ 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 processSignal(
+ signalName: String,
+ params: Map,
+ floatValue: Double?,
+ customUserID: String?
+ ) {
+ queue(signalName, customUserID, params)
+ }
+
+ override fun resetSession(sessionID: UUID) {
+ newSession(sessionID)
+ }
override fun newSession(sessionID: UUID) {
this.configuration.sessionID = sessionID
@@ -90,7 +132,6 @@ class TelemetryManager(
): Result {
return try {
val client = TelemetryClient(
- configuration.telemetryAppID,
configuration.apiBaseURL,
configuration.showDebugLogs,
logger
@@ -110,6 +151,11 @@ class TelemetryManager(
logger?.debug("Installing provider ${provider::class}.")
provider.register(context?.applicationContext as Application?, this)
}
+ // register grand rename based providers
+ for (provider in fallbackProviders) {
+ logger?.debug("Installing provider ${provider::class}.")
+ provider.fallbackRegister(context?.applicationContext as Application?, this)
+ }
}
private fun createSignal(
@@ -121,11 +167,14 @@ class TelemetryManager(
for (provider in this.providers) {
enrichedPayload = provider.enrich(signalType, clientUser, enrichedPayload)
}
+ for (provider in this.fallbackProviders) {
+ enrichedPayload = provider.fallbackEnrich(signalType, clientUser, enrichedPayload)
+ }
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(
appID = configuration.telemetryAppID,
@@ -341,7 +390,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
@@ -369,6 +418,41 @@ class TelemetryManager(
providers = providers + (additionalProviders?.toList() ?: listOf())
}
+ // if any of our default providers are activated, we should append the corresponding
+ // "Grand Rename" provider as well
+ val providersSoFar = providers
+ val fallbackProviders = mutableListOf()
+ if (providersSoFar.isNotEmpty()) {
+ for (provider in providersSoFar) {
+ when (provider) {
+ is SessionProvider -> {
+ if (fallbackProviders.filterIsInstance().isEmpty()) {
+ fallbackProviders.add(SessionAppProvider())
+ }
+ }
+ is AppLifecycleTelemetryProvider -> {
+ // we are no longer sending activity related events
+// if (fallbackProviders.filterIsInstance().isEmpty()) {
+// fallbackProviders.add(SessionActivityProvider())
+// }
+ if (fallbackProviders.filterIsInstance().isEmpty()) {
+ fallbackProviders.add(SessionAppProvider())
+ }
+ }
+
+ is EnvironmentMetadataProvider -> {
+ if (fallbackProviders.filterIsInstance().isEmpty()) {
+ fallbackProviders.add(PlatformContextProvider())
+ }
+ if (fallbackProviders.filterIsInstance().isEmpty()) {
+ fallbackProviders.add(EnvironmentParameterProvider())
+ }
+ }
+ }
+ }
+ }
+
+
// check if sessionID has been provided to override the default one
val sessionID = this.sessionID
if (sessionID != null) {
@@ -417,6 +501,7 @@ class TelemetryManager(
val manager = TelemetryManager(config, providers)
manager.logger = logger
+ manager.fallbackProviders = fallbackProviders
manager.installProviders(context)
val broadcaster =
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/TelemetryManagerSignals.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt
index 610c930..55588f5 100644
--- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt
+++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt
@@ -2,6 +2,13 @@ package com.telemetrydeck.sdk
import java.util.UUID
+@Deprecated(
+ "Use TelemetryDeckClient",
+ ReplaceWith(
+ "TelemetryDeckClient",
+ "com.telemetrydeck.sdk.TelemetryDeckClient"
+ )
+)
interface TelemetryManagerSignals {
/**
diff --git a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt
index 01581b2..1601623 100644
--- a/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt
+++ b/lib/src/main/java/com/telemetrydeck/sdk/TelemetryProvider.kt
@@ -2,9 +2,33 @@ package com.telemetrydeck.sdk
import android.app.Application
+/**
+ * New providers should adopt this interface to be able to be used with the legacy TelemetryManager.
+ *
+ * To preserve backwards compatibility with TelemetryManager, this interface is a copy of [TelemetryDeckProvider] with all methods prefixed with `fallback`.
+ *
+ * This interface should remain internal!
+ */
+@Deprecated("Use TelemetryDeckProvider")
+internal interface TelemetryProviderFallback {
+
+ fun fallbackRegister(ctx: Application?, client: TelemetryDeckSignalProcessor)
+
+ fun fallbackStop()
+
+ fun fallbackEnrich(
+ signalType: String,
+ clientUser: String? = null,
+ additionalPayload: Map = emptyMap()
+ ): Map {
+ return additionalPayload
+ }
+}
+
/**
* Generic interface for plugins which can create Signals
*/
+@Deprecated("Use TelemetryDeckProvider", ReplaceWith("TelemetryDeckProvider"))
interface TelemetryProvider {
/**
* Registers the provider with the telemetry manager.
@@ -23,9 +47,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/AppInfo.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/AppInfo.kt
new file mode 100644
index 0000000..f89a295
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/params/AppInfo.kt
@@ -0,0 +1,8 @@
+package com.telemetrydeck.sdk.params
+
+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/params/Device.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt
new file mode 100644
index 0000000..0660555
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/params/Device.kt
@@ -0,0 +1,18 @@
+package com.telemetrydeck.sdk.params
+
+
+internal enum class Device(val paramName: 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"),
+ TimeZone("TelemetryDeck.Device.timeZone"),
+ 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")
+}
\ 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/params/RunContext.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/RunContext.kt
new file mode 100644
index 0000000..9c59a6e
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/params/RunContext.kt
@@ -0,0 +1,17 @@
+package com.telemetrydeck.sdk.params
+
+
+// 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"),
+ 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/params/SDK.kt b/lib/src/main/java/com/telemetrydeck/sdk/params/SDK.kt
new file mode 100644
index 0000000..56feec9
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/params/SDK.kt
@@ -0,0 +1,8 @@
+package com.telemetrydeck.sdk.params
+
+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/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/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/PlatformReader.kt b/lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt
new file mode 100644
index 0000000..1703e34
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/platform/PlatformReader.kt
@@ -0,0 +1,110 @@
+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
new file mode 100644
index 0000000..31f085b
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProvider.kt
@@ -0,0 +1,140 @@
+package com.telemetrydeck.sdk.providers
+
+import android.app.Application
+import android.icu.util.VersionInfo
+import android.os.Build
+import com.telemetrydeck.sdk.BuildConfig
+import com.telemetrydeck.sdk.DebugLogger
+import com.telemetrydeck.sdk.ManifestMetadataReader
+import com.telemetrydeck.sdk.TelemetryDeckProvider
+import com.telemetrydeck.sdk.TelemetryDeckSignalProcessor
+import com.telemetrydeck.sdk.TelemetryProviderFallback
+import com.telemetrydeck.sdk.params.AppInfo
+import com.telemetrydeck.sdk.params.Device
+import com.telemetrydeck.sdk.params.SDK
+
+/**
+ * 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.
+ */
+internal class EnvironmentParameterProvider : TelemetryDeckProvider, TelemetryProviderFallback {
+ private var enabled: Boolean = true
+ 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 fallbackRegister(ctx: Application?, client: TelemetryDeckSignalProcessor) {
+ register(ctx, client)
+ }
+
+ override fun fallbackStop() {
+ stop()
+ }
+
+ override fun register(ctx: Application?, client: TelemetryDeckSignalProcessor) {
+ appendContextSpecificParams(ctx, client.debugLogger)
+ appendVersionMetadata(client.debugLogger)
+ appendBrandAndMakeMetadata()
+ appendSDKMetadata()
+ this.enabled = true
+ }
+
+ private fun appendSDKMetadata() {
+ metadata[SDK.Name.paramName] = sdkName
+ metadata[SDK.Version.paramName] = BuildConfig.LIBRARY_PACKAGE_NAME
+ metadata[SDK.NameAndVersion.paramName] = "$sdkName ${BuildConfig.LIBRARY_PACKAGE_NAME}"
+ metadata[SDK.BuildType.paramName] = BuildConfig.BUILD_TYPE
+ }
+
+ private fun appendBrandAndMakeMetadata() {
+ metadata[Device.Platform.paramName] = platform
+
+ if (Build.BRAND != null) {
+ metadata[Device.Brand.paramName] = Build.BRAND
+ }
+ if (Build.MODEL != null && Build.PRODUCT != null) {
+ metadata[Device.ModelName.paramName] =
+ "${Build.MODEL} (${Build.PRODUCT})"
+ }
+ metadata[Device.Architecture.paramName] = System.getProperty("os.arch") ?: ""
+ metadata[Device.OperatingSystem.paramName] = os
+ }
+
+ private fun appendVersionMetadata(debugLogger: DebugLogger?) {
+ if (Build.VERSION.RELEASE.isNullOrEmpty()) {
+ debugLogger?.error(
+ "android.os.Build.VERSION.RELEASE is not set - device metadata will not be appended"
+ )
+ return
+ }
+
+
+ val release = Build.VERSION.RELEASE
+ val sdkVersion = Build.VERSION.SDK_INT
+ metadata[Device.SystemVersion.paramName] = "$platform $release (SDK: $sdkVersion)"
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ val versionInfo = VersionInfo.getInstance(release)
+ 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.paramName] = major
+ metadata[Device.SystemMajorMinorVersion.paramName] = "$major.$minor"
+ }
+ }
+
+ private fun appendContextSpecificParams(ctx: Application?, debugLogger: DebugLogger?) {
+ if (ctx == null) {
+ debugLogger?.error("EnvironmentParameterProvider requires a context but received null. Signals will contain incomplete metadata.")
+ return
+ }
+
+ val appVersion = ManifestMetadataReader.getAppVersion(ctx)
+ if (!appVersion.isNullOrEmpty()) {
+ metadata[AppInfo.Version.paramName] = appVersion
+ }
+ ManifestMetadataReader.getBuildNumber(ctx)?.let { buildNumber ->
+ metadata[AppInfo.BuildNumber.paramName] = buildNumber.toString()
+ metadata[AppInfo.VersionAndBuildNumber.paramName] =
+ "$appVersion (build $buildNumber)"
+ }
+ }
+
+ 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
+ }
+
+ override fun fallbackEnrich(
+ signalType: String,
+ clientUser: String?,
+ additionalPayload: Map
+ ): Map {
+ return enrich(signalType, clientUser, additionalPayload)
+ }
+}
\ No newline at end of file
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..d6e305c
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/PlatformContextProvider.kt
@@ -0,0 +1,130 @@
+package com.telemetrydeck.sdk.providers
+
+import android.app.Application
+import android.content.Context
+import com.telemetrydeck.sdk.TelemetryDeckProvider
+import com.telemetrydeck.sdk.TelemetryDeckSignalProcessor
+import com.telemetrydeck.sdk.TelemetryProviderFallback
+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 com.telemetrydeck.sdk.platform.getTimeZone
+import java.lang.ref.WeakReference
+
+internal class PlatformContextProvider : TelemetryDeckProvider, TelemetryProviderFallback {
+ private var enabled: Boolean = true
+ private var manager: WeakReference? = null
+ private var appContext: WeakReference? = null
+ private var metadata = mutableMapOf()
+
+ override fun fallbackRegister(ctx: Application?, client: TelemetryDeckSignalProcessor) {
+ register(ctx, client)
+ }
+
+ override fun fallbackStop() {
+ stop()
+ }
+
+ override fun register(ctx: Application?, client: TelemetryDeckSignalProcessor) {
+ 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
+ return
+ }
+
+ if (android.os.Build.DEVICE != null) {
+ metadata[RunContext.TargetEnvironment.paramName] = android.os.Build.DEVICE
+ }
+
+ // determine if the app was installed by a trusted marketplace
+ val appInfo = getAppInstallationInfo(ctx, this.manager?.get()?.debugLogger)
+ 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 fallbackEnrich(
+ signalType: String,
+ clientUser: String?,
+ additionalPayload: Map
+ ): Map {
+ return enrich(signalType, clientUser, additionalPayload)
+ }
+
+ 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
+ }
+ }
+
+ 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
+ }
+
+ // determine the current time zone
+ val timeZoneInfo = getTimeZone(ctx, this.manager?.get()?.debugLogger)
+ if (timeZoneInfo != null) {
+ attributes[Device.TimeZone.paramName] = timeZoneInfo.id
+ }
+
+ return attributes
+ }
+
+
+}
\ 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..a7a6781
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionActivityProvider.kt
@@ -0,0 +1,107 @@
+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.TelemetryDeckProvider
+import com.telemetrydeck.sdk.TelemetryDeckSignalProcessor
+import com.telemetrydeck.sdk.TelemetryProviderFallback
+import java.lang.ref.WeakReference
+
+/**
+ * Emits signals for application and activity lifecycle events.
+ */
+internal class SessionActivityProvider : TelemetryDeckProvider,
+ Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver, TelemetryProviderFallback {
+ private var manager: WeakReference? = null
+
+ override fun fallbackRegister(ctx: Application?, client: TelemetryDeckSignalProcessor) {
+ register(ctx, client)
+ }
+
+ override fun fallbackStop() {
+ stop()
+ }
+
+ override fun register(ctx: Application?, client: TelemetryDeckSignalProcessor) {
+ 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()?.processSignal(
+ SignalType.ActivityCreated.type,
+ mapOf("activity" to p0.localClassName)
+ )
+ }
+
+ override fun onActivityStarted(p0: Activity) {
+ manager?.get()?.processSignal(
+ SignalType.ActivityStarted.type,
+ mapOf("activity" to p0.localClassName)
+ )
+ }
+
+ override fun onActivityResumed(p0: Activity) {
+ manager?.get()?.processSignal(
+ SignalType.ActivityResumed.type,
+ mapOf("activity" to p0.localClassName)
+ )
+ }
+
+ override fun onActivityPaused(p0: Activity) {
+ manager?.get()?.processSignal(
+ SignalType.ActivityPaused.type,
+ mapOf("activity" to p0.localClassName)
+ )
+ }
+
+ override fun onActivityStopped(p0: Activity) {
+ manager?.get()?.processSignal(
+ SignalType.ActivityStopped.type,
+ mapOf("activity" to p0.localClassName)
+ )
+ }
+
+ override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
+ manager?.get()?.processSignal(
+ SignalType.ActivitySaveInstanceState.type,
+ mapOf("activity" to p0.localClassName)
+ )
+ }
+
+ override fun onActivityDestroyed(p0: Activity) {
+ manager?.get()?.processSignal(
+ SignalType.ActivityDestroyed.type,
+ mapOf("activity" to p0.localClassName)
+ )
+ }
+
+ override fun onStart(owner: LifecycleOwner) {
+ manager?.get()?.processSignal(
+ SignalType.AppForeground.type
+ )
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ manager?.get()?.processSignal(
+ SignalType.AppBackground.type
+ )
+ }
+
+
+}
\ 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..6603702
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/providers/SessionAppProvider.kt
@@ -0,0 +1,54 @@
+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.TelemetryDeckProvider
+import com.telemetrydeck.sdk.TelemetryDeckSignalProcessor
+import com.telemetrydeck.sdk.TelemetryProviderFallback
+import com.telemetrydeck.sdk.signals.Session
+import java.lang.ref.WeakReference
+
+/**
+ * Monitors the app lifecycle in order to broadcast the NewSessionBegan signal.
+ */
+internal class SessionAppProvider : TelemetryDeckProvider, DefaultLifecycleObserver,
+ TelemetryProviderFallback {
+ private var manager: WeakReference? = null
+
+ override fun fallbackRegister(ctx: Application?, client: TelemetryDeckSignalProcessor) {
+ register(ctx, client)
+ }
+
+ override fun fallbackStop() {
+ stop()
+ }
+
+ override fun register(ctx: Application?, client: TelemetryDeckSignalProcessor) {
+ 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()?.processSignal(
+ Session.Started.signalName
+ )
+ }
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ if (manager?.get()?.configuration?.sendNewSessionBeganSignal == true) {
+ // app is going into the background, reset the sessionID
+ manager?.get()?.resetSession()
+ }
+ }
+
+}
\ 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..eacd7ea
--- /dev/null
+++ b/lib/src/main/java/com/telemetrydeck/sdk/signals/Navigation.kt
@@ -0,0 +1,5 @@
+package com.telemetrydeck.sdk.signals
+
+internal enum class Navigation(val signalName: String) {
+ PathChanged("TelemetryDeck.Navigation.pathChanged"),
+}
\ 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/EnvironmentMetadataProviderTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/EnvironmentMetadataProviderTest.kt
index ea89edc..919b905 100644
--- a/lib/src/test/java/com/telemetrydeck/sdk/EnvironmentMetadataProviderTest.kt
+++ b/lib/src/test/java/com/telemetrydeck/sdk/EnvironmentMetadataProviderTest.kt
@@ -4,7 +4,6 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
-import java.util.UUID
class EnvironmentMetadataProviderTest {
@get:Rule
@@ -14,27 +13,37 @@ class EnvironmentMetadataProviderTest {
fun environmentMetadataProvider_sets_client_version() {
val appID = "32CB6574-6732-4238-879F-582FEBEB6536"
val config = TelemetryManagerConfiguration(appID)
- val manager = TelemetryManager.Builder().configuration(config).build(null)
+ val manager = TelemetryManager.Builder().configuration(config).build(null)
manager.queue("type", "clientUser", emptyMap())
val queuedSignal = manager.cache?.empty()?.first()
Assert.assertNotNull(queuedSignal)
- Assert.assertEquals(queuedSignal?.payload?.contains("telemetryClientVersion:com.telemetrydeck.sdk"), true)
+ Assert.assertEquals(
+ queuedSignal?.payload?.contains("telemetryClientVersion:com.telemetrydeck.sdk"),
+ true
+ )
+ Assert.assertEquals(
+ queuedSignal?.payload?.contains("TelemetryDeck.SDK.version: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 = TelemetryManager.Builder().configuration(config).build(null)
+ val manager = TelemetryManager.Builder().configuration(config).build(null)
manager.queue("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)
+ 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/MemorySignalCacheTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/MemorySignalCacheTest.kt
index 1e05589..bcd16f4 100644
--- a/lib/src/test/java/com/telemetrydeck/sdk/MemorySignalCacheTest.kt
+++ b/lib/src/test/java/com/telemetrydeck/sdk/MemorySignalCacheTest.kt
@@ -2,7 +2,7 @@ package com.telemetrydeck.sdk
import org.junit.Assert
import org.junit.Test
-import java.util.*
+import java.util.UUID
class MemorySignalCacheTest {
@Test
diff --git a/lib/src/test/java/com/telemetrydeck/sdk/PersistentSignalCacheTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/PersistentSignalCacheTest.kt
index da4206a..771d019 100644
--- a/lib/src/test/java/com/telemetrydeck/sdk/PersistentSignalCacheTest.kt
+++ b/lib/src/test/java/com/telemetrydeck/sdk/PersistentSignalCacheTest.kt
@@ -1,15 +1,18 @@
package com.telemetrydeck.sdk
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import kotlinx.serialization.decodeFromString
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.newFixedThreadPoolContext
+import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
-import kotlinx.coroutines.*
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
-import java.util.*
+import java.util.UUID
class PersistentSignalCacheTest {
@get:Rule
diff --git a/lib/src/test/java/com/telemetrydeck/sdk/SessionProviderTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/SessionProviderTest.kt
index 07063f6..39d15eb 100644
--- a/lib/src/test/java/com/telemetrydeck/sdk/SessionProviderTest.kt
+++ b/lib/src/test/java/com/telemetrydeck/sdk/SessionProviderTest.kt
@@ -1,15 +1,13 @@
package com.telemetrydeck.sdk
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import androidx.lifecycle.ProcessLifecycleOwner
+import io.mockk.mockk
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
-import org.mockito.Mockito
+@Suppress("DEPRECATION")
class SessionProviderTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@@ -32,7 +30,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)
@@ -45,7 +43,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)
@@ -58,7 +56,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)
@@ -71,7 +69,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)
@@ -85,7 +83,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)
@@ -99,7 +97,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/SignalsUnitTest.kt b/lib/src/test/java/com/telemetrydeck/sdk/SignalsUnitTest.kt
index f858004..f6ec210 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).
@@ -42,8 +38,17 @@ class SignalsUnitTest {
val signalJson = Json.encodeToString(signal)
val decodedSignal = Json.decodeFromString(signalJson)
- // date equality comparison with precision up to milliseconds
- assertEquals(receivedDate.time, decodedSignal.receivedAt?.time)
+ assertDatesEqualIgnoringMilliseconds(receivedDate, decodedSignal.receivedAt)
+ }
+
+ fun truncateMilliseconds(date: Date): Date {
+ return Date(date.time / 1000 * 1000) // Remove milliseconds by truncating to the nearest second
+ }
+
+ fun assertDatesEqualIgnoringMilliseconds(expected: Date, actual: Date) {
+ val truncatedExpected = truncateMilliseconds(expected)
+ val truncatedActual = truncateMilliseconds(actual)
+ assertEquals(truncatedExpected, truncatedActual)
}
@Test
@@ -83,14 +88,29 @@ class SignalsUnitTest {
@Test
fun telemetryClient_correct_service_url() {
val appID = UUID.fromString("32CB6574-6732-4238-879F-582FEBEB6536")
- val client = TelemetryClient(appID, URL("https://nom.telemetrydeck.com"), false, null)
+ val client = TelemetryClient(URL("https://nom.telemetrydeck.com"), false, null)
val endpointUrl = client.getServiceUrl()
// date equality comparison with precision up to milliseconds
assertEquals(
- "https://nom.telemetrydeck.com/api/v1/apps/$appID/signals/multiple/",
+ "https://nom.telemetrydeck.com/v2/",
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
new file mode 100644
index 0000000..e083f48
--- /dev/null
+++ b/lib/src/test/java/com/telemetrydeck/sdk/TelemetryDeckTests.kt
@@ -0,0 +1,429 @@
+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.UUID
+
+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.signal("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.signal("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_From_String() {
+ 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_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)
+ }
+
+
+ @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.signal("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.signal("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(TestTelemetryDeckProvider())
+ .build(null)
+ sut.signal("type")
+
+ Assert.assertEquals(4, sut.providers.count())
+ Assert.assertTrue(sut.providers.last() is TestTelemetryDeckProvider)
+ }
+
+ @Test
+ fun telemetryDeck_addProvider_custom_provider_is_registered() {
+ val provider = TestTelemetryDeckProvider()
+ 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 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"
+ )
+ }
+
+
+ @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 {
+// // 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..06e666b 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 {
@@ -40,10 +41,13 @@ class TelemetryManagerTest {
val appID = "32CB6574-6732-4238-879F-582FEBEB6536"
val config = TelemetryManagerConfiguration(appID)
config.salt = "my salt"
- val manager = TelemetryManager.Builder().configuration(config).build(null)
+ val manager = TelemetryManager.Builder().configuration(config).build(null)
manager.queue("type", "clientUser", emptyMap())
val queuedSignal = manager.cache?.empty()?.first()
- Assert.assertEquals("9a68a3790deb1db66f80855b8e7c5a97df8002ef90d3039f9e16c94cfbd11d99", queuedSignal?.clientUser)
+ Assert.assertEquals(
+ "9a68a3790deb1db66f80855b8e7c5a97df8002ef90d3039f9e16c94cfbd11d99",
+ queuedSignal?.clientUser
+ )
}
@Test
@@ -256,7 +260,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 +406,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..254bfc6
--- /dev/null
+++ b/lib/src/test/java/com/telemetrydeck/sdk/TestProvider.kt
@@ -0,0 +1,15 @@
+package com.telemetrydeck.sdk
+
+import android.app.Application
+
+open class TestProvider : TelemetryProvider {
+ var registered = false
+ override fun register(ctx: Application?, manager: TelemetryManager) {
+ registered = true
+ }
+
+ override fun stop() {
+ //
+ }
+}
+
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..6385421
--- /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: TelemetryDeckSignalProcessor) {
+ 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..49ec13f
--- /dev/null
+++ b/lib/src/test/java/com/telemetrydeck/sdk/providers/EnvironmentParameterProviderTest.kt
@@ -0,0 +1,65 @@
+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(
+ true,
+ queuedSignal?.payload?.contains("TelemetryDeck.SDK.version:com.telemetrydeck.sdk")
+
+ )
+ }
+
+ @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(
+ true,
+ queuedSignal?.payload?.contains("telemetryClientVersion:my value")
+ )
+ }
+
+ @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(
+ true,
+ queuedSignal?.payload?.filter { it.startsWith("TelemetryDeck.SDK.buildType:") }?.isNotEmpty()
+ )
+ }
+}
\ 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..cfb347d
--- /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.TelemetryDeck
+import com.telemetrydeck.sdk.signals.Session
+import io.mockk.mockk
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+
+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 = mockk()
+ val sut = SessionAppProvider()
+ val manager = testDefaultTelemetryManager()
+ sut.register(null, manager)
+
+ sut.onStart(lifecycleOwner)
+
+ Assert.assertEquals(1, manager.cache?.count())
+ Assert.assertEquals(Session.Started.signalName, manager.cache?.empty()?.get(0)?.type)
+ }
+
+ @Test
+ fun sessionProvider_sendNewSessionBeganSignal_onStart_sends_newSessionBegan() {
+ val lifecycleOwner: LifecycleOwner = mockk()
+ val sut = SessionAppProvider()
+ val manager = testTelemetryManager(true)
+ sut.register(null, manager)
+
+ sut.onStart(lifecycleOwner)
+
+ Assert.assertEquals(1, manager.cache?.count())
+ Assert.assertEquals(Session.Started.signalName, manager.cache?.empty()?.get(0)?.type)
+ }
+
+ @Test
+ fun sessionProvider_not_sendNewSessionBeganSignal_onStart_no_signals() {
+ val lifecycleOwner: LifecycleOwner = mockk()
+ 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 = mockk()
+ 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 = mockk()
+ 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 = mockk()
+ 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
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")