You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
5.2 KiB
Kotlin
130 lines
5.2 KiB
Kotlin
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.fenix.experiments
|
|
|
|
import android.content.Context
|
|
import mozilla.components.service.nimbus.NimbusApi
|
|
import mozilla.components.service.nimbus.NimbusAppInfo
|
|
import mozilla.components.service.nimbus.NimbusBuilder
|
|
import mozilla.components.service.nimbus.messaging.FxNimbusMessaging
|
|
import mozilla.components.service.nimbus.messaging.NimbusSystem
|
|
import mozilla.components.support.base.log.logger.Logger
|
|
import org.mozilla.experiments.nimbus.NimbusInterface
|
|
import org.mozilla.experiments.nimbus.internal.NimbusException
|
|
import org.mozilla.fenix.BuildConfig
|
|
import org.mozilla.fenix.R
|
|
import org.mozilla.fenix.ext.components
|
|
import org.mozilla.fenix.ext.settings
|
|
import org.mozilla.fenix.messaging.CustomAttributeProvider
|
|
import org.mozilla.fenix.nimbus.FxNimbus
|
|
import org.mozilla.fenix.utils.Settings
|
|
|
|
/**
|
|
* The maximum amount of time the app launch will be blocked to load experiments from disk.
|
|
*
|
|
* ⚠️ This value was decided from analyzing the Focus metrics (nimbus_initial_fetch) for the ideal
|
|
* timeout. We should NOT change this value without collecting more metrics first.
|
|
*/
|
|
private const val TIME_OUT_LOADING_EXPERIMENT_FROM_DISK_MS = 200L
|
|
|
|
private val logger = Logger("service/Nimbus")
|
|
|
|
/**
|
|
* Create the Nimbus singleton object for the Fenix app.
|
|
*/
|
|
fun createNimbus(context: Context, urlString: String?): NimbusApi {
|
|
// These values can be used in the JEXL expressions when targeting experiments.
|
|
val customTargetingAttributes = CustomAttributeProvider.getCustomTargetingAttributes(context)
|
|
|
|
val isAppFirstRun = context.settings().isFirstNimbusRun
|
|
if (isAppFirstRun) {
|
|
context.settings().isFirstNimbusRun = false
|
|
}
|
|
|
|
// The name "fenix" here corresponds to the app_name defined for the family of apps
|
|
// that encompasses all of the channels for the Fenix app. This is defined upstream in
|
|
// the telemetry system. For more context on where the app_name come from see:
|
|
// https://probeinfo.telemetry.mozilla.org/v2/glean/app-listings
|
|
// and
|
|
// https://github.com/mozilla/probe-scraper/blob/master/repositories.yaml
|
|
val appInfo = NimbusAppInfo(
|
|
appName = "fenix",
|
|
// Note: Using BuildConfig.BUILD_TYPE is important here so that it matches the value
|
|
// passed into Glean. `Config.channel.toString()` turned out to be non-deterministic
|
|
// and would mostly produce the value `Beta` and rarely would produce `beta`.
|
|
channel = BuildConfig.BUILD_TYPE.let { if (it == "debug") "developer" else it },
|
|
customTargetingAttributes = customTargetingAttributes,
|
|
)
|
|
|
|
return NimbusBuilder(context).apply {
|
|
url = urlString
|
|
errorReporter = context::reportError
|
|
initialExperiments = R.raw.initial_experiments
|
|
timeoutLoadingExperiment = TIME_OUT_LOADING_EXPERIMENT_FROM_DISK_MS
|
|
usePreviewCollection = context.settings().nimbusUsePreview
|
|
sharedPreferences = context.settings().preferences
|
|
isFirstRun = isAppFirstRun
|
|
featureManifest = FxNimbus
|
|
onFetchCallback = {
|
|
context.settings().nimbusExperimentsFetched = true
|
|
}
|
|
}.build(appInfo).also { nimbusApi ->
|
|
nimbusApi.recordIsReady(FxNimbus.features.nimbusIsReady.value().eventCount)
|
|
}
|
|
}
|
|
|
|
private fun Context.reportError(message: String, e: Throwable) {
|
|
if (BuildConfig.BUILD_TYPE == "debug") {
|
|
logger.error("Nimbus error: $message", e)
|
|
}
|
|
if (e !is NimbusException || e.isReportableError()) {
|
|
@Suppress("TooGenericExceptionCaught")
|
|
try {
|
|
this.components.analytics.crashReporter.submitCaughtException(e)
|
|
} catch (e: Throwable) {
|
|
logger.error(message, e)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Classifies which errors we should forward to our crash reporter or not. We want to filter out the
|
|
* non-reportable ones if we know there is no reasonable action that we can perform.
|
|
*
|
|
* This fix should be upstreamed as part of: https://github.com/mozilla/application-services/issues/4333
|
|
*/
|
|
fun NimbusException.isReportableError(): Boolean {
|
|
return when (this) {
|
|
is NimbusException.ClientException -> false
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call `fetchExperiments` if the time since the last fetch is over a threshold.
|
|
*
|
|
* The threshold is given by the [NimbusSystem] feature object, defined in the
|
|
* `nimbus.fml.yaml` file.
|
|
*/
|
|
fun NimbusInterface.maybeFetchExperiments(
|
|
context: Context,
|
|
feature: NimbusSystem = FxNimbusMessaging.features.nimbusSystem.value(),
|
|
currentTimeMillis: Long = System.currentTimeMillis(),
|
|
) {
|
|
if (context.settings().nimbusUsePreview) {
|
|
context.settings().nimbusLastFetchTime = 0L
|
|
fetchExperiments()
|
|
} else {
|
|
val minimumPeriodMinutes = feature.refreshIntervalForeground
|
|
val lastFetchTimeMillis = context.settings().nimbusLastFetchTime
|
|
val minimumPeriodMillis = minimumPeriodMinutes * Settings.ONE_MINUTE_MS
|
|
|
|
if (currentTimeMillis - lastFetchTimeMillis >= minimumPeriodMillis) {
|
|
context.settings().nimbusLastFetchTime = currentTimeMillis
|
|
fetchExperiments()
|
|
}
|
|
}
|
|
}
|