11package com.segment.analytics.kotlin.destinations.amplitude
22
3+ import android.content.Context
4+ import android.content.SharedPreferences
35import com.segment.analytics.kotlin.core.*
46import com.segment.analytics.kotlin.core.platform.EventPlugin
57import com.segment.analytics.kotlin.core.platform.Plugin
68import com.segment.analytics.kotlin.core.platform.VersionedPlugin
79import com.segment.analytics.kotlin.core.platform.plugins.logger.*
810import com.segment.analytics.kotlin.core.utilities.putIntegrations
9- import java.util.concurrent.locks.ReentrantLock
10- import kotlin.concurrent.withLock
11+ import com.segment.analytics.kotlin.core.utilities.updateJsonObject
12+ import com.segment.analytics.kotlin.core.utilities.*
13+ import java.util.concurrent.atomic.AtomicBoolean
14+ import java.util.concurrent.atomic.AtomicLong
15+ import androidx.core.content.edit
1116
1217// A Destination plugin that adds session tracking to Amplitude cloud mode.
1318class AmplitudeSession (private val sessionTimeoutMs : Long = 300000 ) : EventPlugin, VersionedPlugin {
1419
20+ companion object {
21+ const val LAST_EVENT_ID = " last_event_id"
22+ const val PREVIOUS_SESSION_ID = " previous_session_id"
23+ const val LAST_EVENT_TIME = " last_event_time"
24+
25+ const val AMP_PREFIX = " [Amplitude] "
26+ const val AMP_SESSION_END_EVENT = " session_end"
27+ const val AMP_SESSION_START_EVENT = " session_start"
28+ }
29+
1530 override val type: Plugin .Type = Plugin .Type .Enrichment
1631 override lateinit var analytics: Analytics
17- private var eventSessionId = - 1L
18- private var sessionId = eventSessionId
1932 private val key = " Actions Amplitude"
20- private val ampSessionEndEvent = " session_end"
21- private val ampSessionStartEvent = " session_start"
22- private var active = false
23- private var lastEventFiredTime = java.lang.System .currentTimeMillis()
24- private var sessionUpdateLock = ReentrantLock ()
33+
34+
35+ private val active = AtomicBoolean (false )
36+ private var prefs: SharedPreferences ? = null
37+
38+ private val _sessionId = AtomicLong (- 1L )
39+ private var sessionId
40+ get() = _sessionId .get()
41+ set(value) {
42+ _sessionId .set(value)
43+ prefs?.edit(commit = true ) { putLong(PREVIOUS_SESSION_ID , value) }
44+ }
45+ private val _lastEventTime = AtomicLong (- 1L )
46+ private var lastEventTime
47+ get() = _lastEventTime .get()
48+ set(value) {
49+ _lastEventTime .set(value)
50+ prefs?.edit(commit = true ) { putLong(LAST_EVENT_TIME , value) }
51+ }
2552
2653 override fun setup (analytics : Analytics ) {
2754 super .setup(analytics)
28- startNewSessionIfNecessary()
55+
56+ var context: Context ? = null
57+ if (analytics.configuration.application is Context ){
58+ context = analytics.configuration.application as Context
59+ }
60+ context?.let {
61+ prefs = context.getSharedPreferences(" analytics-android-${analytics.configuration.writeKey} " , Context .MODE_PRIVATE )
62+ prefs?.let {
63+ _sessionId .set(it.getLong(PREVIOUS_SESSION_ID , - 1 ))
64+ _lastEventTime .set(it.getLong(LAST_EVENT_TIME , - 1 ))
65+ }
66+ }
67+
68+ if (sessionId == - 1L ) {
69+ startNewSession()
70+ }
71+ else {
72+ startNewSessionIfNecessary()
73+ }
2974 }
75+
3076 override fun update (settings : Settings , type : Plugin .UpdateType ) {
31- active = settings.hasIntegrationSettings(key)
77+ if (type != Plugin .UpdateType .Initial ) return
78+
79+ active.set(settings.hasIntegrationSettings(key))
3280 }
3381
3482 override fun execute (event : BaseEvent ): BaseEvent ? {
35- if (! active) { // If amplitude destination is disabled, no need to do this enrichment
83+ if (! active.get() ) { // If amplitude destination is disabled, no need to do this enrichment
3684 return event
3785 }
3886
3987 startNewSessionIfNecessary()
4088
41- val modified = super .execute(event)
4289 analytics.log(
4390 message = " Running ${event.type} payload through AmplitudeSession"
4491 )
4592
46- if (event is TrackEvent ) {
47- if (event.event == ampSessionStartEvent) {
48- // Update session ID for all events after this in the queue
49- eventSessionId = sessionId
50- analytics.log(message = " NewSession = $eventSessionId " )
93+ var modified = super .execute(event)
94+ if (modified is ScreenEvent ) {
95+ val screenName = modified.name
96+ modified.properties = updateJsonObject(modified.properties) {
97+ // amp needs screen name in the properties for screen event
98+ it[" name" ] = screenName
99+ }
100+ }
101+ else if (modified is TrackEvent ) {
102+ if (modified.event == AMP_SESSION_START_EVENT ) {
103+ modified = modified.disableCloudIntegrations(exceptKeys = listOf (key))
104+ analytics.log(message = " NewSession = $sessionId " )
51105 }
52- else if (event.event == ampSessionEndEvent) {
53- analytics.log(message = " EndSession = $eventSessionId " )
106+ else if (modified.event == AMP_SESSION_END_EVENT ) {
107+ modified = modified.disableCloudIntegrations(exceptKeys = listOf (key))
108+ analytics.log(message = " EndSession = $sessionId " )
109+ }
110+ else if (modified.event.contains(AMP_PREFIX )) {
111+ modified = modified.disableCloudIntegrations(exceptKeys = listOf (key))
112+ modified = modified.putIntegrations(key, mapOf (" session_id" to sessionId))
54113 }
55114 }
56115
57- return modified?.putIntegrations(key, mapOf (" session_id" to eventSessionId))
116+ // renew the session if there are activities
117+ lastEventTime = java.lang.System .currentTimeMillis()
118+ return modified
58119 }
59120
60121 override fun track (payload : TrackEvent ): BaseEvent {
@@ -68,38 +129,42 @@ class AmplitudeSession (private val sessionTimeoutMs : Long = 300000) : EventPlu
68129 }
69130
70131 private fun onBackground () {
132+ lastEventTime = java.lang.System .currentTimeMillis()
71133 }
72134
73135 private fun onForeground () {
74136 startNewSessionIfNecessary()
75137 }
76138
77139 private fun startNewSession () {
78- analytics.track(ampSessionStartEvent)
140+ sessionId = java.lang.System .currentTimeMillis()
141+
142+ // need to snapshot the current sessionId
143+ // because when the enrichment closure is applied the sessionId might have changed
144+ val copy = sessionId
145+ analytics.track(AMP_SESSION_START_EVENT ) { event ->
146+ event?.putIntegrations(key, mapOf (" session_id" to copy))
147+ }
79148 }
80149
81150 private fun endSession () {
82- analytics.track(ampSessionEndEvent)
151+ // need to snapshot the current sessionId
152+ // because when the enrichment closure is applied the sessionId might have changed
153+ val copy = sessionId
154+ analytics.track(AMP_SESSION_END_EVENT ) { event ->
155+ event?.putIntegrations(key, mapOf (" session_id" to copy))
156+ }
83157 }
84158
85159 private fun startNewSessionIfNecessary () {
86- sessionUpdateLock.withLock {
87- val current = java.lang.System .currentTimeMillis()
88- // Make sure the first event has a valid ID and we send a session start.
89- // Subsequent events should have session IDs updated after the session track event is sent
90- if (eventSessionId == - 1L || sessionId == - 1L ) {
91- sessionId = current
92- eventSessionId = current
93- lastEventFiredTime = current
94- startNewSession()
95- } else if (current - lastEventFiredTime >= sessionTimeoutMs) {
96- sessionId = current
97- lastEventFiredTime = current
98-
99- endSession()
100- startNewSession()
101- }
102- }
160+ val current = java.lang.System .currentTimeMillis()
161+ val withinSessionLimit = (current - lastEventTime < sessionTimeoutMs)
162+ if (sessionId >= 0 && withinSessionLimit) return
163+
164+ // we'll consider this our new lastEventTime
165+ lastEventTime = current
166+ endSession()
167+ startNewSession()
103168 }
104169
105170 override fun version (): String {
0 commit comments