From 64b636d2a9b575690268870a7e3562e636951f20 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Mon, 29 Jan 2024 13:03:54 +0000 Subject: [PATCH] =?UTF-8?q?Bug=201880476=20=E2=80=94=20Move=20nimbus=20fro?= =?UTF-8?q?m=20components.analytics=20to=20components.nimbus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mozilla/fenix/helpers/Experimentation.kt | 5 +- .../fenix/ui/NimbusMessagingMessageTest.kt | 9 +- .../fenix/ui/NimbusMessagingTriggerTest.kt | 2 +- .../org/mozilla/fenix/FenixApplication.kt | 4 +- .../java/org/mozilla/fenix/HomeActivity.kt | 6 +- .../org/mozilla/fenix/components/Analytics.kt | 20 ----- .../fenix/components/BackgroundServices.kt | 2 +- .../mozilla/fenix/components/Components.kt | 3 +- .../fenix/components/NimbusComponents.kt | 88 +++++++++++++++++++ .../org/mozilla/fenix/home/HomeFragment.kt | 2 +- .../messaging/MessageNotificationWorker.kt | 6 +- .../fenix/nimbus/NimbusBranchesFragment.kt | 4 +- .../fenix/nimbus/NimbusExperimentsFragment.kt | 2 +- .../fenix/onboarding/OnboardingFragment.kt | 2 +- .../fenix/settings/DataChoicesFragment.kt | 2 +- .../fenix/settings/studies/StudiesFragment.kt | 2 +- .../components/BackgroundServicesTest.kt | 5 +- 17 files changed, 114 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/Experimentation.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/Experimentation.kt index 34f4d15e64..4c320a8687 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/helpers/Experimentation.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/Experimentation.kt @@ -9,11 +9,8 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.TestHelper.appContext object Experimentation { - val experiments = - appContext.components.analytics.experiments - fun withHelper(block: NimbusMessagingHelperInterface.() -> Unit) { - val helper = experiments.createMessageHelper() + val helper = appContext.components.nimbus.createJexlHelper() block(helper) } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt index 87b1079574..c353b668f4 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt @@ -20,7 +20,6 @@ import org.junit.Test import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestHelper -import org.mozilla.fenix.messaging.CustomAttributeProvider /** * This test is to test the integrity of messages hardcoded in the FML. @@ -35,7 +34,7 @@ class NimbusMessagingMessageTest { private lateinit var context: Context private val storage - get() = context.components.analytics.messagingStorage + get() = context.components.nimbus.messagingStorage @get:Rule val activityTestRule = @@ -73,10 +72,7 @@ class NimbusMessagingMessageTest { */ @Test fun testAllMessageTriggers() = runTest { - val nimbus = context.components.analytics.experiments - val helper = nimbus.createMessageHelper( - CustomAttributeProvider.getCustomAttributes(context), - ) + val helper = context.components.nimbus.createJexlHelper() val messages = storage.getMessages() messages.forEach { message -> storage.isMessageEligible(message, helper) @@ -84,6 +80,7 @@ class NimbusMessagingMessageTest { fail("${message.id} has a problem with its JEXL trigger: ${storage.malFormedMap.keys}") } } + helper.destroy() } private fun checkIsLocalized(string: String) { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt index 6ba04c9e88..e061d6845f 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt @@ -38,7 +38,7 @@ class NimbusMessagingTriggerTest { @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - nimbus = TestHelper.appContext.components.analytics.experiments + nimbus = TestHelper.appContext.components.nimbus.sdk feature = FxNimbusMessaging.features.messaging.value() } diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 6dfd7a0695..f87e5a0f9d 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -433,7 +433,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { fun queueNimbusFetchInForeground() { queue.runIfReadyOrQueue { GlobalScope.launch(Dispatchers.IO) { - components.analytics.experiments.maybeFetchExperiments( + components.nimbus.sdk.maybeFetchExperiments( context = this@FenixApplication, ) } @@ -502,7 +502,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { beginSetupMegazord() // This lazily constructs the Nimbus object… - val nimbus = components.analytics.experiments + val nimbus = components.nimbus.sdk // … which we then can populate the feature configuration. FxNimbus.initialize { nimbus } } diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index c649a8dd0d..1bc06595f2 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -225,7 +225,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { val startTimeProfiler = components.core.engine.profiler?.getProfilerTime() // Setup nimbus-cli tooling. This is a NOOP when launching normally. - components.analytics.experiments.initializeTooling(applicationContext, intent) + components.nimbus.sdk.initializeTooling(applicationContext, intent) components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager) MarkersFragmentLifecycleCallbacks.register(supportFragmentManager, components.core.engine) @@ -354,7 +354,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { ?.also { Events.appOpened.record(Events.AppOpenedExtra(it)) // This will record an event in Nimbus' internal event store. Used for behavioral targeting - components.analytics.experiments.recordEvent("app_opened") + components.nimbus.events.recordEvent("app_opened") if (safeIntent.action.equals(ACTION_OPEN_PRIVATE_TAB) && it == APP_ICON) { AppIcon.newPrivateTabTapped.record(NoExtras()) @@ -1221,7 +1221,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } private suspend fun showFullscreenMessageIfNeeded(context: Context) { - val messagingStorage = context.components.analytics.messagingStorage + val messagingStorage = context.components.nimbus.messagingStorage val messages = messagingStorage.getMessages() val nextMessage = messagingStorage.getNextMessage(FenixMessageSurfaceId.SURVEY, messages) 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 b68ea5e145..0208c135b5 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -14,10 +14,6 @@ import mozilla.components.lib.crash.sentry.SentryService import mozilla.components.lib.crash.service.CrashReporterService import mozilla.components.lib.crash.service.GleanCrashReporterService import mozilla.components.lib.crash.service.MozillaSocorroService -import mozilla.components.service.nimbus.NimbusApi -import mozilla.components.service.nimbus.messaging.FxNimbusMessaging -import mozilla.components.service.nimbus.messaging.NimbusMessagingStorage -import mozilla.components.service.nimbus.messaging.OnDiskMessageMetadataStorage import mozilla.components.support.ktx.android.content.isMainProcess import mozilla.components.support.utils.BrowsersCache import mozilla.components.support.utils.RunWhenReadyQueue @@ -33,10 +29,8 @@ import org.mozilla.fenix.components.metrics.InstallReferrerMetricsService import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricsStorage import org.mozilla.fenix.crashes.CrashFactCollector -import org.mozilla.fenix.experiments.createNimbus import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.messaging.CustomAttributeProvider import org.mozilla.fenix.perf.lazyMonitored import org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID import org.mozilla.geckoview.BuildConfig.MOZ_APP_VENDOR @@ -159,20 +153,6 @@ class Analytics( context.settings(), ) } - - val experiments: NimbusApi by lazyMonitored { - createNimbus(context, BuildConfig.NIMBUS_ENDPOINT) - } - - val messagingStorage by lazyMonitored { - NimbusMessagingStorage( - context = context, - metadataStorage = OnDiskMessageMetadataStorage(context), - nimbus = experiments, - messagingFeature = FxNimbusMessaging.features.messaging, - attributeProvider = CustomAttributeProvider, - ) - } } private fun isSentryEnabled() = !BuildConfig.SENTRY_TOKEN.isNullOrEmpty() diff --git a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt index 250a380ca4..9b6f4bfbd5 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -230,7 +230,7 @@ internal class TelemetryAccountObserver( // User signed-in into an existing FxA account. AuthType.Signin -> { SyncAuth.signIn.record(NoExtras()) - context.components.analytics.experiments.recordEvent("sync_auth.sign_in") + context.components.nimbus.events.recordEvent("sync_auth.sign_in") } // User created a new FxA account. diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 6a4ea2328c..fd74fcb378 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -170,6 +170,7 @@ class Components(private val context: Context) { } val analytics by lazyMonitored { Analytics(context, performance.visualCompletenessQueue.queue) } + val nimbus by lazyMonitored { NimbusComponents(context) } val publicSuffixList by lazyMonitored { PublicSuffixList(context) } val clipboardHandler by lazyMonitored { ClipboardHandler(context) } val performance by lazyMonitored { PerformanceComponent() } @@ -231,7 +232,7 @@ class Components(private val context: Context) { context.pocketStoriesSelectedCategoriesDataStore, ), MessagingMiddleware( - messagingStorage = analytics.messagingStorage, + messagingStorage = nimbus.messagingStorage, ), MetricsMiddleware(metrics = analytics.metrics), ), diff --git a/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt b/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt new file mode 100644 index 0000000000..a83b3ef594 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt @@ -0,0 +1,88 @@ +/* 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.components + +import android.content.Context +import mozilla.components.service.nimbus.NimbusApi +import mozilla.components.service.nimbus.messaging.FxNimbusMessaging +import mozilla.components.service.nimbus.messaging.NimbusMessagingStorage +import mozilla.components.service.nimbus.messaging.OnDiskMessageMetadataStorage +import org.mozilla.experiments.nimbus.NimbusEventStore +import org.mozilla.experiments.nimbus.NimbusMessagingHelperInterface +import org.mozilla.fenix.BuildConfig +import org.mozilla.fenix.experiments.createNimbus +import org.mozilla.fenix.messaging.CustomAttributeProvider +import org.mozilla.fenix.perf.lazyMonitored + +/** + * Component group for access to Nimbus and other Nimbus services. + */ +class NimbusComponents(private val context: Context) { + + /** + * The main entry point for the Nimbus SDK. Note that almost all access to feature configuration + * should be mediated through a FML generated class, e.g. [FxNimbus]. + */ + val sdk: NimbusApi by lazyMonitored { + createNimbus(context, BuildConfig.NIMBUS_ENDPOINT) + } + + /** + * Convenience method for getting the event store from the SDK. + * + * Before EXP-4354, this is the main write API for recording events to drive + * messaging, experiments and onboarding. + * + * Following EXP-4354, clients will not need to write these events + * themselves. + * + * Read access to the event store should be done through + * the JEXL helper available from [createJexlHelper]. + */ + val events: NimbusEventStore by lazyMonitored { + sdk.events + } + + /** + * Create a new JEXL evaluator suitable for use by any feature. + * + * JEXL evaluator context is provided by the app and changes over time. + * + * For this reason, an evaluator should be not be stored or cached. + * + * Since it has a native peer, to avoid leaking memory, the helper's [destroy] method + * should be called after finishing the set of evaluations. + * + * This can be done automatically using the interface's `use` method, e.g. + * + * ``` + * val isEligible = nimbus.createJexlHelper().use { helper -> + * expressions.all { exp -> helper.evalJexl(exp) } + * } + * ``` + * + * The helper has access to all context needed to drive decisions + * about messaging, onboarding and experimentation. + * + * It also has a built-in cache. + */ + fun createJexlHelper(): NimbusMessagingHelperInterface = + messagingStorage.createMessagingHelper() + + /** + * Low level access to the messaging component. + * + * The app should access this through a [mozilla.components.service.nimbus.messaging.NimbusMessagingController]. + */ + val messagingStorage by lazyMonitored { + NimbusMessagingStorage( + context = context, + metadataStorage = OnDiskMessageMetadataStorage(context), + nimbus = sdk, + messagingFeature = FxNimbusMessaging.features.messaging, + attributeProvider = CustomAttributeProvider, + ) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 588737e35a..24fb5ec9ff 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -360,7 +360,7 @@ class HomeFragment : Fragment() { engine = components.core.engine, messageController = DefaultMessageController( appStore = components.appStore, - messagingController = FenixNimbusMessagingController(components.analytics.messagingStorage), + messagingController = FenixNimbusMessagingController(components.nimbus.messagingStorage), homeActivity = activity, ), store = store, diff --git a/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt b/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt index 6a8bd3ba7f..f75b4ef281 100644 --- a/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt +++ b/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt @@ -45,7 +45,7 @@ class MessageNotificationWorker( override suspend fun doWork(): Result { val context = applicationContext - val messagingStorage = context.components.analytics.messagingStorage + val messagingStorage = context.components.nimbus.messagingStorage val messages = messagingStorage.getMessages() val nextMessage = messagingStorage.getNextMessage(FenixMessageSurfaceId.NOTIFICATION, messages) @@ -178,7 +178,7 @@ class NotificationDismissedService : LifecycleService() { if (intent != null) { val nimbusMessagingController = - FenixNimbusMessagingController(applicationContext.components.analytics.messagingStorage) + FenixNimbusMessagingController(applicationContext.components.nimbus.messagingStorage) lifecycleScope.launch { // Get the relevant message. @@ -209,7 +209,7 @@ class NotificationClickedReceiverActivity : ComponentActivity() { super.onCreate(savedInstanceState) val nimbusMessagingController = - FenixNimbusMessagingController(components.analytics.messagingStorage) + FenixNimbusMessagingController(components.nimbus.messagingStorage) lifecycleScope.launch { // Get the relevant message. diff --git a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt index 9b0854cd5d..f2ec442277 100644 --- a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt @@ -51,7 +51,7 @@ class NimbusBranchesFragment : Fragment() { context = requireContext(), navController = findNavController(), nimbusBranchesStore = nimbusBranchesStore, - experiments = requireContext().components.analytics.experiments, + experiments = requireContext().components.nimbus.sdk, experimentId = args.experimentId, ) @@ -77,7 +77,7 @@ class NimbusBranchesFragment : Fragment() { private fun loadExperimentBranches() { lifecycleScope.launch(Dispatchers.IO) { try { - val experiments = requireContext().components.analytics.experiments + val experiments = requireContext().components.nimbus.sdk val branches = experiments.getExperimentBranches(args.experimentId) ?: emptyList() val selectedBranch = experiments.getExperimentBranch(args.experimentId) ?: "" diff --git a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt index 37f50d6ede..3cfe906d7c 100644 --- a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt @@ -35,7 +35,7 @@ class NimbusExperimentsFragment : Fragment() { setContent { FirefoxTheme { val experiments = - requireContext().components.analytics.experiments.getAvailableExperiments() + requireContext().components.nimbus.sdk.getAvailableExperiments() NimbusExperiments( experiments = experiments, diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt index af4693681b..97d87682e5 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt @@ -232,7 +232,7 @@ class OnboardingFragment : Fragment() { showAddWidgetPage: Boolean, ): List { val jexlConditions = FxNimbus.features.junoOnboarding.value().conditions - val jexlHelper = requireContext().components.analytics.messagingStorage.createMessagingHelper() + val jexlHelper = requireContext().components.nimbus.createJexlHelper() val privacyCaption = Caption( text = getString(R.string.juno_onboarding_privacy_notice_text), diff --git a/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt index e029e2ebf8..f64fecc63a 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt @@ -37,7 +37,7 @@ class DataChoicesFragment : PreferenceFragmentCompat() { // Reset experiment identifiers on both opt-in and opt-out; it's likely // that in future we will need to pass in the new telemetry client_id // to this method when the user opts back in. - context.components.analytics.experiments.resetTelemetryIdentifiers() + context.components.nimbus.sdk.resetTelemetryIdentifiers() } else if (key == getPreferenceKey(R.string.pref_key_marketing_telemetry)) { if (context.settings().isMarketingTelemetryEnabled) { context.components.analytics.metrics.start(MetricServiceType.Marketing) diff --git a/app/src/main/java/org/mozilla/fenix/settings/studies/StudiesFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/studies/StudiesFragment.kt index 87d9c6e1bd..f2ecc0ea6c 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/studies/StudiesFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/studies/StudiesFragment.kt @@ -30,7 +30,7 @@ class StudiesFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - val experiments = requireComponents.analytics.experiments + val experiments = requireComponents.nimbus.sdk _binding = SettingsStudiesBinding.inflate(inflater, container, false) val interactor = DefaultStudiesInteractor((activity as HomeActivity), experiments) StudiesView( diff --git a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt index 5cf7470493..3723d49772 100644 --- a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt @@ -56,8 +56,9 @@ class BackgroundServicesTest { val mockComponents: Components = mockk() every { mockComponents.settings } returns settings - every { mockComponents.analytics } returns mockk { - every { experiments } returns nimbus + every { mockComponents.nimbus } returns mockk { + every { sdk } returns nimbus + every { events } returns nimbus } every { context.components } returns mockComponents every { nimbus.recordEvent(any()) } returns Unit