diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 9d29050bd9..ee54213beb 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -45,6 +45,7 @@ import mozilla.components.service.glean.net.ConceptFetchHttpUploader import mozilla.components.support.base.facts.register import mozilla.components.support.base.log.Log import mozilla.components.support.base.log.logger.Logger +import mozilla.components.support.base.observer.Observable import mozilla.components.support.ktx.android.content.isMainProcess import mozilla.components.support.ktx.android.content.runOnlyInMainProcess import mozilla.components.support.locale.LocaleAwareApplication @@ -72,6 +73,7 @@ import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.isCustomEngine import org.mozilla.fenix.ext.isKnownSearchDomain import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks import org.mozilla.fenix.perf.ProfilerMarkerFactProcessor import org.mozilla.fenix.perf.StartupTimeline @@ -393,7 +395,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider { private fun setupMegazord(): Deferred { // Note: Megazord.init() must be called as soon as possible ... Megazord.init() - + // Give the generated FxNimbus a closure to lazily get the Nimbus object + FxNimbus.initialize { components.analytics.experiments } return GlobalScope.async(Dispatchers.IO) { // ... but RustHttpConfig.setClient() and RustLog.enable() can be called later. RustHttpConfig.setClient(lazy { components.core.client }) @@ -401,10 +404,31 @@ open class FenixApplication : LocaleAwareApplication(), Provider { // We want to ensure Nimbus is initialized as early as possible so we can // experiment on features close to startup. // But we need viaduct (the RustHttp client) to be ready before we do. - components.analytics.experiments.initialize() + components.analytics.experiments.apply { + initialize() + setupNimbusObserver(this) + } } } + private fun setupNimbusObserver(nimbus: Observable) { + nimbus.register(object : NimbusInterface.Observer { + override fun onUpdatesApplied(updated: List) { + onNimbusStartupAndUpdate() + } + }) + + onNimbusStartupAndUpdate() + } + + private fun onNimbusStartupAndUpdate() { + val settings = settings() + if (FeatureFlags.messagingFeature && settings.isExperimentationEnabled) { + components.appStore.dispatch(AppAction.MessagingAction.Restore) + } + reportHomeScreenSectionMetrics(settings) + } + override fun onTrimMemory(level: Int) { super.onTrimMemory(level) @@ -738,6 +762,11 @@ open class FenixApplication : LocaleAwareApplication(), Provider { @VisibleForTesting internal fun reportHomeScreenMetrics(settings: Settings) { + reportOpeningScreenMetrics(settings) + reportHomeScreenSectionMetrics(settings) + } + + private fun reportOpeningScreenMetrics(settings: Settings) { CustomizeHome.openingScreen.set( when { settings.alwaysOpenTheHomepageWhenOpeningTheApp -> "homepage" @@ -746,21 +775,18 @@ open class FenixApplication : LocaleAwareApplication(), Provider { else -> "" } ) - components.analytics.experiments.register(object : NimbusInterface.Observer { - override fun onExperimentsFetched() { - if (FeatureFlags.messagingFeature && settings().isExperimentationEnabled) { - components.appStore.dispatch(AppAction.MessagingAction.Restore) - } - } - override fun onUpdatesApplied(updated: List) { - CustomizeHome.jumpBackIn.set(settings.showRecentTabsFeature) - CustomizeHome.recentlySaved.set(settings.showRecentBookmarksFeature) - CustomizeHome.mostVisitedSites.set(settings.showTopSitesFeature) - CustomizeHome.recentlyVisited.set(settings.historyMetadataUIFeature) - CustomizeHome.pocket.set(settings.showPocketRecommendationsFeature) - CustomizeHome.contile.set(settings.showContileFeature) - } - }) + } + + private fun reportHomeScreenSectionMetrics(settings: Settings) { + // These settings are backed by Nimbus features. + // We break them out here so they can be recorded when + // `nimbus.applyPendingExperiments()` is called. + CustomizeHome.jumpBackIn.set(settings.showRecentTabsFeature) + CustomizeHome.recentlySaved.set(settings.showRecentBookmarksFeature) + CustomizeHome.mostVisitedSites.set(settings.showTopSitesFeature) + CustomizeHome.recentlyVisited.set(settings.historyMetadataUIFeature) + CustomizeHome.pocket.set(settings.showPocketRecommendationsFeature) + CustomizeHome.contile.set(settings.showContileFeature) } protected fun recordOnInit() { diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index 011c2cc2a9..06502565dd 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -124,9 +124,7 @@ class Analytics( } val experiments: NimbusApi by lazyMonitored { - createNimbus(context, BuildConfig.NIMBUS_ENDPOINT).also { api -> - FxNimbus.api = api - } + createNimbus(context, BuildConfig.NIMBUS_ENDPOINT) } val messagingStorage by lazyMonitored { diff --git a/app/src/main/java/org/mozilla/fenix/experiments/NimbusSetup.kt b/app/src/main/java/org/mozilla/fenix/experiments/NimbusSetup.kt index a26fafa656..e769bec4fe 100644 --- a/app/src/main/java/org/mozilla/fenix/experiments/NimbusSetup.kt +++ b/app/src/main/java/org/mozilla/fenix/experiments/NimbusSetup.kt @@ -13,11 +13,26 @@ import mozilla.components.service.nimbus.NimbusAppInfo import mozilla.components.service.nimbus.NimbusDisabled import mozilla.components.service.nimbus.NimbusServerSettings import mozilla.components.support.base.log.logger.Logger +import org.mozilla.experiments.nimbus.NimbusInterface +import org.mozilla.experiments.nimbus.internal.EnrolledExperiment 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.nimbus.FxNimbus + +/** + * Fenix specific observer of Nimbus events. + * + * The generated code `FxNimbus` provides a cache which should be invalidated + * when the experiments recipes are updated. + */ +private val observer = object : NimbusInterface.Observer { + override fun onUpdatesApplied(updated: List) { + FxNimbus.invalidateCachedValues() + } +} @Suppress("TooGenericExceptionCaught") fun createNimbus(context: Context, url: String?): NimbusApi { @@ -69,6 +84,10 @@ fun createNimbus(context: Context, url: String?): NimbusApi { ) ) Nimbus(context, appInfo, serverSettings, errorReporter).apply { + // We register our own internal observer for housekeeping the Nimbus SDK and + // generated code. + register(observer) + // This performs the minimal amount of work required to load branch and enrolment data // into memory. If `getExperimentBranch` is called from another thread between here // and the next nimbus disk write (setting `globalUserParticipation` or diff --git a/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt index 760177993d..a66ea9c91a 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt @@ -11,7 +11,6 @@ import io.mockk.mockk import io.mockk.mockkObject import kotlinx.coroutines.test.advanceUntilIdle import mozilla.components.concept.fetch.Client -import mozilla.components.service.nimbus.NimbusDisabled import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.rule.runTestOnMain @@ -30,7 +29,6 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.settings import org.mozilla.fenix.helpers.FenixRobolectricTestRunner -import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.utils.Settings import org.robolectric.Robolectric import java.io.IOException @@ -56,8 +54,6 @@ class SettingsFragmentTest { mockkObject(Config) every { Config.channel } returns ReleaseChannel.Nightly - FxNimbus.api = NimbusDisabled(testContext) - val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().get() activity.supportFragmentManager.beginTransaction() .add(settingsFragment, "test")