[fenix] For https://github.com/mozilla-mobile/fenix/issues/11654 - Adds leanplum...
[gecko.git] / mobile / android / fenix / app / src / main / java / org / mozilla / fenix / components / metrics / LeanplumMetricsService.kt
blob99a0d4a50ab1d94331baa5d96403b14ad4eaea58
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.fenix.components.metrics
7 import android.app.Application
8 import android.util.Log
9 import com.leanplum.Leanplum
10 import com.leanplum.LeanplumActivityHelper
11 import com.leanplum.annotations.Parser
12 import com.leanplum.internal.LeanplumInternal
13 import kotlinx.coroutines.CoroutineScope
14 import kotlinx.coroutines.Dispatchers
15 import kotlinx.coroutines.Dispatchers.Main
16 import kotlinx.coroutines.Job
17 import kotlinx.coroutines.launch
18 import kotlinx.coroutines.withContext
19 import mozilla.components.support.locale.LocaleManager
20 import org.mozilla.fenix.BuildConfig
21 import org.mozilla.fenix.components.metrics.MozillaProductDetector.MozillaProducts
22 import org.mozilla.fenix.ext.settings
23 import java.util.Locale
24 import java.util.UUID.randomUUID
26 private val Event.name: String?
27     get() = when (this) {
28         is Event.AddBookmark -> "E_Add_Bookmark"
29         is Event.RemoveBookmark -> "E_Remove_Bookmark"
30         is Event.OpenedBookmark -> "E_Opened_Bookmark"
31         is Event.OpenedApp -> "E_Opened_App"
32         is Event.OpenedAppFirstRun -> "E_Opened_App_FirstRun"
33         is Event.InteractWithSearchURLArea -> "E_Interact_With_Search_URL_Area"
34         is Event.CollectionSaved -> "E_Collection_Created"
35         is Event.CollectionTabRestored -> "E_Collection_Tab_Opened"
36         is Event.SyncAuthSignIn -> "E_Sign_In_FxA"
37         is Event.SyncAuthSignOut -> "E_Sign_Out_FxA"
38         is Event.ClearedPrivateData -> "E_Cleared_Private_Data"
39         is Event.DismissedOnboarding -> "E_Dismissed_Onboarding"
40         is Event.FennecToFenixMigrated -> "E_Fennec_To_Fenix_Migrated"
41         is Event.AddonInstalled -> "E_Addon_Installed"
42         is Event.SearchWidgetInstalled -> "E_Search_Widget_Added"
43         is Event.ChangedToDefaultBrowser -> "E_Changed_Default_To_Fenix"
44         is Event.TrackingProtectionSettingChanged -> "E_Changed_ETP"
46         // Do not track other events in Leanplum
47         else -> null
48     }
50 class LeanplumMetricsService(private val application: Application) : MetricsService {
51     val scope = CoroutineScope(Dispatchers.IO)
52     var leanplumJob: Job? = null
54     data class Token(val id: String, val token: String) {
55         enum class Type { Development, Production, Invalid }
57         val type by lazy {
58             when {
59                 token.take(ProdPrefix.length) == ProdPrefix -> Type.Production
60                 token.take(DevPrefix.length) == DevPrefix -> Type.Development
61                 else -> Type.Invalid
62             }
63         }
65         companion object {
66             private const val ProdPrefix = "prod"
67             private const val DevPrefix = "dev"
68         }
69     }
71     override val type = MetricServiceType.Marketing
72     private val token = Token(LeanplumId, LeanplumToken)
74     override fun start() {
76         if (!application.settings().isMarketingTelemetryEnabled) return
78         Leanplum.setIsTestModeEnabled(false)
79         Leanplum.setApplicationContext(application)
80         Leanplum.setDeviceId(randomUUID().toString())
81         Parser.parseVariables(application)
83         leanplumJob = scope.launch {
85             val applicationSetLocale = LocaleManager.getCurrentLocale(application)
86             val currentLocale = when (applicationSetLocale != null) {
87                 true -> applicationSetLocale.isO3Language
88                 false -> Locale.getDefault().isO3Language
89             }
90             if (!isLeanplumEnabled(currentLocale)) {
91                 Log.i(LOGTAG, "Leanplum is not available for this locale: $currentLocale")
92                 return@launch
93             }
95             when (token.type) {
96                 Token.Type.Production -> Leanplum.setAppIdForProductionMode(token.id, token.token)
97                 Token.Type.Development -> Leanplum.setAppIdForDevelopmentMode(token.id, token.token)
98                 Token.Type.Invalid -> {
99                     Log.i(LOGTAG, "Invalid or missing Leanplum token")
100                     return@launch
101                 }
102             }
104             LeanplumActivityHelper.enableLifecycleCallbacks(application)
106             val installedApps = MozillaProductDetector.getInstalledMozillaProducts(application)
108             val trackingProtection = application.settings().run {
109                 when {
110                     !shouldUseTrackingProtection -> "none"
111                     useStandardTrackingProtection -> "standard"
112                     useStrictTrackingProtection -> "strict"
113                     else -> "custom"
114                 }
115             }
117             Leanplum.start(application, hashMapOf(
118                 "default_browser" to MozillaProductDetector.getMozillaBrowserDefault(application).orEmpty(),
119                 "fennec_installed" to installedApps.contains(MozillaProducts.FIREFOX.productName),
120                 "focus_installed" to installedApps.contains(MozillaProducts.FOCUS.productName),
121                 "klar_installed" to installedApps.contains(MozillaProducts.KLAR.productName),
122                 "fxa_signed_in" to application.settings().fxaSignedIn,
123                 "fxa_has_synced_items" to application.settings().fxaHasSyncedItems,
124                 "search_widget_installed" to application.settings().searchWidgetInstalled,
125                 "tracking_protection_enabled" to application.settings().shouldUseTrackingProtection,
126                 "tracking_protection_setting" to trackingProtection,
127                 "fenix" to true
128             ))
130             withContext(Main) {
131                 LeanplumInternal.setCalledStart(true)
132                 LeanplumInternal.setHasStarted(true)
133                 LeanplumInternal.setStartedInBackground(true)
134             }
135         }
136     }
138     override fun stop() {
139         if (application.settings().isMarketingTelemetryEnabled) return
140         // As written in LeanPlum SDK documentation, "This prevents Leanplum from communicating with the server."
141         // as this "isTestMode" flag is checked before LeanPlum SDK does anything.
142         // Also has the benefit effect of blocking the display of already downloaded messages.
143         // The reverse of this - setIsTestModeEnabled(false) must be called before trying to init
144         // LP in the same session.
145         Leanplum.setIsTestModeEnabled(true)
147         // This is just to allow restarting LP and it's functionality in the same app session
148         // as LP stores it's state internally and check against it
149         LeanplumInternal.setCalledStart(false)
150         LeanplumInternal.setHasStarted(false)
151         leanplumJob?.cancel()
152     }
154     override fun track(event: Event) {
155         val leanplumExtras = event.extras
156             ?.map { (key, value) -> key.toString() to value }
157             ?.toMap()
159         event.name?.also {
160             Leanplum.track(it, leanplumExtras)
161         }
162     }
164     override fun shouldTrack(event: Event): Boolean {
165         return application.settings().isTelemetryEnabled &&
166                 token.type != Token.Type.Invalid && !event.name.isNullOrEmpty()
167     }
169     private fun isLeanplumEnabled(locale: String): Boolean {
170         return LEANPLUM_ENABLED_LOCALES.contains(locale)
171     }
173     companion object {
174         private const val LOGTAG = "LeanplumMetricsService"
176         private val LeanplumId: String
177             // Debug builds have a null (nullable) LEANPLUM_ID
178             get() = BuildConfig.LEANPLUM_ID.orEmpty()
179         private val LeanplumToken: String
180             // Debug builds have a null (nullable) LEANPLUM_TOKEN
181             get() = BuildConfig.LEANPLUM_TOKEN.orEmpty()
182         // Leanplum needs to be enabled for the following locales.
183         // Irrespective of the actual device location.
184         private val LEANPLUM_ENABLED_LOCALES = listOf(
185             "eng", // English
186             "zho", // Chinese
187             "deu", // German
188             "fra", // French
189             "ita", // Italian
190             "ind", // Indonesian
191             "por", // Portuguese
192             "spa", // Spanish; Castilian
193             "pol", // Polish
194             "rus", // Russian
195             "hin", // Hindi
196             "per", // Persian
197             "fas", // Persian
198             "ara", // Arabic
199             "jpn" // Japanese
200         )
201     }