diff --git a/android/src/main/java/com/amplitude/android/Configuration.kt b/android/src/main/java/com/amplitude/android/Configuration.kt index 7dea714f..2e5ddce2 100644 --- a/android/src/main/java/com/amplitude/android/Configuration.kt +++ b/android/src/main/java/com/amplitude/android/Configuration.kt @@ -2,12 +2,12 @@ package com.amplitude.android import android.content.Context import com.amplitude.android.utilities.AndroidLoggerProvider +import com.amplitude.android.utilities.AndroidStorageProvider import com.amplitude.core.Configuration import com.amplitude.core.EventCallBack import com.amplitude.core.LoggerProvider import com.amplitude.core.ServerZone import com.amplitude.core.StorageProvider -import com.amplitude.core.utilities.FileStorageProvider class Configuration( apiKey: String, @@ -16,7 +16,7 @@ class Configuration( flushIntervalMillis: Int = FLUSH_INTERVAL_MILLIS, instanceName: String = DEFAULT_INSTANCE, optOut: Boolean = false, - storageProvider: StorageProvider = FileStorageProvider(), + storageProvider: StorageProvider = AndroidStorageProvider(), loggerProvider: LoggerProvider = AndroidLoggerProvider(), minIdLength: Int? = null, partnerId: String? = null, diff --git a/android/src/main/java/com/amplitude/android/plugins/AndroidContextPlugin.kt b/android/src/main/java/com/amplitude/android/plugins/AndroidContextPlugin.kt index 6a340362..25d2dd99 100644 --- a/android/src/main/java/com/amplitude/android/plugins/AndroidContextPlugin.kt +++ b/android/src/main/java/com/amplitude/android/plugins/AndroidContextPlugin.kt @@ -6,6 +6,7 @@ import com.amplitude.common.android.AndroidContextProvider import com.amplitude.core.Amplitude import com.amplitude.core.events.BaseEvent import com.amplitude.core.platform.Plugin +import java.util.UUID class AndroidContextPlugin : Plugin { override val type: Plugin.Type = Plugin.Type.Before @@ -49,9 +50,21 @@ class AndroidContextPlugin : Plugin { private fun applyContextData(event: BaseEvent) { val configuration = amplitude.configuration as Configuration - event.library = "$SDK_LIBRARY/$SDK_VERSION" - event.userId = amplitude.store.userId - event.deviceId = amplitude.store.deviceId + event.timestamp ?: let { + event.timestamp = System.currentTimeMillis() + } + event.insertId ?: let { + event.insertId = UUID.randomUUID().toString() + } + event.library ?: let { + event.library = "$SDK_LIBRARY/$SDK_VERSION" + } + event.userId ?: let { + event.userId = amplitude.store.userId + } + event.deviceId ?: let { + event.deviceId = amplitude.store.deviceId + } event.sessionId = (amplitude as com.amplitude.android.Amplitude).sessionId val trackingOptions = configuration.trackingOptions if (configuration.enableCoppaControl) { diff --git a/android/src/main/java/com/amplitude/android/utilities/AndroidKVS.kt b/android/src/main/java/com/amplitude/android/utilities/AndroidKVS.kt new file mode 100644 index 00000000..901e6de9 --- /dev/null +++ b/android/src/main/java/com/amplitude/android/utilities/AndroidKVS.kt @@ -0,0 +1,14 @@ +package com.amplitude.android.utilities + +import android.content.SharedPreferences +import com.amplitude.id.utilities.KeyValueStore + +class AndroidKVS(private val sharedPreferences: SharedPreferences) : KeyValueStore { + override fun getLong(key: String, defaultVal: Long): Long { + return sharedPreferences.getLong(key, defaultVal) + } + + override fun putLong(key: String, value: Long): Boolean { + return sharedPreferences.edit().putLong(key, value).commit() + } +} diff --git a/android/src/main/java/com/amplitude/android/utilities/AndroidStorage.kt b/android/src/main/java/com/amplitude/android/utilities/AndroidStorage.kt index 68f89b3b..66df52bc 100644 --- a/android/src/main/java/com/amplitude/android/utilities/AndroidStorage.kt +++ b/android/src/main/java/com/amplitude/android/utilities/AndroidStorage.kt @@ -1,44 +1,74 @@ package com.amplitude.android.utilities +import android.content.Context +import android.content.SharedPreferences import com.amplitude.core.Amplitude import com.amplitude.core.Configuration +import com.amplitude.core.EventCallBack import com.amplitude.core.Storage import com.amplitude.core.StorageProvider import com.amplitude.core.events.BaseEvent import com.amplitude.core.platform.EventPipeline +import com.amplitude.core.utilities.EventsFileManager +import com.amplitude.core.utilities.EventsFileStorage +import com.amplitude.core.utilities.FileResponseHandler +import com.amplitude.core.utilities.JSONUtil import com.amplitude.core.utilities.ResponseHandler import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import org.json.JSONArray +import java.io.BufferedReader +import java.io.File class AndroidStorage( - val amplitude: Amplitude -) : Storage { + context: Context, + apiKey: String +) : Storage, EventsFileStorage { + + companion object { + const val STORAGE_PREFIX = "amplitude-android" + } + + private val sharedPreferences: SharedPreferences = + context.getSharedPreferences("$STORAGE_PREFIX-$apiKey", Context.MODE_PRIVATE) + private val storageDirectory: File = context.getDir("amplitude-disk-queue", Context.MODE_PRIVATE) + private val eventsFile = + EventsFileManager(storageDirectory, apiKey, AndroidKVS(sharedPreferences)) + private val eventCallbacksMap = mutableMapOf() + override suspend fun writeEvent(event: BaseEvent) { - TODO("Not yet implemented") + eventsFile.storeEvent(JSONUtil.eventToString(event)) + event.callback?.let { callback -> + event.insertId?. let { + eventCallbacksMap.put(it, callback) + } + } } override suspend fun write(key: Storage.Constants, value: String) { - TODO("Not yet implemented") + sharedPreferences.edit().putString(key.rawVal, value).apply() } override suspend fun rollover() { - TODO("Not yet implemented") + eventsFile.rollover() } override fun read(key: Storage.Constants): String? { - TODO("Not yet implemented") + return sharedPreferences.getString(key.rawVal, null) } override fun readEventsContent(): List { - TODO("Not yet implemented") + return eventsFile.read() } override fun getEventsString(content: Any): String { - TODO("Not yet implemented") + val bufferedReader: BufferedReader = File(content as String).bufferedReader() + bufferedReader.use { + return it.readText() + } } override fun getResponseHandler( - storage: Storage, eventPipeline: EventPipeline, configuration: Configuration, scope: CoroutineScope, @@ -46,12 +76,37 @@ class AndroidStorage( events: Any, eventsString: String ): ResponseHandler { - TODO("Not yet implemented") + return FileResponseHandler( + this, + eventPipeline, + configuration, + scope, + dispatcher, + events as String, + eventsString + ) + } + + override fun removeFile(filePath: String): Boolean { + return eventsFile.remove(filePath) + } + + override fun getEventCallback(insertId: String): EventCallBack? { + return eventCallbacksMap.getOrDefault(insertId, null) + } + + override fun removeEventCallback(insertId: String) { + eventCallbacksMap.remove(insertId) + } + + override fun splitEventFile(filePath: String, events: JSONArray) { + eventsFile.splitFile(filePath, events) } } class AndroidStorageProvider : StorageProvider { override fun getStorage(amplitude: Amplitude): Storage { - return AndroidStorage(amplitude) + val configuration = amplitude.configuration as com.amplitude.android.Configuration + return AndroidStorage(configuration.context, configuration.apiKey) } } diff --git a/core/src/main/java/com/amplitude/core/Storage.kt b/core/src/main/java/com/amplitude/core/Storage.kt index 7c879a17..f990297d 100644 --- a/core/src/main/java/com/amplitude/core/Storage.kt +++ b/core/src/main/java/com/amplitude/core/Storage.kt @@ -28,7 +28,7 @@ interface Storage { fun getEventsString(content: Any): String - fun getResponseHandler(storage: Storage, eventPipeline: EventPipeline, configuration: Configuration, scope: CoroutineScope, dispatcher: CoroutineDispatcher, events: Any, eventsString: String): ResponseHandler + fun getResponseHandler(eventPipeline: EventPipeline, configuration: Configuration, scope: CoroutineScope, dispatcher: CoroutineDispatcher, events: Any, eventsString: String): ResponseHandler } interface StorageProvider { diff --git a/core/src/main/java/com/amplitude/core/platform/EventPipeline.kt b/core/src/main/java/com/amplitude/core/platform/EventPipeline.kt index 4d3993be..305250b2 100644 --- a/core/src/main/java/com/amplitude/core/platform/EventPipeline.kt +++ b/core/src/main/java/com/amplitude/core/platform/EventPipeline.kt @@ -113,7 +113,7 @@ class EventPipeline( // Upload the payloads. connection.close() } - val responseHandler = storage.getResponseHandler(storage, this@EventPipeline, amplitude.configuration, scope, amplitude.retryDispatcher, events, eventsString) + val responseHandler = storage.getResponseHandler(this@EventPipeline, amplitude.configuration, scope, amplitude.retryDispatcher, events, eventsString) responseHandler?.handle(connection.response) } catch (e: Exception) { e.message?.let { diff --git a/core/src/main/java/com/amplitude/core/platform/plugins/ContextPlugin.kt b/core/src/main/java/com/amplitude/core/platform/plugins/ContextPlugin.kt index 735fafc9..a69cbb1d 100644 --- a/core/src/main/java/com/amplitude/core/platform/plugins/ContextPlugin.kt +++ b/core/src/main/java/com/amplitude/core/platform/plugins/ContextPlugin.kt @@ -15,15 +15,21 @@ class ContextPlugin : Plugin { } private fun applyContextData(event: BaseEvent) { - event.timestamp ?: run { + event.timestamp ?: let { event.timestamp = System.currentTimeMillis() } - event.insertId ?: run { + event.insertId ?: let { event.insertId = UUID.randomUUID().toString() } - event.library = Constants.SDK_LIBRARY + "/" + Constants.SDK_VERSION - event.userId = amplitude.store.userId - event.deviceId = amplitude.store.deviceId + event.library ?: let { + event.library = "${Constants.SDK_LIBRARY}/${Constants.SDK_VERSION}" + } + event.userId ?: let { + event.userId = amplitude.store.userId + } + event.deviceId ?: let { + event.deviceId = amplitude.store.deviceId + } event.partnerId ?: let { amplitude.configuration.partnerId ?. let { event.partnerId = it diff --git a/core/src/main/java/com/amplitude/core/utilities/FileResponseHandler.kt b/core/src/main/java/com/amplitude/core/utilities/FileResponseHandler.kt index d603ddba..ec61cb68 100644 --- a/core/src/main/java/com/amplitude/core/utilities/FileResponseHandler.kt +++ b/core/src/main/java/com/amplitude/core/utilities/FileResponseHandler.kt @@ -8,8 +8,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.json.JSONArray -internal class FileResponseHandler( - private val storage: FileStorage, +class FileResponseHandler( + private val storage: EventsFileStorage, private val eventPipeline: EventPipeline, private val configuration: Configuration, private val scope: CoroutineScope, diff --git a/core/src/main/java/com/amplitude/core/utilities/FileStorage.kt b/core/src/main/java/com/amplitude/core/utilities/FileStorage.kt index 01ab8e29..3ad8b642 100644 --- a/core/src/main/java/com/amplitude/core/utilities/FileStorage.kt +++ b/core/src/main/java/com/amplitude/core/utilities/FileStorage.kt @@ -16,7 +16,7 @@ import java.io.File class FileStorage( private val apiKey: String -) : Storage { +) : Storage, EventsFileStorage { companion object { const val STORAGE_PREFIX = "amplitude-kotlin" @@ -68,7 +68,6 @@ class FileStorage( } override fun getResponseHandler( - storage: Storage, eventPipeline: EventPipeline, configuration: Configuration, scope: CoroutineScope, @@ -77,7 +76,7 @@ class FileStorage( eventsString: String ): ResponseHandler { return FileResponseHandler( - storage as FileStorage, + this, eventPipeline, configuration, scope, @@ -87,19 +86,19 @@ class FileStorage( ) } - fun removeFile(filePath: String): Boolean { + override fun removeFile(filePath: String): Boolean { return eventsFile.remove(filePath) } - fun getEventCallback(insertId: String): EventCallBack? { + override fun getEventCallback(insertId: String): EventCallBack? { return eventCallbacksMap.getOrDefault(insertId, null) } - fun removeEventCallback(insertId: String) { + override fun removeEventCallback(insertId: String) { eventCallbacksMap.remove(insertId) } - fun splitEventFile(filePath: String, events: JSONArray) { + override fun splitEventFile(filePath: String, events: JSONArray) { eventsFile.splitFile(filePath, events) } } @@ -109,3 +108,13 @@ class FileStorageProvider : StorageProvider { return FileStorage(amplitude.configuration.apiKey) } } + +interface EventsFileStorage { + fun removeFile(filePath: String): Boolean + + fun getEventCallback(insertId: String): EventCallBack? + + fun removeEventCallback(insertId: String) + + fun splitEventFile(filePath: String, events: JSONArray) +} diff --git a/core/src/main/java/com/amplitude/core/utilities/InMemoryStorage.kt b/core/src/main/java/com/amplitude/core/utilities/InMemoryStorage.kt index 1a8f53f7..0adbb30f 100644 --- a/core/src/main/java/com/amplitude/core/utilities/InMemoryStorage.kt +++ b/core/src/main/java/com/amplitude/core/utilities/InMemoryStorage.kt @@ -32,7 +32,10 @@ class InMemoryStorage( } override fun read(key: Storage.Constants): String? { - return valuesMap.getOrDefault(key.rawVal, null) + if (!valuesMap.contains(key.rawVal)) { + return null + } + return valuesMap.get(key.rawVal) } override fun readEventsContent(): List { @@ -51,7 +54,6 @@ class InMemoryStorage( } override fun getResponseHandler( - storage: Storage, eventPipeline: EventPipeline, configuration: Configuration, scope: CoroutineScope,