17
17
package com.google.firebase.sessions.settings
18
18
19
19
import android.content.Context
20
- import android.net.Uri
20
+ import android.util.Log
21
21
import androidx.datastore.preferences.preferencesDataStore
22
- import java.net.URL
22
+ import com.google.firebase.installations.FirebaseInstallationsApi
23
+ import com.google.firebase.sessions.ApplicationInfo
24
+ import java.util.concurrent.atomic.AtomicBoolean
25
+ import kotlin.coroutines.CoroutineContext
23
26
import kotlin.time.Duration
24
27
import kotlin.time.Duration.Companion.seconds
28
+ import kotlinx.coroutines.CoroutineScope
29
+ import kotlinx.coroutines.Dispatchers
30
+ import kotlinx.coroutines.launch
31
+ import kotlinx.coroutines.runBlocking
32
+ import kotlinx.coroutines.tasks.await
33
+ import org.json.JSONException
34
+ import org.json.JSONObject
25
35
26
- internal class RemoteSettings (val context : Context ) : SettingsProvider {
27
- private val Context .dataStore by preferencesDataStore(name = SESSION_CONFIGS_NAME )
36
+ internal class RemoteSettings (
37
+ val context : Context ,
38
+ val blockingDispatcher : CoroutineContext ,
39
+ val backgroundDispatcher : CoroutineContext ,
40
+ val firebaseInstallationsApi : FirebaseInstallationsApi ,
41
+ val appInfo : ApplicationInfo ,
42
+ private val configsFetcher : CrashlyticsSettingsFetcher = RemoteSettingsFetcher (appInfo),
43
+ dataStoreName : String = SESSION_CONFIGS_NAME
44
+ ) : SettingsProvider {
45
+ private val Context .dataStore by preferencesDataStore(name = dataStoreName)
28
46
private val settingsCache = SettingsCache (context.dataStore)
47
+ private var fetchInProgress = AtomicBoolean (false )
29
48
30
49
override val sessionEnabled: Boolean?
31
50
get() {
@@ -47,45 +66,105 @@ internal class RemoteSettings(val context: Context) : SettingsProvider {
47
66
}
48
67
49
68
override fun updateSettings () {
50
- fetchConfigs()
69
+ // TODO: Move to blocking coroutine dispatcher.
70
+ runBlocking(Dispatchers .Default ) { launch { fetchConfigs() } }
51
71
}
52
72
53
73
override fun isSettingsStale (): Boolean {
54
74
return settingsCache.hasCacheExpired()
55
75
}
56
76
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())
77
+ internal fun clearCachedSettings () {
78
+ val scope = CoroutineScope (backgroundDispatcher)
79
+ scope.launch { settingsCache.removeConfigs() }
80
+ }
81
+
82
+ suspend private fun fetchConfigs () {
83
+ // Check if a fetch is in progress. If yes, return
84
+ if (fetchInProgress.get()) {
85
+ return
78
86
}
79
87
80
- fun fetchConfigs () {
81
- // Check if a fetch is in progress. If yes, return
82
- if (fetchInProgress) {
83
- return
84
- }
88
+ // Check if cache is expired. If not, return
89
+ if (! settingsCache.hasCacheExpired()) {
90
+ return
91
+ }
85
92
86
- // Check if cache is expired. If not, return
87
- // Initiate a fetch. On successful response cache the fetched values
93
+ fetchInProgress.set(true )
88
94
95
+ // Get the installations ID before making a remote config fetch
96
+ var installationId = firebaseInstallationsApi.id.await()
97
+ if (installationId == null ) {
98
+ fetchInProgress.set(false )
99
+ return
89
100
}
101
+
102
+ // All the required fields are available, start making a network request.
103
+ val options =
104
+ mapOf (
105
+ " X-Crashlytics-Installation-ID" to installationId as String ,
106
+ " X-Crashlytics-Device-Model" to appInfo.deviceModel,
107
+ // TODO(visum) Add OS version parameters
108
+ // "X-Crashlytics-OS-Build-Version" to "",
109
+ // "X-Crashlytics-OS-Display-Version" to "",
110
+ " X-Crashlytics-API-Client-Version" to appInfo.sessionSdkVersion
111
+ )
112
+
113
+ configsFetcher.doConfigFetch(
114
+ headerOptions = options,
115
+ onSuccess = {
116
+ var sessionsEnabled: Boolean? = null
117
+ var sessionSamplingRate: Double? = null
118
+ var sessionTimeoutSeconds: Int? = null
119
+ var cacheDuration: Int? = null
120
+ if (it.has(" app_quality" )) {
121
+ val aqsSettings = it.get(" app_quality" ) as JSONObject
122
+ try {
123
+ if (aqsSettings.has(" sessions_enabled" )) {
124
+ sessionsEnabled = aqsSettings.get(" sessions_enabled" ) as Boolean?
125
+ }
126
+
127
+ if (aqsSettings.has(" sampling_rate" )) {
128
+ sessionSamplingRate = aqsSettings.get(" sampling_rate" ) as Double?
129
+ }
130
+
131
+ if (aqsSettings.has(" session_timeout_seconds" )) {
132
+ sessionTimeoutSeconds = aqsSettings.get(" session_timeout_seconds" ) as Int?
133
+ }
134
+
135
+ if (aqsSettings.has(" cache_duration" )) {
136
+ cacheDuration = aqsSettings.get(" cache_duration" ) as Int?
137
+ }
138
+ } catch (exception: JSONException ) {
139
+ Log .e(TAG , " Error parsing the configs remotely fetched: " , exception)
140
+ }
141
+ }
142
+
143
+ val scope = CoroutineScope (backgroundDispatcher)
144
+ sessionsEnabled?.let { settingsCache.updateSettingsEnabled(sessionsEnabled) }
145
+
146
+ sessionTimeoutSeconds?.let {
147
+ settingsCache.updateSessionRestartTimeout(sessionTimeoutSeconds)
148
+ }
149
+
150
+ sessionSamplingRate?.let { settingsCache.updateSamplingRate(sessionSamplingRate) }
151
+
152
+ cacheDuration?.let { settingsCache.updateSessionCacheDuration(cacheDuration) }
153
+ ? : let { settingsCache.updateSessionCacheDuration(86400 ) }
154
+
155
+ settingsCache.updateSessionCacheUpdatedTime(System .currentTimeMillis())
156
+ fetchInProgress.set(false )
157
+ },
158
+ onFailure = {
159
+ // Network request failed here.
160
+ Log .e(TAG , " Error failing to fetch the remote configs" )
161
+ fetchInProgress.set(false )
162
+ }
163
+ )
164
+ }
165
+
166
+ companion object {
167
+ private const val SESSION_CONFIGS_NAME = " firebase_session_settings"
168
+ private const val TAG = " SessionConfigFetcher"
90
169
}
91
170
}
0 commit comments