Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add android context plugin #14

Merged
merged 4 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ android {
}

dependencies {
implementation project(':common')
implementation project(':common-android')
implementation project(':id')
implementation project(':core')
api project(':common')
api project(':common-android')
api project(':id')
api project(':core')
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'

Expand Down
137 changes: 136 additions & 1 deletion android/src/main/java/com/amplitude/android/Amplitude.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,154 @@
package com.amplitude.android

import android.content.Context
import com.amplitude.android.plugins.AndroidContextPlugin
import com.amplitude.android.plugins.AndroidLifecyclePlugin
import com.amplitude.core.Amplitude
import com.amplitude.core.Storage
import com.amplitude.core.platform.plugins.AmplitudeDestination
import com.amplitude.core.utilities.AnalyticsIdentityListener
import com.amplitude.core.utilities.FileStorage
import com.amplitude.id.FileIdentityStorageProvider
import com.amplitude.id.IdentityConfiguration
import com.amplitude.id.IdentityContainer
import kotlinx.coroutines.launch

open class Amplitude(
configuration: Configuration
) : Amplitude(configuration) {

private var inForeground = false
var sessionId: Long = -1
private set
private var lastEventId: Long = -1
private var lastEventTime: Long = -1
private var previousSessionId: Long = -1

override fun build() {
idContainer = IdentityContainer.getInstance(IdentityConfiguration(instanceName = configuration.instanceName, apiKey = configuration.apiKey, identityStorageProvider = FileIdentityStorageProvider()))
val storageDirectory = (configuration as Configuration).context.getDir("${FileStorage.STORAGE_PREFIX}-${configuration.instanceName}", Context.MODE_PRIVATE)
idContainer = IdentityContainer.getInstance(
IdentityConfiguration(
instanceName = configuration.instanceName,
apiKey = configuration.apiKey,
identityStorageProvider = FileIdentityStorageProvider(),
storageDirectory = storageDirectory
)
)
idContainer.identityManager.addIdentityListener(AnalyticsIdentityListener(store))
amplitudeScope.launch(amplitudeDispatcher) {
previousSessionId = storage.read(Storage.Constants.PREVIOUS_SESSION_ID) ?.let {
it.toLong()
} ?: -1

add(AndroidContextPlugin())
}
add(AmplitudeDestination())
add(AndroidLifecyclePlugin())
}

fun onEnterForeground(timestamp: Long) {
amplitudeScope.launch(amplitudeDispatcher) {
startNewSessionIfNeeded(timestamp)
inForeground = true
}
}

fun onExitForeground(timestamp: Long) {
amplitudeScope.launch(amplitudeDispatcher) {
inForeground = false
if ((configuration as Configuration).flushEventsOnClose) {
flush()
}
storage.write(Storage.Constants.PREVIOUS_SESSION_ID, sessionId.toString())
storage.write(Storage.Constants.LAST_EVENT_TIME, lastEventTime.toString())
}
}

fun startNewSessionIfNeeded(timestamp: Long): Boolean {
if (inSession()) {

if (isWithinMinTimeBetweenSessions(timestamp)) {
refreshSessionTime(timestamp)
return false
}

startNewSession(timestamp)
return true
}

// no current session - check for previous session
if (isWithinMinTimeBetweenSessions(timestamp)) {
if (previousSessionId == -1L) {
startNewSession(timestamp)
return true
}

// extend previous session
setSessionId(previousSessionId)
refreshSessionTime(timestamp)
return false
}

startNewSession(timestamp)
return true
}

private fun setSessionId(timestamp: Long) {
sessionId = timestamp
previousSessionId = timestamp
amplitudeScope.launch(amplitudeDispatcher) {
storage.write(Storage.Constants.PREVIOUS_SESSION_ID, timestamp.toString())
}
}

private fun startNewSession(timestamp: Long) {
// end previous session
if ((configuration as Configuration).trackingSessionEvents) {
sendSessionEvent(END_SESSION_EVENT)
}

// start new session
setSessionId(timestamp)
refreshSessionTime(timestamp)
if ((configuration as Configuration).trackingSessionEvents) {
sendSessionEvent(START_SESSION_EVENT)
}
}

private fun sendSessionEvent(sessionEvent: String) {
if (!inSession()) {
return
}
track(sessionEvent)
}

fun refreshSessionTime(timestamp: Long) {
if (!inSession()) {
return
}
lastEventTime = timestamp
amplitudeScope.launch(amplitudeDispatcher) {
storage.write(Storage.Constants.LAST_EVENT_TIME, timestamp.toString())
}
}

private fun isWithinMinTimeBetweenSessions(timestamp: Long): Boolean {
val sessionLimit: Long = (configuration as Configuration).minTimeBetweenSessionsMillis
return timestamp - lastEventTime < sessionLimit
}

private fun inSession(): Boolean {
return sessionId >= 0
}

companion object {
/**
* The event type for start session events.
*/
const val START_SESSION_EVENT = "session_start"
/**
* The event type for end session events.
*/
const val END_SESSION_EVENT = "session_end"
}
}
20 changes: 15 additions & 5 deletions android/src/main/java/com/amplitude/android/Configuration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.amplitude.core.utilities.FileStorageProvider

class Configuration(
apiKey: String,
context: Context,
val context: Context,
flushQueueSize: Int = FLUSH_QUEUE_SIZE,
flushIntervalMillis: Int = FLUSH_INTERVAL_MILLIS,
instanceName: String = DEFAULT_INSTANCE,
Expand All @@ -19,7 +19,17 @@ class Configuration(
loggerProvider: LoggerProvider = AndroidLoggerProvider(),
minIdLength: Int? = null,
callback: ((BaseEvent) -> Unit)? = null,
useAdvertisingIdForDeviceId: Boolean = false,
useAppSetIdForDeviceId: Boolean = false,
enableCoppaControl: Boolean = false
) : Configuration(apiKey, flushQueueSize, flushIntervalMillis, instanceName, optOut, storageProvider, loggerProvider, minIdLength, callback)
val useAdvertisingIdForDeviceId: Boolean = false,
val useAppSetIdForDeviceId: Boolean = false,
val newDeviceIdPerInstall: Boolean = false,
val trackingOptions: TrackingOptions = TrackingOptions(),
val enableCoppaControl: Boolean = false,
val locationListening: Boolean = true,
val flushEventsOnClose: Boolean = true,
val minTimeBetweenSessionsMillis: Long = MIN_TIME_BETWEEN_SESSIONS_MILLIS,
val trackingSessionEvents: Boolean = true
) : Configuration(apiKey, flushQueueSize, flushIntervalMillis, instanceName, optOut, storageProvider, loggerProvider, minIdLength, callback) {
companion object {
const val MIN_TIME_BETWEEN_SESSIONS_MILLIS: Long = 5 * 60 * 1000
}
}
Loading