Skip to content

Commit baf82eb

Browse files
authored
Merge pull request #2185 from OneSignal/fix/outcome_measure_zero_session_time_400_errors
Fix ending an already ended session
2 parents 9df5484 + 1ab8dcc commit baf82eb

File tree

7 files changed

+81
-12
lines changed

7 files changed

+81
-12
lines changed

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/background/IBackgroundService.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import androidx.annotation.WorkerThread
44

55
/**
66
* Implement and provide this interface as part of service registration to indicate the service
7-
* wants to be instantiated and its [backgroundRun] function called when the app is in the background. The
8-
* background process is initiated when the application is no longer in focus. Each background
9-
* service's [scheduleBackgroundRunIn] will be analyzed to determine when [backgroundRun] should be called.
7+
* wants to be instantiated and its [backgroundRun] function called when the app is in the background.
8+
* Each background service's [scheduleBackgroundRunIn] will be analyzed to determine when
9+
* [backgroundRun] should be called.
1010
*/
1111
interface IBackgroundService {
1212
/**
@@ -16,7 +16,12 @@ interface IBackgroundService {
1616
val scheduleBackgroundRunIn: Long?
1717

1818
/**
19-
* Run the background service
19+
* Run the background service.
20+
* WARNING: This may not follow your scheduleBackgroundRunIn schedule:
21+
* 1. May run more often as the lowest scheduleBackgroundRunIn
22+
* value is used across the SDK.
23+
* 2. Android doesn't guarantee exact timing on when the job is run,
24+
* so it's possible for it to be delayed by a few minutes.
2025
*/
2126
@WorkerThread
2227
suspend fun backgroundRun()

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.onesignal.session.internal.influence.Influence
88
import com.onesignal.session.internal.influence.InfluenceChannel
99
import com.onesignal.session.internal.influence.InfluenceType
1010
import com.onesignal.session.internal.influence.InfluenceType.Companion.fromString
11+
import com.onesignal.session.internal.outcomes.migrations.RemoveZeroSessionTimeRecords
1112
import kotlinx.coroutines.Dispatchers
1213
import kotlinx.coroutines.withContext
1314
import org.json.JSONArray
@@ -101,6 +102,7 @@ internal class OutcomeEventsRepository(
101102
override suspend fun getAllEventsToSend(): List<OutcomeEventParams> {
102103
val events: MutableList<OutcomeEventParams> = ArrayList()
103104
withContext(Dispatchers.IO) {
105+
RemoveZeroSessionTimeRecords.run(_databaseProvider)
104106
_databaseProvider.os.query(OutcomeEventsTable.TABLE_NAME) { cursor ->
105107
if (cursor.moveToFirst()) {
106108
do {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.onesignal.session.internal.outcomes.migrations
2+
3+
import com.onesignal.core.internal.database.IDatabaseProvider
4+
import com.onesignal.session.internal.outcomes.impl.OutcomeEventsTable
5+
6+
/**
7+
* Purpose: Clean up invalid cached os__session_duration outcome records
8+
* with zero session_time produced in SDK versions 5.1.15 to 5.1.20 so we stop
9+
* sending these requests to the backend.
10+
*
11+
* Issue: SessionService.backgroundRun() didn't account for it being run more
12+
* than one time in the background, when this happened it would create a
13+
* outcome record with zero time which is invalid.
14+
*/
15+
object RemoveZeroSessionTimeRecords {
16+
fun run(databaseProvider: IDatabaseProvider) {
17+
databaseProvider.os.delete(
18+
OutcomeEventsTable.TABLE_NAME,
19+
OutcomeEventsTable.COLUMN_NAME_NAME + " = \"os__session_duration\"" +
20+
" AND " + OutcomeEventsTable.COLUMN_NAME_SESSION_TIME + " = 0",
21+
null,
22+
)
23+
}
24+
}

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/SessionModel.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ class SessionModel : Model() {
1616
}
1717

1818
/**
19-
* Whether the session is valid.
19+
* Indicates if there is an active session.
20+
* True when app is in the foreground.
21+
* Also true in the background for a short period of time (default 30s)
22+
* as a debouncing mechanism.
2023
*/
2124
var isValid: Boolean
2225
get() = getBooleanProperty(::isValid.name) { false }

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionService.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,28 @@ internal class SessionService(
4747
private var config: ConfigModel? = null
4848
private var shouldFireOnSubscribe = false
4949

50+
// True if app has been foregrounded at least once since the app started
51+
private var hasFocused = false
52+
5053
override fun start() {
5154
session = _sessionModelStore.model
5255
config = _configModelStore.model
53-
// Reset the session validity property to drive a new session
54-
session!!.isValid = false
5556
_applicationService.addApplicationLifecycleHandler(this)
5657
}
5758

59+
/** NOTE: This triggers more often than scheduleBackgroundRunIn defined above,
60+
* as it runs on the lowest IBackgroundService.scheduleBackgroundRunIn across
61+
* the SDK.
62+
*/
5863
override suspend fun backgroundRun() {
64+
endSession()
65+
}
66+
67+
private fun endSession() {
68+
if (!session!!.isValid) return
5969
val activeDuration = session!!.activeDuration
60-
// end the session
6170
Logging.debug("SessionService.backgroundRun: Session ended. activeDuration: $activeDuration")
71+
6272
session!!.isValid = false
6373
sessionLifeCycleNotifier.fire { it.onSessionEnded(activeDuration) }
6474
session!!.activeDuration = 0L
@@ -75,14 +85,20 @@ internal class SessionService(
7585
*/
7686
override fun onFocus(firedOnSubscribe: Boolean) {
7787
Logging.log(LogLevel.DEBUG, "SessionService.onFocus() - fired from start: $firedOnSubscribe")
88+
89+
// Treat app cold starts as a new session, we attempt to end any previous session to do this.
90+
if (!hasFocused) {
91+
hasFocused = true
92+
endSession()
93+
}
94+
7895
if (!session!!.isValid) {
7996
// As the old session was made inactive, we need to create a new session
8097
shouldFireOnSubscribe = firedOnSubscribe
8198
session!!.sessionId = UUID.randomUUID().toString()
8299
session!!.startTime = _time.currentTimeMillis
83100
session!!.focusTime = session!!.startTime
84101
session!!.isValid = true
85-
86102
Logging.debug("SessionService: New session started at ${session!!.startTime}")
87103
sessionLifeCycleNotifier.fire { it.onSessionStarted() }
88104
} else {

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/session/SessionServiceTests.kt

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ class SessionServiceTests : FunSpec({
8686

8787
// Then
8888
sessionModelStore.model.isValid shouldBe true
89-
sessionModelStore.model.startTime shouldBe startTime
89+
sessionModelStore.model.startTime shouldBe mocks.currentTime
9090
sessionModelStore.model.focusTime shouldBe mocks.currentTime
91-
verify(exactly = 1) { mocks.spyCallback.onSessionActive() }
92-
verify(exactly = 0) { mocks.spyCallback.onSessionStarted() }
91+
verify(exactly = 0) { mocks.spyCallback.onSessionActive() }
92+
verify(exactly = 1) { mocks.spyCallback.onSessionStarted() }
9393
}
9494

9595
test("session active duration updated when unfocused") {
@@ -140,4 +140,19 @@ class SessionServiceTests : FunSpec({
140140
sessionModelStore.model.isValid shouldBe false
141141
verify(exactly = 1) { mocks.spyCallback.onSessionEnded(activeDuration) }
142142
}
143+
144+
test("do not trigger onSessionEnd if session is not active") {
145+
// Given
146+
val mocks = Mocks()
147+
mocks.sessionModelStore { it.isValid = false }
148+
val sessionService = mocks.sessionService
149+
sessionService.subscribe(mocks.spyCallback)
150+
sessionService.start()
151+
152+
// When
153+
sessionService.backgroundRun()
154+
155+
// Then
156+
verify(exactly = 0) { mocks.spyCallback.onSessionEnded(any()) }
157+
}
143158
})

OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/background/LocationBackgroundService.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ internal class LocationBackgroundService(
3535
return scheduleTime
3636
}
3737

38+
/** NOTE: This triggers more often than scheduleBackgroundRunIn defined above,
39+
* as it runs on the lowest IBackgroundService.scheduleBackgroundRunIn across
40+
* the SDK.
41+
*/
3842
override suspend fun backgroundRun() {
3943
_capturer.captureLastLocation()
4044
}

0 commit comments

Comments
 (0)