Issue #19040: Remove Leanplum (Beta)

pull/420/head
Sebastian Kaspari 3 years ago
parent 03c4a159d4
commit 5d500a1d66

1
.gitignore vendored

@ -80,7 +80,6 @@ gen-external-apklibs
.DS_Store
# Secrets files, e.g. tokens
.leanplum_token
.adjust_token
.sentry_token
.mls_token

@ -120,7 +120,6 @@ Before you can install any release builds, **You will need to sign production bu
Some features are disabled by default when Fenix is built locally. This can be problematic at times for checking performance since you might want to know how your code behaves with those features.
The known features that are disabled by default are:
- Sentry
- Leanplum
- Adjust
- Mozilla Location Services (also known as MLS)
- Firebase Push Services

@ -315,25 +315,6 @@ android.applicationVariants.all { variant ->
println("--")
}
// -------------------------------------------------------------------------------------------------
// Leanplum: Read token from local file if it exists
// -------------------------------------------------------------------------------------------------
print("Leanplum token: ")
try {
def parts = new File("${rootDir}/.leanplum_token").text.trim().split(":")
def id = parts[0]
def key = parts[1]
buildConfigField 'String', 'LEANPLUM_ID', '"' + id + '"'
buildConfigField 'String', 'LEANPLUM_TOKEN', '"' + key + '"'
println "(Added from .leanplum_token file)"
} catch (FileNotFoundException ignored) {
buildConfigField 'String', 'LEANPLUM_ID', 'null'
buildConfigField 'String', 'LEANPLUM_TOKEN', 'null'
println("X_X")
}
// -------------------------------------------------------------------------------------------------
// MLS: Read token from local file if it exists
// -------------------------------------------------------------------------------------------------
@ -434,9 +415,6 @@ dependencies {
implementation Deps.sentry
implementation Deps.leanplum_core
implementation Deps.leanplum_fcm
implementation Deps.mozilla_concept_base
implementation Deps.mozilla_concept_engine
implementation Deps.mozilla_concept_menu

@ -86,7 +86,6 @@ import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor
import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor
import org.mozilla.fenix.home.intent.OpenBrowserIntentProcessor
import org.mozilla.fenix.home.intent.OpenSpecificTabIntentProcessor
import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
@ -159,7 +158,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
listOf(
SpeechProcessingIntentProcessor(this, components.core.store, components.analytics.metrics),
StartSearchIntentProcessor(components.analytics.metrics),
DeepLinkIntentProcessor(this, components.analytics.leanplumMetricsService),
OpenBrowserIntentProcessor(this, ::getIntentSessionId),
OpenSpecificTabIntentProcessor(this)
)

@ -23,7 +23,6 @@ import org.mozilla.fenix.R
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.LeanplumMetricsService
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.experiments.createNimbus
import org.mozilla.fenix.ext.components
@ -90,13 +89,10 @@ class Analytics(
)
}
val leanplumMetricsService by lazyMonitored { LeanplumMetricsService(context as Application) }
val metrics: MetricController by lazyMonitored {
MetricController.create(
listOf(
GleanMetricsService(context, lazy { context.components.core.store }),
leanplumMetricsService,
AdjustMetricsService(context as Application)
),
isDataTelemetryEnabled = { context.settings().isTelemetryEnabled },

@ -216,14 +216,9 @@ internal class TelemetryAccountObserver(
}?.let {
metricController.track(it)
}
// Used by Leanplum as a context variable.
settings.fxaSignedIn = true
}
override fun onLoggedOut() {
metricController.track(Event.SyncAuthSignOut)
// Used by Leanplum as a context variable.
settings.fxaSignedIn = false
}
}

@ -1,247 +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.app.Application
import android.net.Uri
import android.util.Log
import com.leanplum.Leanplum
import com.leanplum.LeanplumActivityHelper
import com.leanplum.annotations.Parser
import com.leanplum.internal.LeanplumInternal
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.support.locale.LocaleManager
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.components.metrics.MozillaProductDetector.MozillaProducts
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor
import java.util.Locale
import java.util.MissingResourceException
import java.util.UUID.randomUUID
private val Event.name: String?
get() = when (this) {
is Event.AddBookmark -> "E_Add_Bookmark"
is Event.RemoveBookmark -> "E_Remove_Bookmark"
is Event.OpenedBookmark -> "E_Opened_Bookmark"
is Event.OpenedApp -> "E_Opened_App"
is Event.OpenedAppFirstRun -> "E_Opened_App_FirstRun"
is Event.InteractWithSearchURLArea -> "E_Interact_With_Search_URL_Area"
is Event.CollectionSaved -> "E_Collection_Created"
is Event.CollectionTabRestored -> "E_Collection_Tab_Opened"
is Event.SyncAuthSignUp -> "E_FxA_New_Signup"
is Event.SyncAuthSignIn, Event.SyncAuthPaired, Event.SyncAuthOtherExternal -> "E_Sign_In_FxA"
is Event.SyncAuthFromSharedCopy, Event.SyncAuthFromSharedReuse -> "E_Sign_In_FxA_Fennec_to_Fenix"
is Event.SyncAuthSignOut -> "E_Sign_Out_FxA"
is Event.ClearedPrivateData -> "E_Cleared_Private_Data"
is Event.DismissedOnboarding -> "E_Dismissed_Onboarding"
is Event.FennecToFenixMigrated -> "E_Fennec_To_Fenix_Migrated"
is Event.AddonInstalled -> "E_Addon_Installed"
is Event.SearchWidgetInstalled -> "E_Search_Widget_Added"
is Event.ChangedToDefaultBrowser -> "E_Changed_Default_To_Fenix"
is Event.TrackingProtectionSettingChanged -> "E_Changed_ETP"
// Do not track other events in Leanplum
else -> null
}
class LeanplumMetricsService(
private val application: Application
) : MetricsService, DeepLinkIntentProcessor.DeepLinkVerifier {
val scope = CoroutineScope(Dispatchers.IO)
var leanplumJob: Job? = null
data class Token(val id: String, val token: String) {
enum class Type { Development, Production, Invalid }
val type by lazy {
when {
token.take(ProdPrefix.length) == ProdPrefix -> Type.Production
token.take(DevPrefix.length) == DevPrefix -> Type.Development
else -> Type.Invalid
}
}
companion object {
private const val ProdPrefix = "prod"
private const val DevPrefix = "dev"
}
}
override val type = MetricServiceType.Marketing
private val token = Token(LeanplumId, LeanplumToken)
@Suppress("ComplexMethod")
override fun start() {
if (!application.settings().isMarketingTelemetryEnabled) return
Leanplum.setIsTestModeEnabled(false)
Leanplum.setApplicationContext(application)
Leanplum.setDeviceId(randomUUID().toString())
Parser.parseVariables(application)
leanplumJob = scope.launch {
val applicationSetLocale = LocaleManager.getCurrentLocale(application)
val currentLocale = applicationSetLocale ?: Locale.getDefault()
val languageCode =
currentLocale.iso3LanguageOrNull
?: currentLocale.language.let {
if (it.isNotBlank()) {
it
} else {
currentLocale.toString()
}
}
if (!isLeanplumEnabled(languageCode)) {
Log.i(LOGTAG, "Leanplum is not available for this locale: $languageCode")
return@launch
}
when (token.type) {
Token.Type.Production -> Leanplum.setAppIdForProductionMode(token.id, token.token)
Token.Type.Development -> Leanplum.setAppIdForDevelopmentMode(token.id, token.token)
Token.Type.Invalid -> {
Log.i(LOGTAG, "Invalid or missing Leanplum token")
return@launch
}
}
LeanplumActivityHelper.enableLifecycleCallbacks(application)
val installedApps = MozillaProductDetector.getInstalledMozillaProducts(application)
val trackingProtection = application.settings().run {
when {
!shouldUseTrackingProtection -> "none"
useStandardTrackingProtection -> "standard"
useStrictTrackingProtection -> "strict"
else -> "custom"
}
}
Leanplum.start(
application, hashMapOf(
"default_browser" to MozillaProductDetector.getMozillaBrowserDefault(application)
.orEmpty(),
"fennec_installed" to installedApps.contains(MozillaProducts.FIREFOX.productName),
"focus_installed" to installedApps.contains(MozillaProducts.FOCUS.productName),
"klar_installed" to installedApps.contains(MozillaProducts.KLAR.productName),
"fxa_signed_in" to application.settings().fxaSignedIn,
"fxa_has_synced_items" to application.settings().fxaHasSyncedItems,
"search_widget_installed" to application.settings().searchWidgetInstalled,
"tracking_protection_enabled" to application.settings().shouldUseTrackingProtection,
"tracking_protection_setting" to trackingProtection,
"fenix" to true
)
)
withContext(Main) {
LeanplumInternal.setCalledStart(true)
LeanplumInternal.setHasStarted(true)
LeanplumInternal.setStartedInBackground(true)
Log.i(LOGTAG, "Started Leanplum with deviceId ${Leanplum.getDeviceId()}" +
" and userId ${Leanplum.getUserId()}")
}
}
}
/**
* Verifies a deep link and returns `true` for deep links that should be handled and `false` if
* a deep link should be rejected.
*
* @See DeepLinkIntentProcessor.verifier
*/
override fun verifyDeepLink(deepLink: Uri): Boolean {
// We compare the local Leanplum device ID against the "uid" query parameter and only
// accept deep links where both values match.
val uid = deepLink.getQueryParameter("uid")
return uid == Leanplum.getDeviceId()
}
override fun stop() {
if (application.settings().isMarketingTelemetryEnabled) return
// As written in LeanPlum SDK documentation, "This prevents Leanplum from communicating with the server."
// as this "isTestMode" flag is checked before LeanPlum SDK does anything.
// Also has the benefit effect of blocking the display of already downloaded messages.
// The reverse of this - setIsTestModeEnabled(false) must be called before trying to init
// LP in the same session.
Leanplum.setIsTestModeEnabled(true)
// This is just to allow restarting LP and it's functionality in the same app session
// as LP stores it's state internally and check against it
LeanplumInternal.setCalledStart(false)
LeanplumInternal.setHasStarted(false)
leanplumJob?.cancel()
}
override fun track(event: Event) {
val leanplumExtras = event.extras
?.map { (key, value) -> key.toString() to value }
?.toMap()
event.name?.also {
Leanplum.track(it, leanplumExtras)
}
}
override fun shouldTrack(event: Event): Boolean {
return application.settings().isTelemetryEnabled &&
token.type != Token.Type.Invalid && !event.name.isNullOrEmpty()
}
private fun isLeanplumEnabled(locale: String): Boolean {
return LEANPLUM_ENABLED_LOCALES.contains(locale)
}
private val Locale.iso3LanguageOrNull: String?
get() =
try {
this.isO3Language
} catch (_: MissingResourceException) {
null
}
companion object {
private const val LOGTAG = "LeanplumMetricsService"
private val LeanplumId: String
// Debug builds have a null (nullable) LEANPLUM_ID
get() = BuildConfig.LEANPLUM_ID.orEmpty()
private val LeanplumToken: String
// Debug builds have a null (nullable) LEANPLUM_TOKEN
get() = BuildConfig.LEANPLUM_TOKEN.orEmpty()
// Leanplum needs to be enabled for the following locales.
// Irrespective of the actual device location.
private val LEANPLUM_ENABLED_LOCALES = setOf(
"eng", // English
"zho", // Chinese
"deu", // German
"fra", // French
"ita", // Italian
"ind", // Indonesian
"por", // Portuguese
"spa", // Spanish; Castilian
"pol", // Polish
"rus", // Russian
"hin", // Hindi
"per", // Persian
"fas", // Persian
"ara", // Arabic
"jpn" // Japanese
)
private const val PREFERENCE_NAME = "LEANPLUM_PREFERENCES"
private const val DEVICE_ID_KEY = "LP_DEVICE_ID"
}
}

@ -5,7 +5,6 @@
package org.mozilla.fenix.components.metrics
import androidx.annotation.VisibleForTesting
import com.leanplum.Leanplum
import mozilla.components.browser.awesomebar.facts.BrowserAwesomeBarFacts
import mozilla.components.browser.menu.facts.BrowserMenuFacts
import mozilla.components.browser.toolbar.facts.ToolbarFacts
@ -218,7 +217,6 @@ internal class ReleaseMetricController(
if (installedAddons is List<*>) {
settings.installedAddonsCount = installedAddons.size
settings.installedAddonsList = installedAddons.joinToString(",")
Leanplum.setUserAttributes(mapOf("installed_addons" to installedAddons.size))
}
}
@ -226,7 +224,6 @@ internal class ReleaseMetricController(
if (enabledAddons is List<*>) {
settings.enabledAddonsCount = enabledAddons.size
settings.enabledAddonsList = enabledAddons.joinToString()
Leanplum.setUserAttributes(mapOf("enabled_addons" to enabledAddons.size))
}
}

@ -1,176 +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.home.intent
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.provider.Settings
import androidx.core.os.bundleOf
import androidx.navigation.NavController
import mozilla.components.concept.engine.EngineSession
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.GlobalDirections
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.SearchWidgetCreator
import org.mozilla.fenix.ext.alreadyOnDestination
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor.DeepLinkVerifier
import org.mozilla.fenix.settings.SupportUtils
/**
* Deep links in the form of `fenix://host` open different parts of the app.
*
* @param verifier [DeepLinkVerifier] that will be used to verify deep links before handling them.
*/
class DeepLinkIntentProcessor(
private val activity: HomeActivity,
private val verifier: DeepLinkVerifier
) : HomeIntentProcessor {
private val logger = Logger("DeepLinkIntentProcessor")
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
val scheme = intent.scheme?.equals(BuildConfig.DEEP_LINK_SCHEME, ignoreCase = true) ?: return false
return if (scheme) {
intent.data?.let { handleDeepLink(it, navController) }
true
} else {
false
}
}
@Suppress("ComplexMethod")
private fun handleDeepLink(deepLink: Uri, navController: NavController) {
if (!verifier.verifyDeepLink(deepLink)) {
logger.warn("Invalid deep link: $deepLink")
return
}
handleDeepLinkSideEffects(deepLink)
val globalDirections = when (deepLink.host) {
"home", "enable_private_browsing" -> GlobalDirections.Home
"urls_bookmarks" -> GlobalDirections.Bookmarks
"urls_history" -> GlobalDirections.History
"settings" -> GlobalDirections.Settings
"turn_on_sync" -> GlobalDirections.Sync
"settings_search_engine" -> GlobalDirections.SearchEngine
"settings_accessibility" -> GlobalDirections.Accessibility
"settings_delete_browsing_data" -> GlobalDirections.DeleteData
"settings_addon_manager" -> GlobalDirections.SettingsAddonManager
"settings_logins" -> GlobalDirections.SettingsLogins
"settings_tracking_protection" -> GlobalDirections.SettingsTrackingProtection
// We'd like to highlight views within the fragment
// https://github.com/mozilla-mobile/fenix/issues/11856
// The current version of UI has these features in more complex screens.
"settings_privacy" -> GlobalDirections.Settings
"home_collections" -> GlobalDirections.Home
else -> return
}
if (!navController.alreadyOnDestination(globalDirections.destinationId)) {
navController.navigateBlockingForAsyncNavGraph(globalDirections.navDirections)
}
}
/**
* Handle links that require more than just simple navigation.
*/
private fun handleDeepLinkSideEffects(deepLink: Uri) {
when (deepLink.host) {
"enable_private_browsing" -> {
activity.browsingModeManager.mode = BrowsingMode.Private
}
"make_default_browser" -> {
if (SDK_INT >= Build.VERSION_CODES.N) {
val settingsIntent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
settingsIntent.putExtra(SETTINGS_SELECT_OPTION_KEY, DEFAULT_BROWSER_APP_OPTION)
settingsIntent.putExtra(SETTINGS_SHOW_FRAGMENT_ARGS,
bundleOf(SETTINGS_SELECT_OPTION_KEY to DEFAULT_BROWSER_APP_OPTION))
activity.startActivity(settingsIntent)
} else {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic(
activity,
SupportUtils.SumoTopic.SET_AS_DEFAULT_BROWSER
),
newTab = true,
from = BrowserDirection.FromGlobal,
flags = EngineSession.LoadUrlFlags.external()
)
}
}
"open" -> {
val url = deepLink.getQueryParameter("url")
if (url == null || !url.startsWith("https://")) {
logger.info("Not opening deep link: $url")
return
}
activity.openToBrowserAndLoad(
url,
newTab = true,
from = BrowserDirection.FromGlobal,
flags = EngineSession.LoadUrlFlags.external()
)
}
"settings_notifications" -> {
val intent = notificationSettings(activity)
activity.startActivity(intent)
}
"install_search_widget" -> {
if (SDK_INT >= Build.VERSION_CODES.O) {
SearchWidgetCreator.createSearchWidget(activity)
}
}
}
}
private fun notificationSettings(context: Context, channel: String? = null) =
Intent().apply {
when {
SDK_INT >= Build.VERSION_CODES.O -> {
action = channel?.let {
putExtra(Settings.EXTRA_CHANNEL_ID, it)
Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
} ?: Settings.ACTION_APP_NOTIFICATION_SETTINGS
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
}
SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
action = "android.settings.APP_NOTIFICATION_SETTINGS"
putExtra("app_package", context.packageName)
putExtra("app_uid", context.applicationInfo.uid)
}
else -> {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
addCategory(Intent.CATEGORY_DEFAULT)
data = Uri.parse("package:" + context.packageName)
}
}
}
/**
* Interface for a class that verifies deep links before they get handled.
*/
interface DeepLinkVerifier {
/**
* Verifies the given deep link and returns `true` for verified deep links or `false` for
* rejected deep links.
*/
fun verifyDeepLink(deepLink: Uri): Boolean
}
companion object {
private const val SETTINGS_SELECT_OPTION_KEY = ":settings:fragment_args_key"
private const val SETTINGS_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"
private const val DEFAULT_BROWSER_APP_OPTION = "default_browser"
}
}

@ -5,64 +5,12 @@
package org.mozilla.fenix.push
import android.annotation.SuppressLint
import com.google.firebase.messaging.RemoteMessage
import com.google.firebase.messaging.FirebaseMessagingService
import com.leanplum.LeanplumPushFirebaseMessagingService
import com.leanplum.LeanplumPushService
import mozilla.components.concept.push.PushService
import mozilla.components.lib.push.firebase.AbstractFirebasePushService
import mozilla.components.feature.push.AutoPushFeature
/**
* A wrapper class that only exists to delegate to [FirebaseMessagingService] instances.
*
* Implementation notes:
*
* This was a doozy...
*
* With Firebase Cloud Messaging, we've been given some tight constraints in order to get this to
* work:
* - We want to have multiple FCM message receivers for AutoPush and LeanPlum (for now), however
* there can only be one registered [FirebaseMessagingService] in the AndroidManifest.
* - The [LeanplumPushFirebaseMessagingService] does not function as expected unless it's the
* inherited service that receives the messages.
* - The [AutoPushService] is not strongly tied to being the inherited service, but the
* [AutoPushFeature] requires a reference to the push instance as a [PushService].
*
* We tried creating an empty [FirebaseMessagingService] that can hold a list of the services
* for delegating, but the [LeanplumPushFirebaseMessagingService] tries to get a reference to the
* Application Context, however,since the FCM service runs in a background process that gives a
* nullptr. Within LeanPlum, this is something that is probably provided internally.
*
* We tried to pass in an instance of the [AbstractFirebasePushService] to [FirebasePushService]
* through the constructor and delegate the implementation of a [PushService] to that, but alas,
* the service requires you to have an empty default constructor in order for the OS to do the
* initialization. For this reason, we created a singleton instance of the AutoPush instance since
* that lets us easily delegate the implementation to that, as well as make invocations when FCM
* receives new messages.
*/
class FirebasePushService : LeanplumPushFirebaseMessagingService(),
PushService by AutoPushService {
override fun onCreate() {
LeanplumPushService.setCustomizer(LeanplumNotificationCustomizer())
super.onCreate()
}
override fun onNewToken(newToken: String) {
AutoPushService.onNewToken(newToken)
super.onNewToken(newToken)
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
AutoPushService.onMessageReceived(remoteMessage)
super.onMessageReceived(remoteMessage)
}
}
import mozilla.components.lib.push.firebase.AbstractFirebasePushService
/**
* A singleton instance of the FirebasePushService needed for communicating between FCM and the
* [AutoPushFeature].
*/
@SuppressLint("MissingFirebaseInstanceTokenRefresh") // Implemented internally.
object AutoPushService : AbstractFirebasePushService()
class FirebasePushService : AbstractFirebasePushService()

@ -1,31 +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.push
import android.app.Notification
import android.os.Bundle
import androidx.core.app.NotificationCompat
import com.leanplum.LeanplumPushNotificationCustomizer
import org.mozilla.fenix.R
/**
* Notification customizer for incoming Leanplum push messages.
*/
class LeanplumNotificationCustomizer : LeanplumPushNotificationCustomizer {
override fun customize(
builder: NotificationCompat.Builder,
notificationPayload: Bundle?
) {
builder.setSmallIcon(R.drawable.ic_status_logo)
}
// Do not implement if unless we want to support 2 lines of text in the BigPicture style.
// See: https://docs.leanplum.com/docs/customize-your-push-notifications-sample-android
override fun customize(
builder: Notification.Builder?,
notificationPayload: Bundle?,
notificationStyle: Notification.Style?
) = Unit // no-op
}

@ -7,7 +7,6 @@ package org.mozilla.fenix.settings
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.leanplum.Leanplum
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar
@ -24,26 +23,6 @@ class SecretDebugSettingsFragment : PreferenceFragmentCompat() {
val store = requireComponents.core.store
requirePreference<Preference>(R.string.pref_key_leanplum_user_id).apply {
summary = Leanplum.getUserId().let {
if (it.isNullOrEmpty()) {
"No User Id"
} else {
it
}
}
}
requirePreference<Preference>(R.string.pref_key_leanplum_device_id).apply {
summary = Leanplum.getDeviceId().let {
if (it.isNullOrEmpty()) {
"No Device Id"
} else {
it
}
}
}
requirePreference<Preference>(R.string.pref_key_search_region_home).apply {
summary = store.state.search.region?.home ?: "Unknown"
}

@ -851,11 +851,6 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = true
)
var fxaSignedIn by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_fxa_signed_in),
default = false
)
var fxaHasSyncedItems by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_fxa_has_synced_items),
default = false

@ -88,7 +88,6 @@
<string name="pref_key_sync_sign_in" translatable="false">pref_key_sync_sign_in</string>
<string name="pref_key_sync_problem" translatable="false">pref_key_sync_problem</string>
<string name="pref_key_push_project_id" translatable="false">project_id</string>
<string name="pref_key_fxa_signed_in" translatable="false">pref_key_fxa_signed_in</string>
<string name="pref_key_fxa_has_synced_items" translatable="false">pref_key_fxa_has_synced_items</string>
<string name="pref_key_search_widget_installed" translatable="false">pref_key_search_widget_installed</string>
<string name="pref_key_saved_logins_sorting_strategy" translatable="false">pref_key_saved_logins_sorting_strategy</string>
@ -268,8 +267,6 @@
<!-- Secret Info Setting Keys -->
<string name="pref_key_secret_debug_info" translatable="false">pref_key_secret_debug_info</string>
<string name="pref_key_leanplum_user_id" translatable="false">pref_key_leanplum_user_id</string>
<string name="pref_key_leanplum_device_id" translatable="false">pref_key_leanplum_device_id</string>
<string name="pref_key_search_region_home" translatable="false">pref_key_search_region_home</string>
<string name="pref_key_search_region_current" translatable="false">pref_key_search_region_current</string>

@ -73,8 +73,6 @@
<!-- Secret debug info strings -->
<string name="debug_info_telemetry_title" translatable="false">Telemetry</string>
<string name="debug_info_leanplum_user_id" translatable="false">Leanplum User Id</string>
<string name="debug_info_leanplum_device_id" translatable="false">Leanplum Device Id</string>
<string name="debug_info_search_title" translatable="false">Search</string>
<string name="debug_info_region_home" translatable="false">Home region</string>
<string name="debug_info_region_current" translatable="false">Current region</string>

@ -4,17 +4,6 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:title="@string/debug_info_telemetry_title">
<Preference
android:key="@string/pref_key_leanplum_user_id"
android:title="@string/debug_info_leanplum_user_id"
android:enabled="false" />
<Preference
android:key="@string/pref_key_leanplum_device_id"
android:title="@string/debug_info_leanplum_device_id"
android:enabled="false" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/debug_info_search_title">
@ -28,5 +17,4 @@
android:enabled="false" />
</PreferenceCategory>
</PreferenceScreen>

@ -38,7 +38,6 @@ class BackgroundServicesTest {
fun setup() {
MockKAnnotations.init(this)
every { metrics.track(any()) } just Runs
every { settings.fxaSignedIn = any() } just Runs
observer = TelemetryAccountObserver(settings, metrics)
registry = ObserverRegistry<AccountObserver>().apply { register(observer) }
@ -50,7 +49,6 @@ class BackgroundServicesTest {
registry.notifyObservers { onAuthenticated(account, AuthType.Signin) }
verify { metrics.track(Event.SyncAuthSignIn) }
verify { settings.fxaSignedIn = true }
confirmVerified(metrics, settings)
}
@ -60,7 +58,6 @@ class BackgroundServicesTest {
registry.notifyObservers { onAuthenticated(account, AuthType.Signup) }
verify { metrics.track(Event.SyncAuthSignUp) }
verify { settings.fxaSignedIn = true }
confirmVerified(metrics, settings)
}
@ -70,7 +67,6 @@ class BackgroundServicesTest {
registry.notifyObservers { onAuthenticated(account, AuthType.Pairing) }
verify { metrics.track(Event.SyncAuthPaired) }
verify { settings.fxaSignedIn = true }
confirmVerified(metrics, settings)
}
@ -80,7 +76,6 @@ class BackgroundServicesTest {
registry.notifyObservers { onAuthenticated(account, AuthType.MigratedReuse) }
verify { metrics.track(Event.SyncAuthFromSharedReuse) }
verify { settings.fxaSignedIn = true }
confirmVerified(metrics, settings)
registry.notifyObservers { onAuthenticated(account, AuthType.MigratedCopy) }
@ -93,7 +88,6 @@ class BackgroundServicesTest {
registry.notifyObservers { onAuthenticated(account, AuthType.Recovered) }
verify { metrics.track(Event.SyncAuthRecovered) }
verify { settings.fxaSignedIn = true }
confirmVerified(metrics, settings)
}
@ -103,7 +97,6 @@ class BackgroundServicesTest {
registry.notifyObservers { onAuthenticated(account, AuthType.OtherExternal(null)) }
verify { metrics.track(Event.SyncAuthOtherExternal) }
verify { settings.fxaSignedIn = true }
confirmVerified(metrics, settings)
}
@ -113,7 +106,6 @@ class BackgroundServicesTest {
registry.notifyObservers { onAuthenticated(account, AuthType.OtherExternal("someAction")) }
verify { metrics.track(Event.SyncAuthOtherExternal) }
verify { settings.fxaSignedIn = true }
confirmVerified(metrics, settings)
}
@ -123,7 +115,6 @@ class BackgroundServicesTest {
registry.notifyObservers { onAuthenticated(account, AuthType.Existing) }
verify { metrics wasNot Called }
verify { settings.fxaSignedIn = true }
confirmVerified(metrics, settings)
}
@ -131,7 +122,6 @@ class BackgroundServicesTest {
fun `telemetry account observer tracks sign out event`() {
registry.notifyObservers { onLoggedOut() }
verify { metrics.track(Event.SyncAuthSignOut) }
verify { settings.fxaSignedIn = false }
confirmVerified(metrics, settings)
}
}

@ -1,302 +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.home.intent
import android.content.Intent
import android.net.Uri
import android.os.Build.VERSION_CODES.M
import android.os.Build.VERSION_CODES.N
import android.os.Build.VERSION_CODES.P
import androidx.core.net.toUri
import androidx.navigation.NavController
import io.mockk.Called
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.verify
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.engine.EngineSession
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig.DEEP_LINK_SCHEME
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.SearchWidgetCreator
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.SupportUtils
import org.robolectric.annotation.Config
@RunWith(FenixRobolectricTestRunner::class)
class DeepLinkIntentProcessorTest {
private lateinit var activity: HomeActivity
private lateinit var navController: NavController
private lateinit var out: Intent
private lateinit var processor: DeepLinkIntentProcessor
@Before
fun setup() {
activity = mockk(relaxed = true)
navController = mockk(relaxed = true)
out = mockk()
processor = DeepLinkIntentProcessor(activity, object : DeepLinkIntentProcessor.DeepLinkVerifier {
override fun verifyDeepLink(deepLink: Uri): Boolean {
return true
}
})
}
@Test
fun `do not process blank intents`() {
assertFalse(processor.process(Intent(), navController, out))
verify { activity wasNot Called }
verify { navController wasNot Called }
verify { out wasNot Called }
}
@Test
fun `return true if scheme is fenix`() {
assertTrue(processor.process(testIntent("test"), navController, out))
verify { activity wasNot Called }
verify { navController wasNot Called }
verify { out wasNot Called }
}
@Test
fun `return true if scheme is a fenix variant`() {
assertTrue(processor.process(testIntent("fenix-beta://test"), navController, out))
verify { activity wasNot Called }
verify { navController wasNot Called }
verify { out wasNot Called }
}
@Test
fun `process home deep link`() {
assertTrue(processor.process(testIntent("home"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalHome()) }
verify { out wasNot Called }
}
@Test
fun `process urls_bookmarks deep link`() {
assertTrue(processor.process(testIntent("urls_bookmarks"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalBookmarkFragment(BookmarkRoot.Root.id)) }
verify { out wasNot Called }
}
@Test
fun `process urls_history deep link`() {
assertTrue(processor.process(testIntent("urls_history"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalHistoryFragment()) }
verify { out wasNot Called }
}
@Test
fun `process home_collections deep link`() {
assertTrue(processor.process(testIntent("home_collections"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalHome()) }
verify { out wasNot Called }
}
@Test
fun `process settings deep link`() {
assertTrue(processor.process(testIntent("settings"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalSettingsFragment()) }
verify { out wasNot Called }
}
@Test
fun `process turn_on_sync deep link`() {
assertTrue(processor.process(testIntent("turn_on_sync"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalTurnOnSync()) }
verify { out wasNot Called }
}
@Test
fun `process settings_search_engine deep link`() {
assertTrue(processor.process(testIntent("settings_search_engine"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalSearchEngineFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_accessibility deep link`() {
assertTrue(processor.process(testIntent("settings_accessibility"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalAccessibilityFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_delete_browsing_data deep link`() {
assertTrue(processor.process(testIntent("settings_delete_browsing_data"), navController, out))
verify { activity wasNot Called }
verify { navController.navigate(NavGraphDirections.actionGlobalDeleteBrowsingDataFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_addon_manager deep link`() {
assertTrue(processor.process(testIntent("settings_addon_manager"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalAddonsManagementFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_logins deep link`() {
assertTrue(processor.process(testIntent("settings_logins"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalSavedLoginsAuthFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_tracking_protection deep link`() {
assertTrue(processor.process(testIntent("settings_tracking_protection"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalTrackingProtectionFragment()) }
verify { out wasNot Called }
}
@Test
fun `process settings_privacy deep link`() {
assertTrue(processor.process(testIntent("settings_privacy"), navController, out))
verify { navController.navigate(NavGraphDirections.actionGlobalSettingsFragment()) }
verify { out wasNot Called }
}
@Test
fun `process enable_private_browsing deep link`() {
assertTrue(processor.process(testIntent("enable_private_browsing"), navController, out))
verify { activity.browsingModeManager.mode = BrowsingMode.Private }
verify { navController.navigate(NavGraphDirections.actionGlobalHome()) }
verify { out wasNot Called }
}
@Test
fun `process open deep link`() {
assertTrue(processor.process(testIntent("open"), navController, out))
verify { activity wasNot Called }
verify { navController wasNot Called }
verify { out wasNot Called }
assertTrue(processor.process(testIntent("open?url=test"), navController, out))
verify { activity wasNot Called }
verify { navController wasNot Called }
verify { out wasNot Called }
assertTrue(processor.process(testIntent("open?url=https%3A%2F%2Fwww.example.org%2F"), navController, out))
verify {
activity.openToBrowserAndLoad(
"https://www.example.org/",
newTab = true,
from = BrowserDirection.FromGlobal,
flags = EngineSession.LoadUrlFlags.external()
)
}
verify { navController wasNot Called }
verify { out wasNot Called }
}
@Test
fun `process invalid open deep link`() {
val invalidProcessor = DeepLinkIntentProcessor(activity, object : DeepLinkIntentProcessor.DeepLinkVerifier {
override fun verifyDeepLink(deepLink: Uri): Boolean {
return false
}
})
assertTrue(invalidProcessor.process(testIntent("open"), navController, out))
verify { activity wasNot Called }
verify { navController wasNot Called }
verify { out wasNot Called }
assertTrue(invalidProcessor.process(testIntent("open?url=open?url=https%3A%2F%2Fwww.example.org%2F"), navController, out))
verify { activity wasNot Called }
verify { navController wasNot Called }
verify { out wasNot Called }
}
@Test
@Config(minSdk = N, maxSdk = P)
fun `process make_default_browser deep link for above API 23`() {
assertTrue(processor.process(testIntent("make_default_browser"), navController, out))
verify { activity.startActivity(any()) }
verify { navController wasNot Called }
verify { out wasNot Called }
}
@Test
@Config(maxSdk = M)
fun `process make_default_browser deep link for API 23 and below`() {
assertTrue(processor.process(testIntent("make_default_browser"), navController, out))
verify {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic(
activity,
SupportUtils.SumoTopic.SET_AS_DEFAULT_BROWSER
),
newTab = true,
from = BrowserDirection.FromGlobal,
flags = EngineSession.LoadUrlFlags.external()
)
}
verify { navController wasNot Called }
verify { out wasNot Called }
}
@Test
fun `process settings_notifications deep link`() {
assertTrue(processor.process(testIntent("settings_notifications"), navController, out))
verify { navController wasNot Called }
verify { out wasNot Called }
verify { activity.startActivity(any()) }
}
@Test
fun `process install_search_widget deep link`() {
mockkObject(SearchWidgetCreator)
every { SearchWidgetCreator.createSearchWidget(any()) } returns true
assertTrue(processor.process(testIntent("install_search_widget"), navController, out))
verify { navController wasNot Called }
verify { out wasNot Called }
verify { activity wasNot Called }
}
private fun testIntent(uri: String) = Intent("", "$DEEP_LINK_SCHEME://$uri".toUri())
}

@ -1,34 +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.push
import android.app.Notification
import androidx.core.app.NotificationCompat
import io.mockk.Called
import io.mockk.mockk
import io.mockk.verify
import org.junit.Test
import org.mozilla.fenix.R
class LeanplumNotificationCustomizerTest {
private val customizer = LeanplumNotificationCustomizer()
@Test
fun `customize adds icon`() {
val builder = mockk<NotificationCompat.Builder>(relaxed = true)
customizer.customize(builder, mockk())
verify { builder.setSmallIcon(R.drawable.ic_status_logo) }
}
@Test
fun `customize for BigPictureStyle does nothing`() {
val builder = mockk<Notification.Builder>()
customizer.customize(builder, mockk(), mockk())
verify { builder wasNot Called }
}
}

@ -161,18 +161,6 @@ allprojects {
}
}
}
maven {
name "LeanplumRepo"
if (project.hasProperty("leanplumRepo")) {
url project.property("leanplumRepo")
} else {
url "https://repo.leanplum.com"
}
content {
includeGroup("com.leanplum")
}
}
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {

@ -13,7 +13,6 @@ object Versions {
const val sentry = "1.7.10"
const val leakcanary = "2.4"
const val leanplum = "5.4.0"
const val osslicenses_plugin = "0.9.5"
const val detekt = "1.9.1"
const val jna = "5.6.0"
@ -166,9 +165,6 @@ object Deps {
const val sentry = "io.sentry:sentry-android:${Versions.sentry}"
const val leakcanary = "com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}"
const val leanplum_core = "com.leanplum:leanplum-core:${Versions.leanplum}"
const val leanplum_fcm = "com.leanplum:leanplum-fcm:${Versions.leanplum}"
const val androidx_annotation = "androidx.annotation:annotation:${Versions.androidx_annotation}"
const val androidx_biometric = "androidx.biometric:biometric:${Versions.androidx_biometric}"
const val androidx_fragment = "androidx.fragment:fragment-ktx:${Versions.androidx_fragment}"

@ -1,376 +0,0 @@
MMA (Mobile Marketing Automation)
================================
Mozilla wants to engage with users more. MMA is the project for this purpose. When a user performs a certain UI action (or set of UI actions), she will see a prompt and have a chance to interact with it. For example, if a user uses Firefox 10 times a week, but Firefox is not her default browser, we'll prompt the user the next time when she launches our app, and guide her to set us as default browser.
Mozilla is using a third party framework called "Leanplum" in order to achieve this. Leanplum is a San Francisco company, founded in 2012. We put their SDK in our codebase via Carthage.
The SDK is documented at https://www.leanplum.com/docs/android
There are four major components in Leanplum SDK.
1. Events : Events are fired when users perform certain actions.
2. User Attributes: User Attributes are set on a per-user basis, and inform us about an aspect of the user.
3. Deep Links: Actions that users can perform to interact with the Message.
4. Messages: User Interaction points that we want to engage with users that help them use Firefox better.
An Event or a series of Events plus some User Attributes may trigger a Message, and when the user acts on the Message, a Deep Link may be processed.
Data collection
=====================================================
Who will have Leanplum enabled?
======================================================
Everyone with Telemetry enabled.
Where does data sent to the Leanplum backend go?
==============================================
The Leanplum SDK is hard-coded to send data to the endpoint https://www.leanplum.com. The endpoint is
defined by ``com.leanplum.internal.Constants.API_HOST_NAME`` at https://github.com/Leanplum/Leanplum-Android-SDK/blob/master/AndroidSDKCore/src/main/java/com/leanplum/internal/Constants.java#L32
The user is identified by Leanplum using a random UUID generated by us when Leanplum is initialized for the first time. See: https://github.com/mozilla-mobile/fenix/blob/master/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt
This unique identifier is only used by Leanplum and can't be tracked back to any Firefox users.
What data is collected and sent to the Leanplum backend?
======================================================
The Leanplum SDK collects and sends the following information at various times while the SDK is in use.
Sent every time when an event is triggered:
~~~~~~~~~~~~~~~
"action" -> "track" // track: an event is tracked.
"event" -> "Launch" // Used when an event is triggered. e.g. E_Saved_Bookmark.
"info" -> "" // Used when an event is triggered. Basic context associated with the event.
"value" -> 0.0 // Used when an event is triggered. Value of that event.
"messageId" -> 5111602214338560 // Used when an event is triggered. The ID of the message.
~~~~~~~~~~~~~~~
Sent when the app starts:
~~~~~~~~~~~~~~~
"action" -> "start" // start: Leanplum SDK starts. heartbeat
"userAttributes" -> "{ // A set of key-value pairs used to describe the user.
"Focus Installed" -> true // If Focus for Android is installed.
"Klar Installed" -> true // If Klar for Android is installed.
"Fennec Installed" -> true // If Fennec for Android is installed.
}"
"appId" -> "app_6Ao...." // Leanplum App ID.
"clientKey" -> "dev_srwDUNZR...." // Leanplum client access key.
"systemName" -> "Android" // Fixed String in SDK.
"locale" -> "zh_TW" // System Locale.
"timezone" -> "Asia/Taipei" // System Timezone.
"versionName" -> "55.0a1" // Fennec version.
"systemVersion" -> "10.3.1" // System version.
"deviceModel" -> "Galaxy" // System device model.
"timezoneOffsetSeconds" -> "28800" // User timezone offset with PST.
"deviceName" -> "sdaswani-31710" // System device name.
"region" -> "(detect)" // Not used. We strip location so this is will be the default stub value in Leanplum SDK.
"city" -> "(detect)" // Same as above.
"country" -> "(detect)" // Same as above.
"location" -> "(detect)" // Same as above.
"newsfeedMessages" -> " size = 0" // Not used. New Leanplum Inbox message(Leanplum feature) count.
"includeDefaults" -> "false" // Not used. Always false.
~~~~~~~~~~~~~~~
Sent every time a session is renewed or has a state change:
~~~~~~~~~~~~~~~
"action" -> "heartbeat" // heartbeat: every 15 minutes when app is in the foreground
// pauseSession: when app goes to background
// resumeSession: when app goes to foreground
~~~~~~~~~~~~~~~
Sent for every Message:
~~~~~~~~~~~~~~~
"userId" -> "b13b3c239d01aa7c" // Set by Fennec, we use random uuid so users are anonymous to Leanplum.
"deviceId" -> "b13b3c239d01aa7c" // Same as above.
"sdkVersion" -> "2.2.2-SNAPSHOT" // Leanplum SDK version.
"devMode" -> "true" // If the SDK is in developer mode. For official builds, it's false.
"time" -> "1.497595093902E9" // System time in second.
"token" -> "nksZ5pa0R5iegC7wj...." // Token come from Leanplum backend.
~~~~~~~~~~~~~~~
Notes on what data is collected
===============================
User Identifier
---------------
Since Device ID is a random UUID, Leanplum can't map the device to any know Client ID in Fennec nor Advertising ID.
User Attributes
---------------
<table>
<tr>
<th>Key</th>
<th>Description</th>
<th>Data Review</th>
</tr>
<tr>
<td>`default_browser`</td>
<td>A string containing the name of the default browser if property of Mozilla or an empty string</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3459#issuecomment-502252010">#3459</a></td>
</tr>
<tr>
<td>`focus_installed`</td>
<td>A boolean indicating that Firefox Focus is installed</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3459#issuecomment-502252010">#3459</a></td>
</tr>
<tr>
<td>`klar_installed`</td>
<td>A boolean indicating that Firefox Klar is installed</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3459#issuecomment-502252010">#3459</a></td>
</tr>
<tr>
<td>`fennec_installed`</td>
<td>A boolean indicating that Fennec is installed</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3459#issuecomment-502252010">#3459</a></td>
</tr>
<tr>
<td>`fxa_signed_in`</td>
<td>A boolean indicating that the user is signed in to FxA</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4568#issuecomment-519159545">#4568</a></td>
</tr>
<tr>
<td>`fxa_has_synced_items`</td>
<td>A boolean indicating that the user has opted to sync at least one category of items with FxA</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4568#issuecomment-519159545">#4568</a></td>
</tr>
<tr>
<td>`search_widget_installed`</td>
<td>A boolean indicating that the user has at least one search widget placed on the home screen</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4694#issuecomment-520591275">#4694</a></td>
</tr>
<tr>
<td>`tracking_protection_enabled`</td>
<td>A boolean indicating that the user has enabled tracking protection</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/11965#issuecomment-649731798">#11965</a></td>
</tr>
<tr>
<td>`tracking_protection_setting`</td>
<td>A string indicating the level at which the user has set tracking protection. Possible values are `none`, `standard`, `strict` and `custom`</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/11965#issuecomment-649731798">#11965</a></td>
</tr>
<tr>
<td>`fenix`</td>
<td>A boolean indicating that this is a Fenix installation</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/8208">#8208</a></td>
</tr>
<tr>
<td>`installed_addons`</td>
<td>A boolean indicating that there are addons installed</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/13233">#13233</a></td>
</tr>
<tr>
<td>`enabled_addons`</td>
<td>A boolean indicating that there are addons enabled</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/13233">#13233</a></td>
</tr>
</table>
Events
-------
Most of the Leanplum events can be mapped to a single combination of Telemetry event (Event+Method+Extra).
Some events are not collected in Mozilla Telemetry. This will be addressed separately in each campaign review.
There are three elements that are used for each event. They are: event name, value(default: 0.0), and info(default: "").
Default value for event value is 0.0. Default value for event info is empty string.
Here is the list of current Events sent, which can be found here in the code base: https://github.com/mozilla-mobile/fenix/blob/master/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt
<table>
<tr>
<th>Event</th>
<th>Description</th>
<th>Data Review</th>
</tr>
<tr>
<td>`E_Opened_App_FirstRun`</td>
<td>The first launch after install</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3459#issuecomment-502252010">#3459</a></td>
</tr>
<tr>
<td>`E_Opened_App`</td>
<td>Whenever the App is launched.</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3459#issuecomment-502252010">#3459</a></td>
</tr>
<tr>
<td>`E_Interact_With_Search_URL_Area`</td>
<td>The user interacts with search url area.</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3459#issuecomment-502252010">#3459</a></td>
</tr>
<tr>
<td>`E_Opened_Bookmark`</td>
<td>The user opened a bookmark</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3632#issuecomment-505135753">#3632</a></td>
</tr>
<tr>
<td>`E_Add_Bookmark`</td>
<td>The user added a bookmark</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3632#issuecomment-505135753">#3632</a></td>
</tr>
<tr>
<td>`E_Remove_Bookmark`</td>
<td>The user removed a bookmark</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3632#issuecomment-505135753">#3632</a></td>
</tr>
<tr>
<td>`E_Collection_Created`</td>
<td>The user created a new collection</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4626#issuecomment-519691332">#4626</a></td>
</tr>
<tr>
<td>`E_Collection_Tab_Opened`</td>
<td>The user opened a tab from a previously created collection</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4626#issuecomment-519691332">#4626</a></td>
</tr>
<tr>
<td>`E_FxA_New_Signup`</td>
<td>The user completed the signup process to new FxA account</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4626#issuecomment-519691332">#4626</a></td>
</tr>
<tr>
<td>`E_Sign_In_FxA`</td>
<td>The user successfully signed in to FxA</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4626#issuecomment-519691332">#4626</a></td>
</tr>
<tr>
<td>`E_Sign_In_FxA_Fennec_to_Fenix`</td>
<td>The user successfully signed in to FxA using previously signed in Fennec account</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4626#issuecomment-519691332">#4626</a></td>
</tr>
<tr>
<td>`E_Sign_Out_FxA`</td>
<td>The user successfully signed out of FxA</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4626#issuecomment-519691332">#4626</a></td>
</tr>
<tr>
<td>`E_Cleared_Private_Data`</td>
<td>The user cleared one or many types of private data</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/4626#issuecomment-519691332">#4626</a></td>
</tr>
<tr>
<td>`E_Dismissed_Onboarding`</td>
<td>The user finished onboarding. Could be triggered by pressing "start browsing," opening settings, or invoking a search.</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/3459#issuecomment-502191109">#3459</a></td>
</tr>
<tr>
<td>`E_Fennec_To_Fenix_Migrated`</td>
<td>The user has just migrated from Fennec to Fenix.</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/8208#issuecomment-584040440">#8208</a></td>
</tr>
<tr>
<td>`E_Addon_Installed`</td>
<td>The user has installed an addon from the addon management page.</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/12136#issuecomment-651922547">#12136</a></td>
</tr>
<tr>
<td>`E_Search_Widget_Added`</td>
<td>The user has installed the search widget to their homescreen.</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/13003">#13003</a></td>
</tr>
<tr>
<td>`E_Changed_Default_To_Fenix`</td>
<td>The user has changed their default browser to Fenix while Fenix was in the background and then resumed the app.</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/13003">#13003</a></td>
</tr>
<tr>
<td>`E_Changed_ETP`</td>
<td>The user has changed their enhanched tracking protection setting.</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/13003">#13003</a></td>
</tr>
</table>
Deep links
-------
Deep links are hooks utilized by marketing to direct users to certain portions of the application through a link. They can also be invoked by other applications or even users
directly to access specific screens quickly.
Here is the list of current deep links available, which can be found here in the code base: https://github.com/mozilla-mobile/fenix/blob/master/app/src/main/AndroidManifest.xml
<table>
<tr>
<th>Deep link</th>
<th>Description</th>
</tr>
<tr>
<td>`fenix://home`</td>
<td>Opens to the Fenix home screen</td>
</tr>
<tr>
<td>`fenix://urls_bookmarks`</td>
<td>Opens to the list of the user's bookmarks at its root</td>
</tr>
<tr>
<td>`fenix://urls_history`</td>
<td>Opens to the list of pages the user has visited</td>
</tr>
<tr>
<td>`fenix://home_collections`</td>
<td>Opens to the list of collections the user has saved. It is implemented as `fenix://home`</td>
</tr>
<tr>
<td>`fenix://settings`</td>
<td>Opens to the top level settings screen</td>
</tr>
<tr>
<td>`fenix://turn_on_sync`</td>
<td>Opens to the turn on sync screen. **Only valid if the user is not signed in to FxA**</td>
</tr>
<tr>
<td>`fenix://settings_search_engine`</td>
<td>Opens to the search engine settings screen</td>
</tr>
<tr>
<td>`fenix://settings_accessibility`</td>
<td>Opens to the accessibility settings screen</td>
</tr>
<tr>
<td>`fenix://settings_delete_browsing_data`</td>
<td>Opens to the delete browsing data settings screen</td>
</tr>
<tr>
<td>`fenix://settings_addon_manager`</td>
<td>Opens to the settings page to install and manage addons</td>
</tr>
<tr>
<td>`fenix://settings_logins`</td>
<td>Opens to the Logins and passwords settings page configure how logins are treated. This is *not* the list of actual logins</td>
</tr>
<tr>
<td>`fenix://settings_tracking_protection`</td>
<td>Opens to the Enhanced Tracking Protection settings page</td>
</tr>
<tr>
<td>`fenix://settings_privacy`</td>
<td>Opens to the settings page which contains the privacy settings. Currently, this is the same as `fenix://settings`</td>
</tr>
<tr>
<td>`fenix://enable_private_browsing`</td>
<td>Opens to the Fenix home screen and enables private browsing</td>
</tr>
<tr>
<td>`fenix://open?url={DESIRED_URL}`</td>
<td>Creates a new tab, opens to the browser screen and loads the {DESIRED_URL}</td>
</tr>
<tr>
<td>`fenix://make_default_browser`</td>
<td>Opens to the Android default apps settings screen. If Android API <= 23 opens tab to support page defined in SupportUtils.SumoTopic.SET_AS_DEFAULT_BROWSER</td>
</tr>
<tr>
<td>`fenix://settings_notifications`</td>
<td>Opens to the Android notification settings screen for Fenix. **Only works on Android API >=24**</td>
</tr>
<tr>
<td>`fenix://install_search_widget`</td>
<td>Adds the search widget to the users launcher homescreen. **Only works on Android API >=26**</td>
</tr>
</table>
Messages
-----------
Messages are in-app prompts to the user from Leanplum. The user interaction of that prompt will be sent to the Leanplum backend (such as "Accept" or "Show") to track overall engagement with the Message. The Message is downloaded from Leanplum when the Leanplum SDK is initialized at App start, assuming the fulfillment criteria for the Message is met. As mentioned before, the fulfillment criteria is a set of required Events and User Attributes. The fulfillment criteria are set in the Leanplum backend.
We currently don't have any messages planned for Firefox Preview

@ -1,14 +1,11 @@
# Telemetry
Fenix uses Mozilla's telemetry service (Glean) and LeanPlum to measure feature performance and engagement.
Fenix uses Mozilla's telemetry service (Glean) to measure feature performance and engagement.
## Glean pings and metrics
By using the Glean SDK, Fenix can send the pings the SDK owns and defines, as documented [in the Glean SDK docs](https://mozilla.github.io/glean/book/user/pings/index.html).
Additional metrics or pings defined by Fenix are documented in the [Glean SDK autogenerated docs](metrics.md).
## Leanplum
See [here](https://github.com/mozilla-mobile/fenix/blob/master/docs/mma.md) for details on Leanplum usage in Firefox for Android.
## Crash reporting
See [here](https://github.com/mozilla-mobile/fenix/blob/master/docs/crash-reporting.md) for details on crash reporting in Firefox for Android.

@ -45,7 +45,6 @@ def add_shippable_secrets(config, tasks):
} for key, target_file in (
('adjust', '.adjust_token'),
('firebase', 'app/src/{}/res/values/firebase.xml'.format(gradle_build_type)),
('leanplum', '.leanplum_token'),
('sentry_dsn', '.sentry_token'),
('mls', '.mls_token'),
('nimbus_url', '.nimbus'),
@ -56,7 +55,6 @@ def add_shippable_secrets(config, tasks):
"path": target_file,
} for fake_value, target_file in (
("faketoken", ".adjust_token"),
("fake:token", ".leanplum_token"), # : is used by leanplum
("faketoken", ".mls_token"),
("https://fake@sentry.prod.mozaws.net/368", ".sentry_token"),
)])

@ -19,7 +19,7 @@ pushd $PROJECT_DIR
. taskcluster/scripts/toolchain/android-gradle-dependencies/before.sh
NEXUS_PREFIX='http://localhost:8081/nexus/content/repositories'
GRADLE_ARGS="--parallel -PgoogleRepo=$NEXUS_PREFIX/google/ -PjcenterRepo=$NEXUS_PREFIX/jcenter/ -PcentralRepo=$NEXUS_PREFIX/central/ -PleanplumRepo=$NEXUS_PREFIX/leanplum/"
GRADLE_ARGS="--parallel -PgoogleRepo=$NEXUS_PREFIX/google/ -PjcenterRepo=$NEXUS_PREFIX/jcenter/ -PcentralRepo=$NEXUS_PREFIX/central/"
# We build everything to be sure to fetch all dependencies
./gradlew $GRADLE_ARGS assemble assembleAndroidTest testClasses ktlint detekt
# Some tests may be flaky, although they still download dependencies. So we let the following

@ -23,7 +23,6 @@ mkdir -p android-gradle-dependencies /builds/worker/artifacts
cp -R ${NEXUS_WORK}/storage/jcenter android-gradle-dependencies
cp -R ${NEXUS_WORK}/storage/google android-gradle-dependencies
cp -R ${NEXUS_WORK}/storage/central android-gradle-dependencies
cp -R ${NEXUS_WORK}/storage/leanplum android-gradle-dependencies
tar cf - android-gradle-dependencies | xz > /builds/worker/artifacts/android-gradle-dependencies.tar.xz

@ -54,37 +54,6 @@
<autoBlockActive>true</autoBlockActive>
</externalConfiguration>
</repository>
<repository>
<id>leanplum</id>
<name>leanplum</name>
<providerRole>org.sonatype.nexus.proxy.repository.Repository</providerRole>
<providerHint>maven2</providerHint>
<localStatus>IN_SERVICE</localStatus>
<notFoundCacheActive>true</notFoundCacheActive>
<notFoundCacheTTL>1440</notFoundCacheTTL>
<userManaged>true</userManaged>
<exposed>true</exposed>
<browseable>true</browseable>
<writePolicy>READ_ONLY</writePolicy>
<indexable>true</indexable>
<searchable>true</searchable>
<localStorage>
<provider>file</provider>
</localStorage>
<remoteStorage>
<url>https://repo.leanplum.com/</url>
</remoteStorage>
<externalConfiguration>
<repositoryPolicy>RELEASE</repositoryPolicy>
<checksumPolicy>STRICT</checksumPolicy>
<fileTypeValidation>true</fileTypeValidation>
<downloadRemoteIndex>false</downloadRemoteIndex>
<artifactMaxAge>-1</artifactMaxAge>
<metadataMaxAge>1440</metadataMaxAge>
<itemMaxAge>1440</itemMaxAge>
<autoBlockActive>true</autoBlockActive>
</externalConfiguration>
</repository>
<repository>
<id>gradle-plugins</id>
<name>Gradle Plugins</name>

Loading…
Cancel
Save