Skip to content

Commit 68ad541

Browse files
authored
Merge d3faacc into f7fff6b
2 parents f7fff6b + d3faacc commit 68ad541

File tree

9 files changed

+298
-13
lines changed

9 files changed

+298
-13
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.google.firebase.inject.Provider
2525
import com.google.firebase.installations.FirebaseInstallationsApi
2626
import com.google.firebase.ktx.Firebase
2727
import com.google.firebase.ktx.app
28+
import com.google.firebase.sessions.settings.SessionsSettings
2829
import kotlinx.coroutines.CoroutineDispatcher
2930

3031
class FirebaseSessions
@@ -38,9 +39,11 @@ internal constructor(
3839
private val eventGDTLogger = EventGDTLogger(transportFactoryProvider)
3940
private val sessionCoordinator =
4041
SessionCoordinator(firebaseInstallations, backgroundDispatcher, eventGDTLogger)
42+
private val sessionSettings = SessionsSettings(firebaseApp.applicationContext)
4143

4244
init {
43-
val sessionInitiator = SessionInitiator(WallClock::elapsedRealtime, this::initiateSessionStart)
45+
val sessionInitiator =
46+
SessionInitiator(WallClock::elapsedRealtime, this::initiateSessionStart, sessionSettings)
4447
val appContext = firebaseApp.applicationContext.applicationContext
4548
if (appContext is Application) {
4649
appContext.registerActivityLifecycleCallbacks(sessionInitiator.activityLifecycleCallbacks)

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.google.firebase.sessions
1919
import android.app.Activity
2020
import android.app.Application.ActivityLifecycleCallbacks
2121
import android.os.Bundle
22+
import com.google.firebase.sessions.settings.SessionsSettings
2223
import kotlin.time.Duration
2324

2425
/**
@@ -30,7 +31,8 @@ import kotlin.time.Duration
3031
*/
3132
internal class SessionInitiator(
3233
private val elapsedRealtime: () -> Duration,
33-
private val initiateSessionStart: () -> Unit
34+
private val initiateSessionStart: () -> Unit,
35+
private val sessionsSettings: SessionsSettings
3436
) {
3537
private var backgroundTime = elapsedRealtime()
3638

@@ -44,7 +46,7 @@ internal class SessionInitiator(
4446

4547
fun appForegrounded() {
4648
val interval = elapsedRealtime() - backgroundTime
47-
val sessionTimeout = SessionsSettings().sessionRestartTimeout
49+
val sessionTimeout = sessionsSettings.sessionRestartTimeout
4850
if (interval > sessionTimeout) {
4951
initiateSessionStart()
5052
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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.settings
18+
19+
import android.content.Context
20+
import android.content.pm.PackageManager
21+
import kotlin.time.Duration
22+
import kotlin.time.DurationUnit
23+
import kotlin.time.toDuration
24+
25+
internal class LocalOverrideSettings(val context: Context) : SettingsProvider {
26+
27+
private val sessions_metadata_flag_sessionsEnabled = "firebase_sessions_enabled"
28+
private val sessions_metadata_flag_sessionRestartTimeout =
29+
"firebase_sessions_sessions_restart_timeout"
30+
private val sessions_metadata_flag_samplingRate = "firebase_sessions_sampling_rate"
31+
private val metadata =
32+
context.packageManager
33+
.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
34+
.metaData
35+
36+
override val sessionEnabled: Boolean?
37+
get() {
38+
if (metadata != null && metadata.containsKey(sessions_metadata_flag_sessionsEnabled)) {
39+
return metadata.getBoolean(sessions_metadata_flag_sessionsEnabled)
40+
}
41+
return null
42+
}
43+
44+
override val sessionRestartTimeout: Duration?
45+
get() {
46+
if (metadata != null && metadata.containsKey(sessions_metadata_flag_sessionRestartTimeout)) {
47+
val timeoutInSeconds = metadata.getInt(sessions_metadata_flag_sessionRestartTimeout)
48+
val duration = timeoutInSeconds!!.toDuration(DurationUnit.SECONDS)
49+
return duration
50+
}
51+
return null
52+
}
53+
54+
override val samplingRate: Double?
55+
get() {
56+
if (metadata != null && metadata.containsKey(sessions_metadata_flag_samplingRate)) {
57+
return metadata.getDouble(sessions_metadata_flag_samplingRate)
58+
}
59+
return null
60+
}
61+
62+
override fun updateSettings() {
63+
// Nothing to be done here since there is nothing to be updated.
64+
}
65+
66+
override fun isSettingsStale(): Boolean {
67+
// Settings are never stale since all of these are from Manifest file.
68+
return false
69+
}
70+
}

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionsSettings.kt renamed to firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SessionsSettings.kt

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.google.firebase.sessions
17+
package com.google.firebase.sessions.settings
1818

19+
import android.content.Context
1920
import kotlin.time.Duration
2021
import kotlin.time.Duration.Companion.minutes
2122

@@ -24,26 +25,52 @@ import kotlin.time.Duration.Companion.minutes
2425
*
2526
* @hide
2627
*/
27-
internal class SessionsSettings {
28+
internal class SessionsSettings(val context: Context) {
29+
30+
var localOverrideSettings = LocalOverrideSettings(context)
31+
32+
// Order of preference for all the configs below:
33+
// 1. Honor local overrides
34+
// 2. If no local overrides, use remote config
35+
// 3. If no remote config, fall back to SDK defaults.
36+
2837
// Setting to qualify if sessions service is enabled.
2938
val sessionsEnabled: Boolean
3039
get() {
40+
if (localOverrideSettings.sessionEnabled != null) {
41+
return localOverrideSettings.sessionEnabled!!
42+
}
43+
44+
// SDK Default
3145
return true
3246
}
3347

3448
// Setting that provides the sessions sampling rate.
3549
val samplingRate: Double
3650
get() {
51+
if (localOverrideSettings.samplingRate != null) {
52+
return localOverrideSettings.samplingRate!!
53+
}
54+
55+
// SDK Default
3756
return 1.0
3857
}
3958

4059
// Background timeout config value before which a new session is generated
4160
val sessionRestartTimeout: Duration
42-
get() = 30.minutes
61+
get() {
62+
if (localOverrideSettings.sessionRestartTimeout != null) {
63+
return localOverrideSettings.sessionRestartTimeout!!
64+
}
65+
66+
// SDK Default
67+
return 30.minutes
68+
}
4369

4470
// Update the settings for all the settings providers
4571
fun updateSettings() {
4672
// Placeholder to initiate settings update on different sources
47-
// Expected sources: RemoteSettings, ManifestOverrides, SDK Defaults
73+
// Pending sources: RemoteSettings
74+
localOverrideSettings.updateSettings()
4875
}
4976
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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.settings
18+
19+
import kotlin.time.Duration
20+
21+
interface SettingsProvider {
22+
// Setting to control if session collection is enabled
23+
val sessionEnabled: Boolean?
24+
25+
// Setting to represent when to restart a new session after app backgrounding.
26+
val sessionRestartTimeout: Duration?
27+
28+
// Setting denoting the percentage of the sessions data that should be collected
29+
val samplingRate: Double?
30+
31+
// Function to initiate refresh of the settings for the provider
32+
fun updateSettings()
33+
34+
// Function representing if the settings are stale.
35+
fun isSettingsStale(): Boolean
36+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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.Bundle
20+
import com.google.common.truth.Truth.assertThat
21+
import com.google.firebase.FirebaseApp
22+
import com.google.firebase.sessions.settings.LocalOverrideSettings
23+
import com.google.firebase.sessions.testing.FakeFirebaseApp
24+
import kotlin.time.Duration.Companion.minutes
25+
import org.junit.After
26+
import org.junit.Test
27+
import org.junit.runner.RunWith
28+
import org.robolectric.RobolectricTestRunner
29+
30+
@RunWith(RobolectricTestRunner::class)
31+
class LocalOverrideSettingsTest {
32+
33+
@Test
34+
fun localOverrides_returnsNullByDefault() {
35+
val context = FakeFirebaseApp.fakeFirebaseApp().applicationContext
36+
37+
val localSettings = LocalOverrideSettings(context)
38+
assertThat(localSettings.sessionEnabled).isNull()
39+
assertThat(localSettings.sessionRestartTimeout).isNull()
40+
assertThat(localSettings.samplingRate).isNull()
41+
}
42+
43+
@Test
44+
fun localOverrides_validateIfOverrideValuesAreFetchedCorrectly() {
45+
val metadata = Bundle()
46+
metadata.putBoolean("firebase_sessions_enabled", false)
47+
metadata.putDouble("firebase_sessions_sampling_rate", 0.5)
48+
metadata.putInt("firebase_sessions_sessions_restart_timeout", 180)
49+
val context = FakeFirebaseApp.fakeFirebaseApp(metadata).applicationContext
50+
51+
val localSettings = LocalOverrideSettings(context)
52+
assertThat(localSettings.sessionEnabled).isFalse()
53+
assertThat(localSettings.sessionRestartTimeout).isEqualTo(3.minutes)
54+
assertThat(localSettings.samplingRate).isEqualTo(0.5)
55+
}
56+
57+
@Test
58+
fun localOverridesForSomeFields_validateIfOverrideValuesAreFetchedCorrectly() {
59+
val metadata = Bundle()
60+
metadata.putBoolean("firebase_sessions_enabled", false)
61+
metadata.putInt("firebase_sessions_sessions_restart_timeout", 180)
62+
val context = FakeFirebaseApp.fakeFirebaseApp(metadata).applicationContext
63+
64+
val localSettings = LocalOverrideSettings(context)
65+
assertThat(localSettings.sessionEnabled).isFalse()
66+
assertThat(localSettings.sessionRestartTimeout).isEqualTo(3.minutes)
67+
assertThat(localSettings.samplingRate).isNull()
68+
}
69+
70+
@After
71+
fun cleanUp() {
72+
FirebaseApp.clearInstancesForTest()
73+
}
74+
}

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@
1717
package com.google.firebase.sessions
1818

1919
import com.google.common.truth.Truth.assertThat
20+
import com.google.firebase.FirebaseApp
21+
import com.google.firebase.sessions.settings.SessionsSettings
22+
import com.google.firebase.sessions.testing.FakeFirebaseApp
2023
import kotlin.time.Duration
2124
import kotlin.time.Duration.Companion.minutes
25+
import org.junit.After
2226
import org.junit.Test
27+
import org.junit.runner.RunWith
28+
import org.robolectric.RobolectricTestRunner
2329

30+
@RunWith(RobolectricTestRunner::class)
2431
class SessionInitiatorTest {
2532
class FakeClock {
2633
var elapsed = Duration.ZERO
@@ -43,9 +50,11 @@ class SessionInitiatorTest {
4350
@Test
4451
fun coldStart_initiatesSession() {
4552
val sessionStartCounter = SessionStartCounter()
53+
val context = FakeFirebaseApp.fakeFirebaseApp().applicationContext
54+
val settings = SessionsSettings(context)
4655

4756
// Simulate a cold start by simply constructing the SessionInitiator object
48-
SessionInitiator(Duration::ZERO, sessionStartCounter::initiateSessionStart)
57+
SessionInitiator(Duration::ZERO, sessionStartCounter::initiateSessionStart, settings)
4958

5059
assertThat(sessionStartCounter.count).isEqualTo(1)
5160
}
@@ -54,9 +63,11 @@ class SessionInitiatorTest {
5463
fun appForegrounded_largeInterval_initiatesSession() {
5564
val fakeClock = FakeClock()
5665
val sessionStartCounter = SessionStartCounter()
66+
val context = FakeFirebaseApp.fakeFirebaseApp().applicationContext
67+
val settings = SessionsSettings(context)
5768

5869
val sessionInitiator =
59-
SessionInitiator(fakeClock::elapsed, sessionStartCounter::initiateSessionStart)
70+
SessionInitiator(fakeClock::elapsed, sessionStartCounter::initiateSessionStart, settings)
6071

6172
// First session on cold start
6273
assertThat(sessionStartCounter.count).isEqualTo(1)
@@ -73,9 +84,11 @@ class SessionInitiatorTest {
7384
fun appForegrounded_smallInterval_doesNotInitiatesSession() {
7485
val fakeClock = FakeClock()
7586
val sessionStartCounter = SessionStartCounter()
87+
val context = FakeFirebaseApp.fakeFirebaseApp().applicationContext
88+
val settings = SessionsSettings(context)
7689

7790
val sessionInitiator =
78-
SessionInitiator(fakeClock::elapsed, sessionStartCounter::initiateSessionStart)
91+
SessionInitiator(fakeClock::elapsed, sessionStartCounter::initiateSessionStart, settings)
7992

8093
// First session on cold start
8194
assertThat(sessionStartCounter.count).isEqualTo(1)
@@ -92,9 +105,11 @@ class SessionInitiatorTest {
92105
fun appForegrounded_background_foreground_largeIntervals_initiatesSessions() {
93106
val fakeClock = FakeClock()
94107
val sessionStartCounter = SessionStartCounter()
108+
val context = FakeFirebaseApp.fakeFirebaseApp().applicationContext
109+
val settings = SessionsSettings(context)
95110

96111
val sessionInitiator =
97-
SessionInitiator(fakeClock::elapsed, sessionStartCounter::initiateSessionStart)
112+
SessionInitiator(fakeClock::elapsed, sessionStartCounter::initiateSessionStart, settings)
98113

99114
assertThat(sessionStartCounter.count).isEqualTo(1)
100115

@@ -114,9 +129,11 @@ class SessionInitiatorTest {
114129
fun appForegrounded_background_foreground_smallIntervals_doesNotInitiateNewSessions() {
115130
val fakeClock = FakeClock()
116131
val sessionStartCounter = SessionStartCounter()
132+
val context = FakeFirebaseApp.fakeFirebaseApp().applicationContext
133+
val settings = SessionsSettings(context)
117134

118135
val sessionInitiator =
119-
SessionInitiator(fakeClock::elapsed, sessionStartCounter::initiateSessionStart)
136+
SessionInitiator(fakeClock::elapsed, sessionStartCounter::initiateSessionStart, settings)
120137

121138
// First session on cold start
122139
assertThat(sessionStartCounter.count).isEqualTo(1)
@@ -135,6 +152,11 @@ class SessionInitiatorTest {
135152
assertThat(sessionStartCounter.count).isEqualTo(1)
136153
}
137154

155+
@After
156+
fun cleanUp() {
157+
FirebaseApp.clearInstancesForTest()
158+
}
159+
138160
companion object {
139161
private val SMALL_INTERVAL = 29.minutes // not enough time to initiate a new session
140162
private val LARGE_INTERVAL = 31.minutes // enough to initiate another session

0 commit comments

Comments
 (0)