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