Skip to content

Commit 39f16c6

Browse files
authored
Merge 43b0342 into 33f6660
2 parents 33f6660 + 43b0342 commit 39f16c6

File tree

6 files changed

+449
-20
lines changed

6 files changed

+449
-20
lines changed

firebase-sessions/firebase-sessions.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ firebaseLibrary {
2626

2727
android {
2828
val targetSdkVersion: Int by rootProject
29-
compileSdk = targetSdkVersion
29+
compileSdk = 33
3030
defaultConfig {
3131
minSdk = 16
3232
targetSdk = targetSdkVersion
@@ -48,6 +48,7 @@ dependencies {
4848
implementation("com.google.firebase:firebase-encoders:17.0.0")
4949
implementation("com.google.firebase:firebase-installations-interop:17.1.0")
5050
implementation("com.google.android.datatransport:transport-api:3.0.0")
51+
implementation ("androidx.datastore:datastore-preferences:1.0.0")
5152
implementation(libs.androidx.annotation)
5253

5354
runtimeOnly("com.google.firebase:firebase-installations:17.1.3")

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/LocalOverrideSettings.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,32 @@ internal class LocalOverrideSettings(val context: Context) : SettingsProvider {
3535

3636
override val sessionEnabled: Boolean?
3737
get() {
38-
if (metadata != null && metadata.containsKey(sessions_metadata_flag_sessionsEnabled)) {
39-
return metadata.getBoolean(sessions_metadata_flag_sessionsEnabled)
38+
metadata?.let {
39+
if (it.containsKey(sessions_metadata_flag_sessionsEnabled)) {
40+
return it.getBoolean(sessions_metadata_flag_sessionsEnabled)
41+
}
4042
}
4143
return null
4244
}
4345

4446
override val sessionRestartTimeout: Duration?
4547
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
48+
metadata?.let {
49+
if (it.containsKey(sessions_metadata_flag_sessionRestartTimeout)) {
50+
val timeoutInSeconds = it.getInt(sessions_metadata_flag_sessionRestartTimeout)
51+
val duration = timeoutInSeconds.toDuration(DurationUnit.SECONDS)
52+
return duration
53+
}
5054
}
5155
return null
5256
}
5357

5458
override val samplingRate: Double?
5559
get() {
56-
if (metadata != null && metadata.containsKey(sessions_metadata_flag_samplingRate)) {
57-
return metadata.getDouble(sessions_metadata_flag_samplingRate)
60+
metadata?.let {
61+
if (it.containsKey(sessions_metadata_flag_samplingRate)) {
62+
return it.getDouble(sessions_metadata_flag_samplingRate)
63+
}
5864
}
5965
return null
6066
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.net.Uri
21+
import androidx.datastore.preferences.preferencesDataStore
22+
import java.net.URL
23+
import kotlin.time.Duration
24+
import kotlin.time.Duration.Companion.seconds
25+
26+
internal class RemoteSettings(val context: Context) : SettingsProvider {
27+
private val Context.dataStore by preferencesDataStore(name = SESSION_CONFIGS_NAME)
28+
private val settingsCache = SettingsCache(context.dataStore)
29+
30+
override val sessionEnabled: Boolean?
31+
get() {
32+
return settingsCache.sessionsEnabled()
33+
}
34+
35+
override val sessionRestartTimeout: Duration?
36+
get() {
37+
val durationInSeconds = settingsCache.sessionRestartTimeout()
38+
if (durationInSeconds != null) {
39+
return durationInSeconds.toLong().seconds
40+
}
41+
return null
42+
}
43+
44+
override val samplingRate: Double?
45+
get() {
46+
return settingsCache.sessionSamplingRate()
47+
}
48+
49+
override fun updateSettings() {
50+
fetchConfigs()
51+
}
52+
53+
override fun isSettingsStale(): Boolean {
54+
return settingsCache.hasCacheExpired()
55+
}
56+
57+
companion object SettingsFetcher {
58+
private const val SESSION_CONFIGS_NAME = "firebase_session_settings"
59+
private const val FIREBASE_SESSIONS_BASE_URL_STRING =
60+
"https://firebase-settings.crashlytics.com"
61+
private const val FIREBASE_PLATFORM = "android"
62+
private const val fetchInProgress = false
63+
private val settingsUrl: URL = run {
64+
var uri =
65+
Uri.Builder()
66+
.scheme("https")
67+
.authority(FIREBASE_SESSIONS_BASE_URL_STRING)
68+
.appendPath("spi/v2/platforms")
69+
.appendPath(FIREBASE_PLATFORM)
70+
.appendPath("gmp")
71+
// TODO(visum) Replace below with the GMP APPId
72+
.appendPath("GMP_APP_ID")
73+
.appendPath("settings")
74+
.appendQueryParameter("build_version", "")
75+
.appendQueryParameter("display_version", "")
76+
77+
URL(uri.build().toString())
78+
}
79+
80+
fun fetchConfigs() {
81+
// Check if a fetch is in progress. If yes, return
82+
if (fetchInProgress) {
83+
return
84+
}
85+
86+
// Check if cache is expired. If not, return
87+
// Initiate a fetch. On successful response cache the fetched values
88+
89+
}
90+
}
91+
}

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

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import kotlin.time.Duration.Companion.minutes
2727
*/
2828
internal class SessionsSettings(val context: Context) {
2929

30-
var localOverrideSettings = LocalOverrideSettings(context)
30+
private var localOverrideSettings = LocalOverrideSettings(context)
31+
private var remoteSettings = RemoteSettings(context)
3132

3233
// Order of preference for all the configs below:
3334
// 1. Honor local overrides
@@ -37,40 +38,46 @@ internal class SessionsSettings(val context: Context) {
3738
// Setting to qualify if sessions service is enabled.
3839
val sessionsEnabled: Boolean
3940
get() {
40-
if (localOverrideSettings.sessionEnabled != null) {
41-
return localOverrideSettings.sessionEnabled!!
41+
localOverrideSettings.sessionEnabled?.let {
42+
return it
43+
}
44+
remoteSettings.sessionEnabled?.let {
45+
return it
4246
}
43-
4447
// SDK Default
4548
return true
4649
}
4750

4851
// Setting that provides the sessions sampling rate.
4952
val samplingRate: Double
5053
get() {
51-
if (localOverrideSettings.samplingRate != null) {
52-
return localOverrideSettings.samplingRate!!
54+
localOverrideSettings.samplingRate?.let {
55+
return it
56+
}
57+
remoteSettings.samplingRate?.let {
58+
return it
5359
}
54-
5560
// SDK Default
5661
return 1.0
5762
}
5863

5964
// Background timeout config value before which a new session is generated
6065
val sessionRestartTimeout: Duration
6166
get() {
62-
if (localOverrideSettings.sessionRestartTimeout != null) {
63-
return localOverrideSettings.sessionRestartTimeout!!
67+
localOverrideSettings.sessionRestartTimeout?.let {
68+
return it
69+
}
70+
remoteSettings.sessionRestartTimeout?.let {
71+
return it
6472
}
65-
6673
// SDK Default
6774
return 30.minutes
6875
}
6976

7077
// Update the settings for all the settings providers
7178
fun updateSettings() {
7279
// Placeholder to initiate settings update on different sources
73-
// Pending sources: RemoteSettings
7480
localOverrideSettings.updateSettings()
81+
remoteSettings.updateSettings()
7582
}
7683
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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 androidx.datastore.core.DataStore
20+
import androidx.datastore.preferences.core.Preferences
21+
import androidx.datastore.preferences.core.booleanPreferencesKey
22+
import androidx.datastore.preferences.core.doublePreferencesKey
23+
import androidx.datastore.preferences.core.edit
24+
import androidx.datastore.preferences.core.intPreferencesKey
25+
import androidx.datastore.preferences.core.longPreferencesKey
26+
import kotlinx.coroutines.flow.first
27+
28+
internal data class SessionConfigs(
29+
val sessionEnabled: Boolean? = null,
30+
val sessionSamplingRate: Double? = null,
31+
val cacheDuration: Int? = null,
32+
val sessionRestartTimeout: Int? = null,
33+
val cacheUpdatedTime: Long? = null
34+
)
35+
36+
internal class SettingsCache(private val store: DataStore<Preferences>) {
37+
private var sessionConfigs = SessionConfigs()
38+
39+
private object SettingsCacheKeys {
40+
val SETTINGS_CACHE_SESSIONS_ENABLED = booleanPreferencesKey("firebase_sessions_enabled")
41+
val SETTINGS_CACHE_SAMPLING_RATE = doublePreferencesKey("firebase_sessions_sampling_rate")
42+
val SETTINGS_CACHE_SESSIONS_RESTART_TIMEOUT_SECONDS =
43+
intPreferencesKey("firebase_sessions_restart_timeout")
44+
val SETTINGS_CACHE_SESSIONS_CACHE_DURATION_SECONDS =
45+
intPreferencesKey("firebase_sessions_cache_duration")
46+
val SETTINGS_CACHE_SESSIONS_CACHE_UPDATED_TIME =
47+
longPreferencesKey("firebase_sessions_cache_updated_time")
48+
}
49+
50+
private suspend fun updateSessionConfigs() = mapSessionConfigs(store.data.first().toPreferences())
51+
52+
private fun mapSessionConfigs(settings: Preferences): SessionConfigs {
53+
val sessionEnabled = settings[SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_ENABLED]
54+
val sessionSamplingRate = settings[SettingsCacheKeys.SETTINGS_CACHE_SAMPLING_RATE]
55+
val sessionRestartTimeout =
56+
settings[SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_RESTART_TIMEOUT_SECONDS]
57+
val cacheDuration = settings[SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_CACHE_DURATION_SECONDS]
58+
val cacheUpdatedTime = settings[SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_CACHE_UPDATED_TIME]
59+
60+
sessionConfigs =
61+
SessionConfigs(
62+
sessionEnabled = sessionEnabled,
63+
sessionSamplingRate = sessionSamplingRate,
64+
sessionRestartTimeout = sessionRestartTimeout,
65+
cacheDuration = cacheDuration,
66+
cacheUpdatedTime = cacheUpdatedTime
67+
)
68+
return sessionConfigs
69+
}
70+
71+
internal fun hasCacheExpired(): Boolean {
72+
if (sessionConfigs.cacheUpdatedTime != null) {
73+
val currentTimestamp = System.currentTimeMillis()
74+
val timeDifferenceSeconds = (currentTimestamp - sessionConfigs.cacheUpdatedTime!!) / 1000
75+
if (timeDifferenceSeconds < sessionConfigs.cacheDuration!!) return false
76+
}
77+
return true
78+
}
79+
80+
fun sessionsEnabled(): Boolean? = sessionConfigs.sessionEnabled
81+
82+
fun sessionSamplingRate(): Double? = sessionConfigs.sessionSamplingRate
83+
84+
fun sessionRestartTimeout(): Int? = sessionConfigs.sessionRestartTimeout
85+
86+
suspend fun updateSettingsEnabled(enabled: Boolean?) {
87+
store.edit { preferences ->
88+
enabled?.run { preferences[SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_ENABLED] = enabled }
89+
?: run { preferences.remove(SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_ENABLED) }
90+
}
91+
updateSessionConfigs()
92+
}
93+
94+
suspend fun updateSamplingRate(rate: Double?) {
95+
store.edit { preferences ->
96+
rate?.run { preferences[SettingsCacheKeys.SETTINGS_CACHE_SAMPLING_RATE] = rate }
97+
?: run { preferences.remove(SettingsCacheKeys.SETTINGS_CACHE_SAMPLING_RATE) }
98+
}
99+
updateSessionConfigs()
100+
}
101+
102+
suspend fun updateSessionRestartTimeout(timeoutInSeconds: Int?) {
103+
store.edit { preferences ->
104+
timeoutInSeconds?.run {
105+
preferences[SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_RESTART_TIMEOUT_SECONDS] =
106+
timeoutInSeconds
107+
}
108+
?: run {
109+
preferences.remove(SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_RESTART_TIMEOUT_SECONDS)
110+
}
111+
}
112+
updateSessionConfigs()
113+
}
114+
115+
suspend fun updateSessionCacheDuration(cacheDurationInSeconds: Int?) {
116+
store.edit { preferences ->
117+
cacheDurationInSeconds?.run {
118+
preferences[SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_CACHE_DURATION_SECONDS] =
119+
cacheDurationInSeconds
120+
}
121+
?: run {
122+
preferences.remove(SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_CACHE_DURATION_SECONDS)
123+
}
124+
}
125+
updateSessionConfigs()
126+
}
127+
128+
suspend fun updateSessionCacheUpdatedTime(cacheUpdatedTime: Long?) {
129+
store.edit { preferences ->
130+
cacheUpdatedTime?.run {
131+
preferences[SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_CACHE_UPDATED_TIME] = cacheUpdatedTime
132+
}
133+
?: run { preferences.remove(SettingsCacheKeys.SETTINGS_CACHE_SESSIONS_CACHE_UPDATED_TIME) }
134+
}
135+
updateSessionConfigs()
136+
}
137+
138+
suspend fun removeConfigs() {
139+
store.edit { preferences ->
140+
preferences.clear()
141+
updateSessionConfigs()
142+
}
143+
}
144+
}

0 commit comments

Comments
 (0)