Skip to content

Commit 5f592d2

Browse files
authored
Add TimeProvider interface to abstract away the clock (#4905)
* Add TimeProvider interface to abstract away the clock * Add comment about default elapsed time being zero
1 parent 1abd88d commit 5f592d2

File tree

11 files changed

+136
-75
lines changed

11 files changed

+136
-75
lines changed

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessions.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ internal constructor(
4040
private val sessionCoordinator =
4141
SessionCoordinator(firebaseInstallations, backgroundDispatcher, eventGDTLogger)
4242
private val sessionSettings = SessionsSettings(firebaseApp.applicationContext)
43+
private val timeProvider: TimeProvider = Time()
4344

4445
init {
4546
val sessionInitiator =
46-
SessionInitiator(WallClock::elapsedRealtime, this::initiateSessionStart, sessionSettings)
47+
SessionInitiator(timeProvider, this::initiateSessionStart, sessionSettings)
4748
val appContext = firebaseApp.applicationContext.applicationContext
4849
if (appContext is Application) {
4950
appContext.registerActivityLifecycleCallbacks(sessionInitiator.activityLifecycleCallbacks)
@@ -60,7 +61,8 @@ internal constructor(
6061

6162
private fun initiateSessionStart() {
6263
val sessionDetails = sessionGenerator.generateNewSession()
63-
val sessionEvent = SessionEvents.startSession(firebaseApp, sessionDetails, sessionSettings)
64+
val sessionEvent =
65+
SessionEvents.startSession(firebaseApp, sessionDetails, sessionSettings, timeProvider)
6466

6567
if (sessionDetails.collectEvents) {
6668
sessionCoordinator.attemptLoggingSessionEvent(sessionEvent)

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ internal object SessionEvents {
105105
firebaseApp: FirebaseApp,
106106
sessionDetails: SessionDetails,
107107
sessionsSettings: SessionsSettings,
108-
currentTimeUs: Long = WallClock.currentTimeUs()
108+
timeProvider: TimeProvider
109109
) =
110110
SessionEvent(
111111
eventType = EventType.SESSION_START,
@@ -114,7 +114,7 @@ internal object SessionEvents {
114114
sessionDetails.sessionId,
115115
sessionDetails.firstSessionId,
116116
sessionDetails.sessionIndex,
117-
currentTimeUs,
117+
eventTimestampUs = timeProvider.currentTimeUs(),
118118
DataCollectionStatus(sessionSamplingRate = sessionsSettings.samplingRate),
119119
),
120120
applicationInfo = getApplicationInfo(firebaseApp)

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionInitiator.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import android.app.Activity
2020
import android.app.Application.ActivityLifecycleCallbacks
2121
import android.os.Bundle
2222
import com.google.firebase.sessions.settings.SessionsSettings
23-
import kotlin.time.Duration
2423

2524
/**
2625
* The [SessionInitiator] is responsible for calling the [initiateSessionStart] callback whenever a
@@ -30,22 +29,22 @@ import kotlin.time.Duration
3029
* @hide
3130
*/
3231
internal class SessionInitiator(
33-
private val elapsedRealtime: () -> Duration,
32+
private val timeProvider: TimeProvider,
3433
private val initiateSessionStart: () -> Unit,
3534
private val sessionsSettings: SessionsSettings
3635
) {
37-
private var backgroundTime = elapsedRealtime()
36+
private var backgroundTime = timeProvider.elapsedRealtime()
3837

3938
init {
4039
initiateSessionStart()
4140
}
4241

4342
fun appBackgrounded() {
44-
backgroundTime = elapsedRealtime()
43+
backgroundTime = timeProvider.elapsedRealtime()
4544
}
4645

4746
fun appForegrounded() {
48-
val interval = elapsedRealtime() - backgroundTime
47+
val interval = timeProvider.elapsedRealtime() - backgroundTime
4948
val sessionTimeout = sessionsSettings.sessionRestartTimeout
5049
if (interval > sessionTimeout) {
5150
initiateSessionStart()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.sessions
18+
19+
import android.os.SystemClock
20+
import kotlin.time.Duration
21+
import kotlin.time.Duration.Companion.milliseconds
22+
23+
/** Time provider interface, for testing purposes. */
24+
internal interface TimeProvider {
25+
fun elapsedRealtime(): Duration
26+
fun currentTimeUs(): Long
27+
}
28+
29+
/** "Wall clock" time provider. */
30+
internal class Time : TimeProvider {
31+
/**
32+
* Gets the [Duration] elapsed in "wall clock" time since device boot.
33+
*
34+
* This clock is guaranteed to be monotonic, and continues to tick even when the CPU is in power
35+
* saving modes, so is the recommend basis for general purpose interval timing.
36+
*/
37+
override fun elapsedRealtime(): Duration = SystemClock.elapsedRealtime().milliseconds
38+
39+
/**
40+
* Gets the current "wall clock" time in microseconds.
41+
*
42+
* This clock can be set by the user or the phone network, so the time may jump backwards or
43+
* forwards unpredictably. This clock should only be used when correspondence with real-world
44+
* dates and times is important, such as in a calendar or alarm clock application.
45+
*/
46+
override fun currentTimeUs(): Long = System.currentTimeMillis() * US_PER_MILLIS
47+
48+
companion object {
49+
/** Microseconds per millisecond. */
50+
private const val US_PER_MILLIS = 1000L
51+
}
52+
}

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/WallClock.kt

Lines changed: 0 additions & 35 deletions
This file was deleted.

firebase-sessions/src/test/kotlin/com/google/firebase/sessions/EventGDTLoggerTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.google.firebase.FirebaseApp
2424
import com.google.firebase.sessions.settings.SessionsSettings
2525
import com.google.firebase.sessions.testing.FakeFirebaseApp
2626
import com.google.firebase.sessions.testing.FakeProvider
27+
import com.google.firebase.sessions.testing.FakeTimeProvider
2728
import com.google.firebase.sessions.testing.FakeTransportFactory
2829
import com.google.firebase.sessions.testing.TestSessionEventData
2930
import org.junit.After
@@ -42,7 +43,7 @@ class EventGDTLoggerTest {
4243
fakeFirebaseApp.firebaseApp,
4344
TestSessionEventData.TEST_SESSION_DETAILS,
4445
SessionsSettings(fakeFirebaseApp.firebaseApp.applicationContext),
45-
TestSessionEventData.TEST_SESSION_TIMESTAMP_US,
46+
FakeTimeProvider(),
4647
)
4748
val fakeTransportFactory = FakeTransportFactory()
4849
val fakeTransportFactoryProvider = FakeProvider(fakeTransportFactory as TransportFactory)

firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionCoordinatorTest.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,18 @@ package com.google.firebase.sessions
1818

1919
import androidx.test.ext.junit.runners.AndroidJUnit4
2020
import com.google.common.truth.Truth.assertThat
21+
import com.google.firebase.FirebaseApp
2122
import com.google.firebase.sessions.settings.SessionsSettings
2223
import com.google.firebase.sessions.testing.FakeEventGDTLogger
2324
import com.google.firebase.sessions.testing.FakeFirebaseApp
2425
import com.google.firebase.sessions.testing.FakeFirebaseInstallations
26+
import com.google.firebase.sessions.testing.FakeTimeProvider
2527
import com.google.firebase.sessions.testing.TestSessionEventData
2628
import kotlinx.coroutines.ExperimentalCoroutinesApi
2729
import kotlinx.coroutines.test.StandardTestDispatcher
2830
import kotlinx.coroutines.test.runCurrent
2931
import kotlinx.coroutines.test.runTest
32+
import org.junit.After
3033
import org.junit.Test
3134
import org.junit.runner.RunWith
3235

@@ -50,7 +53,7 @@ class SessionCoordinatorTest {
5053
fakeFirebaseApp.firebaseApp,
5154
TestSessionEventData.TEST_SESSION_DETAILS,
5255
SessionsSettings(fakeFirebaseApp.firebaseApp.applicationContext),
53-
TestSessionEventData.TEST_SESSION_TIMESTAMP_US,
56+
FakeTimeProvider(),
5457
)
5558

5659
sessionCoordinator.attemptLoggingSessionEvent(sessionEvent)
@@ -61,4 +64,9 @@ class SessionCoordinatorTest {
6164
assertThat(fakeEventGDTLogger.loggedEvent!!.sessionData.firebaseInstallationId)
6265
.isEqualTo("FaKeFiD")
6366
}
67+
68+
@After
69+
fun cleanUp() {
70+
FirebaseApp.clearInstancesForTest()
71+
}
6472
}

firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionEventEncoderTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.google.firebase.FirebaseApp
2222
import com.google.firebase.sessions.SessionEvents.SESSION_EVENT_ENCODER
2323
import com.google.firebase.sessions.settings.SessionsSettings
2424
import com.google.firebase.sessions.testing.FakeFirebaseApp
25+
import com.google.firebase.sessions.testing.FakeTimeProvider
2526
import com.google.firebase.sessions.testing.TestSessionEventData
2627
import org.junit.After
2728
import org.junit.Test
@@ -43,7 +44,7 @@ class SessionEventEncoderTest {
4344
fakeFirebaseApp.firebaseApp,
4445
TestSessionEventData.TEST_SESSION_DETAILS,
4546
SessionsSettings(fakeFirebaseApp.firebaseApp.applicationContext),
46-
TestSessionEventData.TEST_SESSION_TIMESTAMP_US,
47+
FakeTimeProvider(),
4748
)
4849

4950
val json = SESSION_EVENT_ENCODER.encode(sessionEvent)

firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionEventTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ import com.google.common.truth.Truth.assertThat
2121
import com.google.firebase.FirebaseApp
2222
import com.google.firebase.sessions.settings.SessionsSettings
2323
import com.google.firebase.sessions.testing.FakeFirebaseApp
24+
import com.google.firebase.sessions.testing.FakeTimeProvider
2425
import com.google.firebase.sessions.testing.TestSessionEventData.TEST_DATA_COLLECTION_STATUS
2526
import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_DATA
2627
import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_DETAILS
2728
import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_EVENT
28-
import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_TIMESTAMP_US
2929
import org.junit.After
3030
import org.junit.Test
3131
import org.junit.runner.RunWith
@@ -41,7 +41,7 @@ class SessionEventTest {
4141
fakeFirebaseApp.firebaseApp,
4242
TEST_SESSION_DETAILS,
4343
SessionsSettings(fakeFirebaseApp.firebaseApp.applicationContext),
44-
TEST_SESSION_TIMESTAMP_US,
44+
FakeTimeProvider(),
4545
)
4646

4747
assertThat(sessionEvent).isEqualTo(TEST_SESSION_EVENT)
@@ -58,7 +58,7 @@ class SessionEventTest {
5858
fakeFirebaseApp.firebaseApp,
5959
TEST_SESSION_DETAILS,
6060
SessionsSettings(fakeFirebaseApp.firebaseApp.applicationContext),
61-
TEST_SESSION_TIMESTAMP_US,
61+
FakeTimeProvider(),
6262
)
6363

6464
assertThat(sessionEvent)

0 commit comments

Comments
 (0)