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

fix: add setSessionId() method #155

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
24 changes: 20 additions & 4 deletions android/src/main/java/com/amplitude/android/Amplitude.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.amplitude.core.platform.plugins.GetAmpliExtrasPlugin
import com.amplitude.core.utilities.FileStorage
import com.amplitude.id.IdentityConfiguration
import kotlinx.coroutines.launch
import java.util.Date

open class Amplitude(
configuration: Configuration
Expand Down Expand Up @@ -86,10 +87,6 @@ open class Amplitude(
fun onEnterForeground(timestamp: Long) {
inForeground = true

if ((configuration as Configuration).optOut) {
return
}

val dummyEnterForegroundEvent = BaseEvent()
dummyEnterForegroundEvent.eventType = DUMMY_ENTER_FOREGROUND_EVENT
dummyEnterForegroundEvent.timestamp = timestamp
Expand All @@ -112,6 +109,21 @@ open class Amplitude(
}
}

fun setSessionId(timestamp: Long): Amplitude {
val dummySetSessionEvent = BaseEvent()
dummySetSessionEvent.eventType = DUMMY_SET_SESSION_EVENT
dummySetSessionEvent.sessionId = timestamp
dummySetSessionEvent.timestamp = timestamp
timeline.process(dummySetSessionEvent)
return this
}

fun setSessionId(date: Date): Amplitude {
val timestamp = date.time
setSessionId(timestamp)
return this
}

private fun registerShutdownHook() {
Runtime.getRuntime().addShutdownHook(object : Thread() {
override fun run() {
Expand All @@ -138,6 +150,10 @@ open class Amplitude(
* The event type for dummy exit foreground events.
*/
internal const val DUMMY_EXIT_FOREGROUND_EVENT = "dummy_exit_foreground"
/**
* The event type for dummy "set session id" events.
*/
internal const val DUMMY_SET_SESSION_EVENT = "dummy_set_session"
}
}
/**
Expand Down
38 changes: 28 additions & 10 deletions android/src/main/java/com/amplitude/android/Timeline.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,15 @@ class Timeline : Timeline() {
// do nothing
} else if (event.eventType == Amplitude.DUMMY_ENTER_FOREGROUND_EVENT) {
skipEvent = true
sessionEvents = startNewSessionIfNeeded(eventTimestamp)
sessionEvents = startNewSessionIfNeeded(eventTimestamp, false)
} else if (event.eventType == Amplitude.DUMMY_EXIT_FOREGROUND_EVENT) {
skipEvent = true
refreshSessionTime(eventTimestamp)
} else if (event.eventType == Amplitude.DUMMY_SET_SESSION_EVENT) {
skipEvent = true
sessionEvents = if ((eventSessionId ?: -1) >= 0) startNewSession(eventSessionId!!) else endCurrentSession()
} else {
if (!message.inForeground) {
sessionEvents = startNewSessionIfNeeded(eventTimestamp)
} else {
refreshSessionTime(eventTimestamp)
}
sessionEvents = startNewSessionIfNeeded(eventTimestamp, message.inForeground)
}

if (!skipEvent && event.sessionId == null) {
Expand Down Expand Up @@ -111,8 +110,8 @@ class Timeline : Timeline() {
}
}

private suspend fun startNewSessionIfNeeded(timestamp: Long): Iterable<BaseEvent>? {
if (inSession() && isWithinMinTimeBetweenSessions(timestamp)) {
private suspend fun startNewSessionIfNeeded(timestamp: Long, inForeground: Boolean): Iterable<BaseEvent>? {
if (inSession() && (inForeground || isWithinMinTimeBetweenSessions(timestamp))) {
refreshSessionTime(timestamp)
return null
}
Expand All @@ -129,7 +128,7 @@ class Timeline : Timeline() {
val configuration = amplitude.configuration as Configuration
// If any trackingSessionEvents is false (default value is true), means it is manually set
@Suppress("DEPRECATION")
val trackingSessionEvents = configuration.trackingSessionEvents && configuration.defaultTracking.sessions
val trackingSessionEvents = configuration.trackingSessionEvents && configuration.defaultTracking.sessions && !configuration.optOut

// end previous session
if (trackingSessionEvents && inSession()) {
Expand All @@ -147,13 +146,32 @@ class Timeline : Timeline() {
val sessionStartEvent = BaseEvent()
sessionStartEvent.eventType = Amplitude.START_SESSION_EVENT
sessionStartEvent.timestamp = timestamp
sessionStartEvent.sessionId = sessionId
sessionStartEvent.sessionId = timestamp
sessionEvents.add(sessionStartEvent)
}

return sessionEvents
}

private suspend fun endCurrentSession(): Iterable<BaseEvent> {
val sessionEvents = mutableListOf<BaseEvent>()
val configuration = amplitude.configuration as Configuration
// If any trackingSessionEvents is false (default value is true), means it is manually set
@Suppress("DEPRECATION")
val trackingSessionEvents = configuration.trackingSessionEvents && configuration.defaultTracking.sessions && !configuration.optOut

if (trackingSessionEvents && inSession()) {
val sessionEndEvent = BaseEvent()
sessionEndEvent.eventType = Amplitude.END_SESSION_EVENT
sessionEndEvent.timestamp = if (lastEventTime > 0) lastEventTime else null
sessionEndEvent.sessionId = sessionId
sessionEvents.add(sessionEndEvent)
}

setSessionId(-1)
return sessionEvents
}

private suspend fun refreshSessionTime(timestamp: Long) {
if (!inSession()) {
return
Expand Down
214 changes: 214 additions & 0 deletions android/src/test/java/com/amplitude/android/AmplitudeSessionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,220 @@ class AmplitudeSessionTest {
Assertions.assertEquals(1, tracks.count())
}

@Test
fun amplitude_setSessionIdInBackgroundShouldStartNewSession() = runTest {
val amplitude = Amplitude(createConfiguration())
setDispatcher(amplitude, testScheduler)

val mockedPlugin = spyk(StubPlugin())
amplitude.add(mockedPlugin)

amplitude.isBuilt.await()

amplitude.track(createEvent(100, "test event 1"))
amplitude.setSessionId(150)
amplitude.track(createEvent(200, "test event 2"))

advanceUntilIdle()
Thread.sleep(100)

val tracks = mutableListOf<BaseEvent>()

verify {
mockedPlugin.track(capture(tracks))
}

tracks.sortBy { event -> event.eventId }

Assertions.assertEquals(5, tracks.count())

var event = tracks[0]
Assertions.assertEquals(Amplitude.START_SESSION_EVENT, event.eventType)
Assertions.assertEquals(100, event.sessionId)
Assertions.assertEquals(100, event.timestamp)

event = tracks[1]
Assertions.assertEquals("test event 1", event.eventType)
Assertions.assertEquals(100, event.sessionId)
Assertions.assertEquals(100, event.timestamp)

event = tracks[2]
Assertions.assertEquals(Amplitude.END_SESSION_EVENT, event.eventType)
Assertions.assertEquals(100, event.sessionId)
Assertions.assertEquals(100, event.timestamp)

event = tracks[3]
Assertions.assertEquals(Amplitude.START_SESSION_EVENT, event.eventType)
Assertions.assertEquals(150, event.sessionId)
Assertions.assertEquals(150, event.timestamp)

event = tracks[4]
Assertions.assertEquals("test event 2", event.eventType)
Assertions.assertEquals(150, event.sessionId)
Assertions.assertEquals(200, event.timestamp)
}

@Test
fun amplitude_setSessionIdInForegroundShouldStartNewSession() = runTest {
val amplitude = Amplitude(createConfiguration())
setDispatcher(amplitude, testScheduler)

val mockedPlugin = spyk(StubPlugin())
amplitude.add(mockedPlugin)

amplitude.isBuilt.await()

amplitude.onEnterForeground(1000)
amplitude.track(createEvent(1050, "test event 1"))
amplitude.setSessionId(1100)
amplitude.track(createEvent(2000, "test event 2"))

advanceUntilIdle()
Thread.sleep(100)

val tracks = mutableListOf<BaseEvent>()

verify {
mockedPlugin.track(capture(tracks))
}

tracks.sortBy { event -> event.eventId }

Assertions.assertEquals(5, tracks.count())

var event = tracks[0]
Assertions.assertEquals(Amplitude.START_SESSION_EVENT, event.eventType)
Assertions.assertEquals(1000, event.sessionId)
Assertions.assertEquals(1000, event.timestamp)

event = tracks[1]
Assertions.assertEquals("test event 1", event.eventType)
Assertions.assertEquals(1000, event.sessionId)
Assertions.assertEquals(1050, event.timestamp)

event = tracks[2]
Assertions.assertEquals(Amplitude.END_SESSION_EVENT, event.eventType)
Assertions.assertEquals(1000, event.sessionId)
Assertions.assertEquals(1050, event.timestamp)

event = tracks[3]
Assertions.assertEquals(Amplitude.START_SESSION_EVENT, event.eventType)
Assertions.assertEquals(1100, event.sessionId)
Assertions.assertEquals(1100, event.timestamp)

event = tracks[4]
Assertions.assertEquals("test event 2", event.eventType)
Assertions.assertEquals(1100, event.sessionId)
Assertions.assertEquals(2000, event.timestamp)
}

@Test
fun amplitude_setSessionEndInBackgroundShouldEndCurrentSession() = runTest {
val amplitude = Amplitude(createConfiguration())
setDispatcher(amplitude, testScheduler)

val mockedPlugin = spyk(StubPlugin())
amplitude.add(mockedPlugin)

amplitude.isBuilt.await()

amplitude.track(createEvent(1000, "test event 1"))
amplitude.setSessionId(-1)
amplitude.track(createEvent(2000, "test event 2"))

advanceUntilIdle()
Thread.sleep(100)

val tracks = mutableListOf<BaseEvent>()

verify {
mockedPlugin.track(capture(tracks))
}

tracks.sortBy { event -> event.eventId }

Assertions.assertEquals(5, tracks.count())

var event = tracks[0]
Assertions.assertEquals(Amplitude.START_SESSION_EVENT, event.eventType)
Assertions.assertEquals(1000, event.sessionId)
Assertions.assertEquals(1000, event.timestamp)

event = tracks[1]
Assertions.assertEquals("test event 1", event.eventType)
Assertions.assertEquals(1000, event.sessionId)
Assertions.assertEquals(1000, event.timestamp)

event = tracks[2]
Assertions.assertEquals(Amplitude.END_SESSION_EVENT, event.eventType)
Assertions.assertEquals(1000, event.sessionId)
Assertions.assertEquals(1000, event.timestamp)

event = tracks[3]
Assertions.assertEquals(Amplitude.START_SESSION_EVENT, event.eventType)
Assertions.assertEquals(2000, event.sessionId)
Assertions.assertEquals(2000, event.timestamp)

event = tracks[4]
Assertions.assertEquals("test event 2", event.eventType)
Assertions.assertEquals(2000, event.sessionId)
Assertions.assertEquals(2000, event.timestamp)
}

@Test
fun amplitude_setSessionEndInForegroundShouldEndCurrentSession() = runTest {
val amplitude = Amplitude(createConfiguration())
setDispatcher(amplitude, testScheduler)

val mockedPlugin = spyk(StubPlugin())
amplitude.add(mockedPlugin)

amplitude.isBuilt.await()

amplitude.onEnterForeground(1000)
amplitude.track(createEvent(1500, "test event 1"))
amplitude.setSessionId(-1)
amplitude.track(createEvent(2000, "test event 2"))

advanceUntilIdle()
Thread.sleep(100)

val tracks = mutableListOf<BaseEvent>()

verify {
mockedPlugin.track(capture(tracks))
}

tracks.sortBy { event -> event.eventId }

Assertions.assertEquals(5, tracks.count())

var event = tracks[0]
Assertions.assertEquals(Amplitude.START_SESSION_EVENT, event.eventType)
Assertions.assertEquals(1000, event.sessionId)
Assertions.assertEquals(1000, event.timestamp)

event = tracks[1]
Assertions.assertEquals("test event 1", event.eventType)
Assertions.assertEquals(1000, event.sessionId)
Assertions.assertEquals(1500, event.timestamp)

event = tracks[2]
Assertions.assertEquals(Amplitude.END_SESSION_EVENT, event.eventType)
Assertions.assertEquals(1000, event.sessionId)
Assertions.assertEquals(1500, event.timestamp)

event = tracks[3]
Assertions.assertEquals(Amplitude.START_SESSION_EVENT, event.eventType)
Assertions.assertEquals(2000, event.sessionId)
Assertions.assertEquals(2000, event.timestamp)

event = tracks[4]
Assertions.assertEquals("test event 2", event.eventType)
Assertions.assertEquals(2000, event.sessionId)
Assertions.assertEquals(2000, event.timestamp)
}

private fun createEvent(timestamp: Long, eventType: String, sessionId: Long? = null): BaseEvent {
val event = BaseEvent()
event.userId = "user"
Expand Down