For #27698: add set as default growth data
This commit is contained in:
parent
c59b0845a0
commit
2bccb86a9b
|
@ -1,4 +1,12 @@
|
|||
---
|
||||
growth-data:
|
||||
description: A feature measuring campaign growth data
|
||||
hasExposure: true
|
||||
exposureDescription: ""
|
||||
variables:
|
||||
enabled:
|
||||
type: boolean
|
||||
description: "If true, the feature is active"
|
||||
homescreen:
|
||||
description: The homescreen that the user goes to when they press home or new tab.
|
||||
hasExposure: true
|
||||
|
|
|
@ -377,6 +377,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
|
|||
if (settings().isMarketingTelemetryEnabled) {
|
||||
components.analytics.metrics.start(MetricServiceType.Marketing)
|
||||
}
|
||||
|
||||
components.appStore.dispatch(AppAction.MetricsInitializedAction)
|
||||
}
|
||||
|
||||
protected open fun setupLeakCanary() {
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.mozilla.fenix.HomeActivity
|
|||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ReleaseChannel
|
||||
import org.mozilla.fenix.components.metrics.AdjustMetricsService
|
||||
import org.mozilla.fenix.components.metrics.DefaultMetricsStorage
|
||||
import org.mozilla.fenix.components.metrics.GleanMetricsService
|
||||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
import org.mozilla.fenix.experiments.createNimbus
|
||||
|
@ -31,6 +32,7 @@ import org.mozilla.fenix.gleanplumb.NimbusMessagingStorage
|
|||
import org.mozilla.fenix.gleanplumb.OnDiskMessageMetadataStorage
|
||||
import org.mozilla.fenix.nimbus.FxNimbus
|
||||
import org.mozilla.fenix.perf.lazyMonitored
|
||||
import org.mozilla.fenix.utils.BrowsersCache
|
||||
import org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID
|
||||
import org.mozilla.geckoview.BuildConfig.MOZ_APP_VENDOR
|
||||
import org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION
|
||||
|
@ -119,7 +121,15 @@ class Analytics(
|
|||
MetricController.create(
|
||||
listOf(
|
||||
GleanMetricsService(context),
|
||||
AdjustMetricsService(context as Application),
|
||||
AdjustMetricsService(
|
||||
application = context as Application,
|
||||
storage = DefaultMetricsStorage(
|
||||
context = context,
|
||||
settings = context.settings(),
|
||||
checkDefaultBrowser = { BrowsersCache.all(context).isDefaultBrowser },
|
||||
),
|
||||
crashReporter = crashReporter,
|
||||
),
|
||||
),
|
||||
isDataTelemetryEnabled = { context.settings().isTelemetryEnabled },
|
||||
isMarketingDataTelemetryEnabled = { context.settings().isMarketingTelemetryEnabled },
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.mozilla.fenix.autofill.AutofillConfirmActivity
|
|||
import org.mozilla.fenix.autofill.AutofillSearchActivity
|
||||
import org.mozilla.fenix.autofill.AutofillUnlockActivity
|
||||
import org.mozilla.fenix.components.appstate.AppState
|
||||
import org.mozilla.fenix.components.metrics.MetricsMiddleware
|
||||
import org.mozilla.fenix.datastore.pocketStoriesSelectedCategoriesDataStore
|
||||
import org.mozilla.fenix.ext.asRecentTabs
|
||||
import org.mozilla.fenix.ext.components
|
||||
|
@ -207,6 +208,7 @@ class Components(private val context: Context) {
|
|||
context.pocketStoriesSelectedCategoriesDataStore,
|
||||
),
|
||||
MessagingMiddleware(messagingStorage = analytics.messagingStorage),
|
||||
MetricsMiddleware(metrics = analytics.metrics),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -191,4 +191,9 @@ sealed class AppAction : Action {
|
|||
val imageState: Wallpaper.ImageFileState,
|
||||
) : WallpaperAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the app's metrics have been initialized and startup data can be sent.
|
||||
*/
|
||||
object MetricsInitializedAction : AppAction()
|
||||
}
|
||||
|
|
|
@ -220,6 +220,7 @@ internal object AppStoreReducer {
|
|||
val wallpaperState = state.wallpaperState.copy(availableWallpapers = wallpapers)
|
||||
state.copy(wallpaperState = wallpaperState)
|
||||
}
|
||||
is AppAction.MetricsInitializedAction -> state
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,12 +10,23 @@ import android.os.Bundle
|
|||
import android.util.Log
|
||||
import com.adjust.sdk.Adjust
|
||||
import com.adjust.sdk.AdjustConfig
|
||||
import com.adjust.sdk.AdjustEvent
|
||||
import com.adjust.sdk.LogLevel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.lib.crash.CrashReporter
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
import org.mozilla.fenix.Config
|
||||
import org.mozilla.fenix.ext.settings
|
||||
|
||||
class AdjustMetricsService(private val application: Application) : MetricsService {
|
||||
class AdjustMetricsService(
|
||||
private val application: Application,
|
||||
private val storage: MetricsStorage,
|
||||
private val crashReporter: CrashReporter,
|
||||
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
) : MetricsService {
|
||||
override val type = MetricServiceType.Marketing
|
||||
|
||||
override fun start() {
|
||||
|
@ -70,9 +81,22 @@ class AdjustMetricsService(private val application: Application) : MetricsServic
|
|||
Adjust.gdprForgetMe(application.applicationContext)
|
||||
}
|
||||
|
||||
// We're not currently sending events directly to Adjust
|
||||
override fun track(event: Event) { /* noop */ }
|
||||
override fun shouldTrack(event: Event): Boolean = false
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
override fun track(event: Event) {
|
||||
CoroutineScope(dispatcher).launch {
|
||||
try {
|
||||
if (event is Event.GrowthData && storage.shouldTrack(event)) {
|
||||
Adjust.trackEvent(AdjustEvent(event.tokenName))
|
||||
storage.updateSentState(event)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
crashReporter.submitCaughtException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldTrack(event: Event): Boolean =
|
||||
event is Event.GrowthData
|
||||
|
||||
companion object {
|
||||
private const val LOGTAG = "AdjustMetricsService"
|
||||
|
|
|
@ -12,4 +12,14 @@ sealed class Event {
|
|||
|
||||
internal open val extras: Map<*, String>?
|
||||
get() = null
|
||||
|
||||
/**
|
||||
* Events related to growth campaigns.
|
||||
*/
|
||||
sealed class GrowthData(val tokenName: String) : Event() {
|
||||
/**
|
||||
* Event recording whether Firefox has been set as the default browser.
|
||||
*/
|
||||
object SetAsDefault : GrowthData("xgpcgt")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package org.mozilla.fenix.components.metrics
|
||||
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import org.mozilla.fenix.components.appstate.AppAction
|
||||
import org.mozilla.fenix.components.appstate.AppState
|
||||
|
||||
/**
|
||||
* A middleware that will map incoming actions to relevant events for [metrics].
|
||||
*/
|
||||
class MetricsMiddleware(
|
||||
private val metrics: MetricController,
|
||||
) : Middleware<AppState, AppAction> {
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<AppState, AppAction>,
|
||||
next: (AppAction) -> Unit,
|
||||
action: AppAction,
|
||||
) {
|
||||
handleAction(action)
|
||||
next(action)
|
||||
}
|
||||
|
||||
private fun handleAction(action: AppAction) = when (action) {
|
||||
is AppAction.MetricsInitializedAction -> {
|
||||
metrics.track(Event.GrowthData.SetAsDefault)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/* 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 kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.nimbus.FxNimbus
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
/**
|
||||
* Interface defining functions around persisted local state for certain metrics.
|
||||
*/
|
||||
interface MetricsStorage {
|
||||
/**
|
||||
* Determines whether an [event] should be sent based on locally-stored state.
|
||||
*/
|
||||
suspend fun shouldTrack(event: Event): Boolean
|
||||
|
||||
/**
|
||||
* Updates locally-stored state for an [event] that has just been sent.
|
||||
*/
|
||||
suspend fun updateSentState(event: Event)
|
||||
}
|
||||
|
||||
internal class DefaultMetricsStorage(
|
||||
context: Context,
|
||||
private val settings: Settings,
|
||||
private val checkDefaultBrowser: () -> Boolean,
|
||||
private val shouldSendGenerally: () -> Boolean = { shouldSendGenerally(context) },
|
||||
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
) : MetricsStorage {
|
||||
/**
|
||||
* Checks local state to see whether the [event] should be sent.
|
||||
*/
|
||||
override suspend fun shouldTrack(event: Event): Boolean =
|
||||
withContext(dispatcher) {
|
||||
shouldSendGenerally() && when (event) {
|
||||
Event.GrowthData.SetAsDefault -> {
|
||||
!settings.setAsDefaultGrowthSent && checkDefaultBrowser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateSentState(event: Event) = withContext(dispatcher) {
|
||||
when (event) {
|
||||
Event.GrowthData.SetAsDefault -> settings.setAsDefaultGrowthSent = true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val dayMillis: Long = 1000 * 60 * 60 * 24
|
||||
private const val windowStartMillis: Long = dayMillis * 2
|
||||
private const val windowEndMillis: Long = dayMillis * 28
|
||||
|
||||
fun shouldSendGenerally(context: Context): Boolean {
|
||||
val installedTime = context.packageManager
|
||||
.getPackageInfo(context.packageName, 0)
|
||||
.firstInstallTime
|
||||
val timeDifference = System.currentTimeMillis() - installedTime
|
||||
val withinWindow = timeDifference in windowStartMillis..windowEndMillis
|
||||
|
||||
return context.settings().adjustCampaignId.isNotEmpty() &&
|
||||
FxNimbus.features.growthData.value().enabled &&
|
||||
withinWindow
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1413,4 +1413,9 @@ class Settings(private val appContext: Context) : PreferencesHolder {
|
|||
HttpsOnlyMode.ENABLED
|
||||
}
|
||||
}
|
||||
|
||||
var setAsDefaultGrowthSent by booleanPreference(
|
||||
key = appContext.getPreferenceKey(R.string.pref_key_growth_set_as_default),
|
||||
default = false,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -307,4 +307,7 @@
|
|||
<string name="pref_key_history_metadata_feature" translatable="false">pref_key_history_metadata_feature</string>
|
||||
<string name="pref_key_show_unified_search" translatable="false">pref_key_show_unified_search</string>
|
||||
<string name="pref_key_custom_glean_server_url" translatable="false">pref_key_custom_glean_server_url</string>
|
||||
|
||||
<!-- Growth Data -->
|
||||
<string name="pref_key_growth_set_as_default" translatable="false">pref_key_growth_set_as_default</string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.components.metrics
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
class DefaultMetricsStorageTest {
|
||||
|
||||
private var checkDefaultBrowser = false
|
||||
private val doCheckDefaultBrowser = { checkDefaultBrowser }
|
||||
private var shouldSendGenerally = true
|
||||
private val doShouldSendGenerally = { shouldSendGenerally }
|
||||
|
||||
private val settings = mockk<Settings>()
|
||||
|
||||
private val dispatcher = StandardTestDispatcher()
|
||||
|
||||
private lateinit var storage: DefaultMetricsStorage
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
checkDefaultBrowser = false
|
||||
shouldSendGenerally = true
|
||||
storage = DefaultMetricsStorage(mockk(), settings, doCheckDefaultBrowser, doShouldSendGenerally, dispatcher)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN that events should not be generally sent WHEN event would be tracked THEN it is not`() = runTest(dispatcher) {
|
||||
shouldSendGenerally = false
|
||||
checkDefaultBrowser = true
|
||||
every { settings.setAsDefaultGrowthSent } returns false
|
||||
|
||||
val result = storage.shouldTrack(Event.GrowthData.SetAsDefault)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN set as default has not been sent and app is not default WHEN checked for sending THEN will not be sent`() = runTest(dispatcher) {
|
||||
every { settings.setAsDefaultGrowthSent } returns false
|
||||
checkDefaultBrowser = false
|
||||
|
||||
val result = storage.shouldTrack(Event.GrowthData.SetAsDefault)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN set as default has not been sent and app is default WHEN checked for sending THEN will be sent`() = runTest(dispatcher) {
|
||||
every { settings.setAsDefaultGrowthSent } returns false
|
||||
checkDefaultBrowser = true
|
||||
|
||||
val result = storage.shouldTrack(Event.GrowthData.SetAsDefault)
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN set as default has been sent and app is default WHEN checked for sending THEN will be not sent`() = runTest(dispatcher) {
|
||||
every { settings.setAsDefaultGrowthSent } returns true
|
||||
checkDefaultBrowser = true
|
||||
|
||||
val result = storage.shouldTrack(Event.GrowthData.SetAsDefault)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN set as default updated THEN settings will be updated accordingly`() = runTest(dispatcher) {
|
||||
val updateSlot = slot<Boolean>()
|
||||
every { settings.setAsDefaultGrowthSent = capture(updateSlot) } returns Unit
|
||||
|
||||
storage.updateSentState(Event.GrowthData.SetAsDefault)
|
||||
|
||||
assertTrue(updateSlot.captured)
|
||||
}
|
||||
}
|
|
@ -219,6 +219,18 @@ features:
|
|||
value:
|
||||
enabled: false
|
||||
|
||||
growth-data:
|
||||
description: A feature measuring campaign growth data
|
||||
variables:
|
||||
enabled:
|
||||
description: If true, the feature is active
|
||||
type: Boolean
|
||||
default: false
|
||||
defaults:
|
||||
- channel: release
|
||||
value:
|
||||
enabled: true
|
||||
|
||||
types:
|
||||
objects:
|
||||
MessageData:
|
||||
|
|
Loading…
Reference in New Issue