diff --git a/.experimenter.json b/.experimenter.json new file mode 100644 index 000000000..aafcabe34 --- /dev/null +++ b/.experimenter.json @@ -0,0 +1,59 @@ +{ + "search-term-groups": { + "description": "A feature allowing the grouping of URLs around the search term that it came from.", + "hasExposure": true, + "exposureDescription": "", + "variables": { + "enabled": { + "description": "If true, the feature shows up on the homescreen and on the new tab screen.", + "type": "boolean" + } + } + }, + "default-browser-message": { + "description": "A small feature allowing experiments on the placement of a default browser message.", + "hasExposure": true, + "exposureDescription": "", + "variables": { + "message-location": { + "description": "Where is the message to be put.", + "enum": [ + "homescreen-banner", + "settings", + "app-menu-item" + ], + "type": "string" + } + } + }, + "nimbus-validation": { + "description": "A feature that does not correspond to an application feature suitable for showing that Nimbus is working. This should never be used in production.", + "hasExposure": true, + "exposureDescription": "", + "variables": { + "settings-icon": { + "description": "The drawable displayed in the app menu for Settings", + "type": "string" + }, + "settings-punctuation": { + "description": "The emoji displayed in the Settings screen title.", + "type": "string" + }, + "settings-title": { + "description": "The title of displayed in the Settings screen and app menu.", + "type": "string" + } + } + }, + "homescreen": { + "description": "The homescreen that the user goes to when they press home or new tab.", + "hasExposure": true, + "exposureDescription": "", + "variables": { + "sections-enabled": { + "description": "This property provides a lookup table of whether or not the given section should be enabled. If the section is enabled, it should be toggleable in the settings screen, and on by default.", + "type": "json" + } + } + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 7d04267d5..6a1423caa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -380,7 +380,28 @@ android.applicationVariants.all { variant -> // Generate Kotlin code for the Fenix Glean metrics. apply plugin: "org.mozilla.telemetry.glean-gradle-plugin" - +apply plugin: "org.mozilla.components.nimbus-gradle-plugin" + +nimbus { + // The path to the Nimbus feature manifest file + manifestFile = "nimbus.fml.yaml" + // The fully qualified class name for the generated features. + // If the classname begins with a '.' this is taken as a suffix to the app's package name + destinationClass = ".nimbus.FxNimbus" + // Map from the variant name to the channel as experimenter and nimbus understand it. + // If nimbus's channels were accurately set up well for this project, then this + // shouldn't be needed. + channels = [ + debug: "developer", + nightly: "nightly", + beta: "beta", + release: "release", + ] + // This is generated by the FML and should be checked into git. + // It will be fetched by Experimenter (the Nimbus experiment website) + // and used to inform experiment configuration. + experimenterManifest = ".experimenter.json" +} configurations { // There's an interaction between Gradle's resolution of dependencies with different types // (@jar, @aar) for `implementation` and `testImplementation` and with Android Studio's built-in diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index d2c6aeee0..e175dba4d 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -66,7 +66,6 @@ import org.mozilla.fenix.components.Core import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.components.metrics.MozillaProductDetector -import org.mozilla.fenix.components.metrics.SecurePrefsTelemetry import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.isCustomEngine import org.mozilla.fenix.ext.isKnownSearchDomain @@ -273,8 +272,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider { System.currentTimeMillis() - Core.HISTORY_METADATA_MAX_AGE_IN_MS ) } - - SecurePrefsTelemetry(this@FenixApplication, components.analytics.experiments).startTests() } // Account manager initialization needs to happen on the main thread. GlobalScope.launch(Dispatchers.Main) { 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 f01adf20a..bcbb5c3d8 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -23,9 +23,9 @@ import org.mozilla.fenix.ReleaseChannel import org.mozilla.fenix.components.metrics.AdjustMetricsService import org.mozilla.fenix.components.metrics.GleanMetricsService import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.experiments.NimbusFeatures import org.mozilla.fenix.experiments.createNimbus import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.perf.lazyMonitored import org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID import org.mozilla.geckoview.BuildConfig.MOZ_APP_VENDOR @@ -109,11 +109,9 @@ class Analytics( } val experiments: NimbusApi by lazyMonitored { - createNimbus(context, BuildConfig.NIMBUS_ENDPOINT) - } - - val features: NimbusFeatures by lazyMonitored { - NimbusFeatures(context) + createNimbus(context, BuildConfig.NIMBUS_ENDPOINT).also { api -> + FxNimbus.api = api + } } } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/SecurePrefsTelemetry.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/SecurePrefsTelemetry.kt deleted file mode 100644 index 0035408ae..000000000 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/SecurePrefsTelemetry.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* 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.metrics - -import android.content.Context -import android.os.Build -import mozilla.components.lib.dataprotect.SecurePrefsReliabilityExperiment -import mozilla.components.service.nimbus.NimbusApi -import org.mozilla.fenix.experiments.ExperimentBranch -import org.mozilla.fenix.experiments.FeatureId -import org.mozilla.fenix.ext.withExperiment - -/** - * Allows starting a quick test of ACs SecureAbove22Preferences that will emit Facts - * for the basic operations and allow us to log them for later evaluation of APIs stability. - */ -class SecurePrefsTelemetry( - private val appContext: Context, - private val experiments: NimbusApi -) { - fun startTests() { - // The Android Keystore is used to secure the shared prefs only on API 23+ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - // These tests should run only if the experiment is live - experiments.withExperiment(FeatureId.ANDROID_KEYSTORE) { experimentBranch -> - // .. and this device is not in the control group. - if (experimentBranch == ExperimentBranch.TREATMENT) { - SecurePrefsReliabilityExperiment(appContext)() - } - } - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index 6204d5fbe..74ce88b61 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -37,11 +37,10 @@ import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged import org.mozilla.fenix.R import org.mozilla.fenix.components.accounts.FenixAccountManager -import org.mozilla.fenix.experiments.ExperimentBranch -import org.mozilla.fenix.experiments.FeatureId import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.ext.withExperiment +import org.mozilla.fenix.nimbus.FxNimbus +import org.mozilla.fenix.nimbus.MessageSurfaceId import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.utils.BrowsersCache @@ -361,6 +360,7 @@ open class DefaultToolbarMenu( @VisibleForTesting(otherwise = PRIVATE) val coreMenuItems by lazy { + val defaultBrowserItem = getSetDefaultBrowserItem() val menuItems = listOfNotNull( if (shouldUseBottomToolbar) null else menuToolbar, @@ -372,8 +372,8 @@ open class DefaultToolbarMenu( extensionsItem, syncMenuItem, BrowserMenuDivider(), - getSetDefaultBrowserItem(), - getSetDefaultBrowserItem()?.let { BrowserMenuDivider() }, + defaultBrowserItem, + defaultBrowserItem?.let { BrowserMenuDivider() }, findInPageItem, desktopSiteItem, customizeReaderView.apply { visible = ::shouldShowReaderViewCustomization }, @@ -446,23 +446,17 @@ open class DefaultToolbarMenu( } } - private fun getSetDefaultBrowserItem(): BrowserMenuImageText? { - val experiments = context.components.analytics.experiments - val browsers = BrowsersCache.all(context) - - return experiments.withExperiment(FeatureId.DEFAULT_BROWSER) { experimentBranch -> - if (experimentBranch == ExperimentBranch.DEFAULT_BROWSER_TOOLBAR_MENU && - !browsers.isFirefoxDefaultBrowser + private fun getSetDefaultBrowserItem(): BrowserMenuImageText? = + if (BrowsersCache.all(context).isFirefoxDefaultBrowser) { + null + } else if (FxNimbus.features.defaultBrowserMessage.value().messageLocation == MessageSurfaceId.APP_MENU_ITEM) { + BrowserMenuImageText( + label = context.getString(R.string.preferences_set_as_default_browser), + imageResource = R.mipmap.ic_launcher ) { - return@withExperiment BrowserMenuImageText( - label = context.getString(R.string.preferences_set_as_default_browser), - imageResource = R.mipmap.ic_launcher - ) { - onItemTapped.invoke(ToolbarMenu.Item.SetDefaultBrowser) - } - } else { - null + onItemTapped.invoke(ToolbarMenu.Item.SetDefaultBrowser) } + } else { + null } - } } diff --git a/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt b/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt deleted file mode 100644 index cd5d7f9dd..000000000 --- a/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* 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 - -/** - * Enums to identify features in the app. These will likely grow and shrink depending - * on the experiments we want to perform. - * - * @property jsonName the kebab-case version of the feature id as represented in the Nimbus - * experiment JSON. - */ -enum class FeatureId(val jsonName: String) { - NIMBUS_VALIDATION("nimbus-validation"), - ANDROID_KEYSTORE("fenix-android-keystore"), - DEFAULT_BROWSER("fenix-default-browser"), - HOME_PAGE("homescreen") -} - -/** - * Experiment branches are becoming less interesting, though we collect some well - * defined ones here. - */ -class ExperimentBranch { - companion object { - const val TREATMENT = "treatment" - const val CONTROL = "control" - const val A1 = "a1" - const val A2 = "a2" - const val DEFAULT_BROWSER_TOOLBAR_MENU = "default_browser_toolbar_menu" - const val DEFAULT_BROWSER_NEW_TAB_BANNER = "default_browser_newtab_banner" - const val DEFAULT_BROWSER_SETTINGS_MENU = "default_browser_settings_menu" - } -} diff --git a/app/src/main/java/org/mozilla/fenix/experiments/NimbusFeatures.kt b/app/src/main/java/org/mozilla/fenix/experiments/NimbusFeatures.kt deleted file mode 100644 index e05542fb5..000000000 --- a/app/src/main/java/org/mozilla/fenix/experiments/NimbusFeatures.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* 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 org.mozilla.experiments.nimbus.mapKeysAsEnums -import org.mozilla.fenix.FeatureFlags -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.getVariables - -/** - * Component for exposing nimbus Feature Variables. - * For more information see https://experimenter.info/feature-variables-and-me - * - * @param context - A [Context] for accessing the feature variables from nimbus. - */ -class NimbusFeatures(private val context: Context) { - - val homeScreen: HomeScreenFeatures by lazy { - HomeScreenFeatures(context) - } - - /** - * Component that indicates which features should be active on the home screen. - */ - class HomeScreenFeatures(private val context: Context) { - /** - * `FeatureId.HOME_PAGE` feature; the complete JSON, is shown here: - * - * ```json - * { - * "sections-enabled": { - * "topSites": true, - * "recentlySaved": false, - * "jumpBackIn": false, - * "pocket": false, - * "recentExplorations": false - * } - * } - * ``` - */ - - /** - * This enum accompanies the `FeatureId.HOME_PAGE` feature. - * - * These names here should match the names of entries in the JSON. - */ - @Suppress("EnumNaming") - private enum class HomeScreenSection(val default: Boolean) { - topSites(true), - recentlySaved(true), - jumpBackIn(true), - pocket(true), - recentExplorations(true); - - companion object { - /** - * CreateS a map with the corresponding default values for each sections. - */ - fun toMap(context: Context): Map { - return values().associate { section -> - val value = if (section == pocket) { - FeatureFlags.isPocketRecommendationsFeatureEnabled(context) - } else { - section.default - } - section to value - } - } - } - } - - private val homeScreenFeatures: Map by lazy { - val experiments = context.components.analytics.experiments - val variables = experiments.getVariables(FeatureId.HOME_PAGE, false) - val sections: Map = - variables.getBoolMap("sections-enabled")?.mapKeysAsEnums() - ?: HomeScreenSection.toMap(context) - sections - } - - /** - * Indicates if the recently tabs feature is active. - */ - fun isRecentlyTabsActive(): Boolean { - return homeScreenFeatures[HomeScreenSection.jumpBackIn] == true - } - - /** - * Indicates if the recently saved feature is active. - */ - fun isRecentlySavedActive(): Boolean { - return homeScreenFeatures[HomeScreenSection.recentlySaved] == true - } - - /** - * Indicates if the recently exploration feature is active. - */ - fun isRecentExplorationsActive(): Boolean { - return homeScreenFeatures[HomeScreenSection.recentExplorations] == true - } - - /** - * Indicates if the pocket recommendations feature is active. - */ - fun isPocketRecommendationsActive(): Boolean { - return homeScreenFeatures[HomeScreenSection.pocket] == true - } - - /** - * Indicates if the top sites feature is active. - */ - fun isTopSitesActive(): Boolean { - return homeScreenFeatures[HomeScreenSection.topSites] == true - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/ext/Nimbus.kt b/app/src/main/java/org/mozilla/fenix/ext/Nimbus.kt deleted file mode 100644 index a218be948..000000000 --- a/app/src/main/java/org/mozilla/fenix/ext/Nimbus.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* 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.ext - -import mozilla.components.service.nimbus.NimbusApi -import mozilla.components.support.base.log.logger.Logger -import org.mozilla.experiments.nimbus.Variables -import org.mozilla.fenix.experiments.FeatureId - -/** - * Gets the branch name of an experiment acting on the feature given `featureId`, and transforms it - * with given closure. - * - * You are probably looking for `withVariables`. - * - * If we're enrolled in an experiment, the transform is passed the branch id/slug as a `String`. - * - * If we're not enrolled in the experiment, or the experiment is not valid then the transform - * is passed a `null`. - */ -fun NimbusApi.withExperiment(featureId: FeatureId, transform: (String?) -> T): T { - return transform(withExperiment(featureId)) -} - -/** - * The synonym for [getExperimentBranch] to complement [withExperiment(String, (String?) -> T))]. - * - * Short-hand for ` org.mozilla.experiments.nimbus.NimbusApi.getExperimentBranch`. - */ -@Suppress("TooGenericExceptionCaught") -fun NimbusApi.withExperiment(featureId: FeatureId) = - try { - getExperimentBranch(featureId.jsonName) - } catch (e: Throwable) { - Logger.error("Failed to getExperimentBranch(${featureId.jsonName})", e) - null - } - -/** - * Get the variables needed to configure the feature given by `featureId`. - * - * @param featureId The feature id that identifies the feature under experiment. - * - * @param sendExposureEvent Passing `true` to this parameter will record the exposure event - * automatically if the client is enrolled in an experiment for the given [featureId]. - * Passing `false` here indicates that the application will manually record the exposure - * event by calling the `sendExposureEvent` function at the time of the exposure to the - * feature. - * - * See [sendExposureEvent] for more information on manually recording the event. - * - * @return a [Variables] object used to configure the feature. - */ -fun NimbusApi.getVariables(featureId: FeatureId, sendExposureEvent: Boolean = true) = - getVariables(featureId.jsonName, sendExposureEvent) - -/** - * A synonym for `getVariables(featureId, sendExposureEvent)`. - * - * This exists as a complement to the `withVariable(featureId, sendExposureEvent, transform)` method. - * - * @param featureId the id of the feature as it appears in `Experimenter` - * @param sendExposureEvent by default `true`. This logs an event that the user was exposed to an experiment - * involving this feature. - * @return a `Variables` object providing typed accessors to a remotely configured JSON object. - */ -fun NimbusApi.withVariables(featureId: FeatureId, sendExposureEvent: Boolean = true) = - getVariables(featureId, sendExposureEvent) - -/** - * Get a `Variables` object for this feature and use that to configure the feature itself or a - * more type safe configuration object. - * - * @param featureId the id of the feature as it appears in `Experimenter` - * @param sendExposureEvent by default `true`. This logs an event that the user was exposed to an experiment - * involving this feature. - */ -fun NimbusApi.withVariables(featureId: FeatureId, sendExposureEvent: Boolean = true, transform: (Variables) -> T) = - transform(getVariables(featureId, sendExposureEvent)) - -/** - * Records the `exposure` event in telemetry. - * - * This is a manual function to accomplish the same purpose as passing `true` as the - * `sendExposureEvent` property of the `getVariables` function. It is intended to be used - * when requesting feature variables must occur at a different time than the actual user's - * exposure to the feature within the app. - * - * - Examples: - * - If the `Variables` are needed at a different time than when the exposure to the feature - * actually happens, such as constructing a menu happening at a different time than the - * user seeing the menu. - * - If `getVariables` is required to be called multiple times for the same feature and it is - * desired to only record the exposure once, such as if `getVariables` were called - * with every keystroke. - * - * In the case where the use of this function is required, then the `getVariables` function - * should be called with `false` so that the exposure event is not recorded when the variables - * are fetched. - * - * This function is safe to call even when there is no active experiment for the feature. The SDK - * will ensure that an event is only recorded for active experiments. - * - * @param featureId string representing the id of the feature for which to record the exposure - * event. - */ -fun NimbusApi.recordExposureEvent(featureId: FeatureId) = - recordExposureEvent(featureId.jsonName) 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 e46d631e5..c01a4d9bc 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -94,13 +94,11 @@ import org.mozilla.fenix.components.toolbar.FenixTabCounterMenu import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.databinding.FragmentHomeBinding import org.mozilla.fenix.datastore.pocketStoriesSelectedCategoriesDataStore -import org.mozilla.fenix.experiments.FeatureId import org.mozilla.fenix.ext.asRecentTabs import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.hideToolbar import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav -import org.mozilla.fenix.ext.recordExposureEvent import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.ext.settings @@ -119,6 +117,7 @@ import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor import org.mozilla.fenix.home.sessioncontrol.SessionControlView import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder import org.mozilla.fenix.home.topsites.DefaultTopSitesView +import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.onboarding.FenixOnboarding import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks import org.mozilla.fenix.settings.SupportUtils @@ -390,7 +389,7 @@ class HomeFragment : Fragment() { activity.themeManager.applyStatusBarTheme(activity) - requireContext().components.analytics.experiments.recordExposureEvent(FeatureId.HOME_PAGE) + FxNimbus.features.homescreen.recordExposure() if (shouldEnableWallpaper()) { val wallpaperManger = requireComponents.wallpaperManager diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt b/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt index 468522410..9a09de948 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt @@ -26,10 +26,9 @@ import mozilla.components.support.ktx.android.content.getColorFromAttr import org.mozilla.fenix.R import org.mozilla.fenix.components.accounts.AccountState import org.mozilla.fenix.components.accounts.FenixAccountManager -import org.mozilla.fenix.experiments.FeatureId import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.getVariables import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.whatsnew.WhatsNew @@ -112,7 +111,6 @@ class HomeMenu( @Suppress("ComplexMethod") private fun coreMenuItems(): List { - val experiments = context.components.analytics.experiments val settings = context.components.settings val bookmarksItem = BrowserMenuImageText( @@ -176,10 +174,15 @@ class HomeMenu( } // Use nimbus to set the icon and title. - val variables = experiments.getVariables(FeatureId.NIMBUS_VALIDATION) + val nimbusValidation = FxNimbus.features.nimbusValidation.value() + val settingsIcon = context.resources.getIdentifier( + nimbusValidation.settingsIcon, + "drawable", + context.packageName + ) val settingsItem = BrowserMenuImageText( - variables.getText("settings-title") ?: context.getString(R.string.browser_menu_settings), - variables.getDrawableResource("settings-icon") ?: R.drawable.mozac_ic_settings, + nimbusValidation.settingsTitle, + settingsIcon, primaryTextColor ) { onItemTapped.invoke(Item.Settings) diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index 220d0f187..5c5e5ab24 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -40,8 +40,6 @@ import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.databinding.AmoCollectionOverrideDialogBinding -import org.mozilla.fenix.experiments.ExperimentBranch -import org.mozilla.fenix.experiments.FeatureId import org.mozilla.fenix.ext.application import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey @@ -50,10 +48,10 @@ import org.mozilla.fenix.ext.navigateToNotificationsSettings import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.REQUEST_CODE_BROWSER_ROLE -import org.mozilla.fenix.ext.getVariables import org.mozilla.fenix.ext.openSetDefaultBrowserOption import org.mozilla.fenix.ext.showToolbar -import org.mozilla.fenix.ext.withExperiment +import org.mozilla.fenix.nimbus.FxNimbus +import org.mozilla.fenix.nimbus.MessageSurfaceId import org.mozilla.fenix.settings.account.AccountUiView import org.mozilla.fenix.utils.BrowsersCache import org.mozilla.fenix.utils.Settings @@ -161,10 +159,10 @@ class SettingsFragment : PreferenceFragmentCompat() { super.onResume() // Use nimbus to set the title, and a trivial addition - val experiments = requireContext().components.analytics.experiments - val variables = experiments.getVariables(FeatureId.NIMBUS_VALIDATION) - val title = variables.getText("settings-title") ?: getString(R.string.settings_title) - val suffix = variables.getString("settings-title-punctuation") ?: "" + val nimbusValidation = FxNimbus.features.nimbusValidation.value() + + val title = nimbusValidation.settingsTitle + val suffix = nimbusValidation.settingsPunctuation showToolbar("$title$suffix") @@ -606,12 +604,8 @@ class SettingsFragment : PreferenceFragmentCompat() { } } - private fun isDefaultBrowserExperimentBranch(): Boolean { - val experiments = context?.components?.analytics?.experiments - return experiments?.withExperiment(FeatureId.DEFAULT_BROWSER) { experimentBranch -> - (experimentBranch == ExperimentBranch.DEFAULT_BROWSER_SETTINGS_MENU) - } == true - } + private fun isDefaultBrowserExperimentBranch(): Boolean = + FxNimbus.features.defaultBrowserMessage.value().messageLocation == MessageSurfaceId.SETTINGS private fun isFirefoxDefaultBrowser(): Boolean { val browsers = BrowsersCache.all(requireContext()) diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index da06462d1..fc2674137 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -35,11 +35,11 @@ import org.mozilla.fenix.components.settings.counterPreference import org.mozilla.fenix.components.settings.featureFlagPreference import org.mozilla.fenix.components.settings.lazyFeatureFlagPreference import org.mozilla.fenix.components.toolbar.ToolbarPosition -import org.mozilla.fenix.experiments.ExperimentBranch -import org.mozilla.fenix.experiments.FeatureId import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey -import org.mozilla.fenix.ext.withExperiment +import org.mozilla.fenix.nimbus.FxNimbus +import org.mozilla.fenix.nimbus.HomeScreenSection +import org.mozilla.fenix.nimbus.MessageSurfaceId import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu @@ -120,7 +120,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { var showTopFrecentSites by lazyFeatureFlagPreference( appContext.getPreferenceKey(R.string.pref_key_enable_top_frecent_sites), featureFlag = true, - default = { appContext.components.analytics.features.homeScreen.isTopSitesActive() } + default = { homescreenSections[HomeScreenSection.TOP_SITES] == true }, ) var numberOfAppLaunches by intPreference( @@ -327,12 +327,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { */ fun shouldShowSetAsDefaultBrowserCard(): Boolean { val browsers = BrowsersCache.all(appContext) - val experiments = appContext.components.analytics.experiments - val isExperimentBranch = - experiments.withExperiment(FeatureId.DEFAULT_BROWSER) { experimentBranch -> - (experimentBranch == ExperimentBranch.DEFAULT_BROWSER_NEW_TAB_BANNER) - } - return isExperimentBranch == true && + val feature = FxNimbus.features.defaultBrowserMessage.value() + val isExperimentBranch = feature.messageLocation == MessageSurfaceId.HOMESCREEN_BANNER + return isExperimentBranch && !userDismissedExperimentCard && !browsers.isFirefoxDefaultBrowser && numberOfAppLaunches > APP_LAUNCHES_TO_SHOW_DEFAULT_BROWSER_CARD @@ -1214,9 +1211,13 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = false ) + private val homescreenSections: Map by lazy { + FxNimbus.features.homescreen.value().sectionsEnabled + } + var historyMetadataUIFeature by lazyFeatureFlagPreference( appContext.getPreferenceKey(R.string.pref_key_history_metadata_feature), - default = { appContext.components.analytics.features.homeScreen.isRecentExplorationsActive() }, + default = { homescreenSections[HomeScreenSection.RECENT_EXPLORATIONS] == true }, featureFlag = FeatureFlags.historyMetadataUIFeature || isHistoryMetadataEnabled ) @@ -1227,7 +1228,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { var showRecentTabsFeature by lazyFeatureFlagPreference( appContext.getPreferenceKey(R.string.pref_key_recent_tabs), featureFlag = FeatureFlags.showRecentTabsFeature, - default = { appContext.components.analytics.features.homeScreen.isRecentlyTabsActive() } + default = { homescreenSections[HomeScreenSection.JUMP_BACK_IN] == true }, ) /** @@ -1236,7 +1237,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { */ var showRecentBookmarksFeature by lazyFeatureFlagPreference( appContext.getPreferenceKey(R.string.pref_key_recent_bookmarks), - default = { appContext.components.analytics.features.homeScreen.isRecentlySavedActive() }, + default = { homescreenSections[HomeScreenSection.RECENTLY_SAVED] == true }, featureFlag = FeatureFlags.recentBookmarksFeature ) @@ -1268,7 +1269,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { var showPocketRecommendationsFeature by lazyFeatureFlagPreference( appContext.getPreferenceKey(R.string.pref_key_pocket_homescreen_recommendations), featureFlag = FeatureFlags.isPocketRecommendationsFeatureEnabled(appContext), - default = { appContext.components.analytics.features.homeScreen.isPocketRecommendationsActive() }, + default = { homescreenSections[HomeScreenSection.POCKET] == true }, ) /** 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 c6e4388c1..401f40a74 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt @@ -10,6 +10,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkObject 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 org.junit.Assert.assertFalse @@ -25,6 +26,7 @@ import org.mozilla.fenix.ReleaseChannel import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey 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 @@ -49,6 +51,8 @@ class SettingsFragmentTest { mockkObject(Config) every { Config.channel } returns ReleaseChannel.Nightly + + FxNimbus.api = NimbusDisabled(testContext) } @Test diff --git a/build.gradle b/build.gradle index 4906425ba..6791a8820 100644 --- a/build.gradle +++ b/build.gradle @@ -70,6 +70,7 @@ buildscript { classpath Deps.osslicenses_plugin classpath "org.mozilla.components:tooling-glean-gradle:${Versions.mozilla_android_components}" + classpath "org.mozilla.components:tooling-nimbus-gradle:${Versions.mozilla_android_components}" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/nimbus.fml.yaml b/nimbus.fml.yaml new file mode 100644 index 000000000..3589a8722 --- /dev/null +++ b/nimbus.fml.yaml @@ -0,0 +1,89 @@ +--- +channels: + - release + - beta + - nightly + - developer +features: + homescreen: + description: The homescreen that the user goes to when they press home or new tab. + variables: + sections-enabled: + description: "This property provides a lookup table of whether or not the given section should be enabled. + If the section is enabled, it should be toggleable in the settings screen, and on by default." + type: Map + default: + { + "top-sites": true, + "jump-back-in": true, + "recently-saved": true, + "recent-explorations": true, + "pocket": true + } + defaults: + - channel: nightly + value: { + "sections-enabled": { + "top-sites": true, + "jump-back-in": true, + "recently-saved": true, + "recent-explorations": true, + "pocket": true + } + } + nimbus-validation: + description: "A feature that does not correspond to an application feature suitable for showing + that Nimbus is working. This should never be used in production." + variables: + settings-title: + description: The title of displayed in the Settings screen and app menu. + type: Text + default: browser_menu_settings + settings-punctuation: + description: The emoji displayed in the Settings screen title. + type: String + default: "" + settings-icon: + description: The drawable displayed in the app menu for Settings + type: String + default: mozac_ic_settings + search-term-groups: + description: A feature allowing the grouping of URLs around the search term that it came from. + variables: + enabled: + description: If true, the feature shows up on the homescreen and on the new tab screen. + type: Boolean + default: false + default-browser-message: + description: A small feature allowing experiments on the placement of a default browser message. + variables: + message-location: + description: Where is the message to be put. + type: MessageSurfaceId + default: homescreen-banner +types: + objects: {} + enums: + HomeScreenSection: + description: The identifiers for the sections of the homescreen. + variants: + top-sites: + description: The frecency and pinned sites. + recently-saved: + description: The sites the user has bookmarked recently. + jump-back-in: + description: The tabs the user was looking immediately before being interrupted. + recent-explorations: + description: The tab groups + pocket: + description: The pocket section. This should only be available in the US. + MessageSurfaceId: + description: The identity of a message surface, used in the default browser experiments + variants: + app-menu-item: + description: An item in the default toolbar menu. + settings: + description: A setting in the settings screen. + homescreen-banner: + description: A banner in the homescreen. +