Skip to content

Commit

Permalink
Additional "default browser" prompts: metrics (#5543)
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasPaczos authored Feb 5, 2025
1 parent f6a88b2 commit 37e34fb
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ open class BrowserActivity : DuckDuckGoActivity() {
is Command.OpenInNewTab -> launchNewTab(command.url)
is Command.OpenSavedSite -> currentTab?.submitQuery(command.url)
is Command.ShowSetAsDefaultBrowserDialog -> showSetAsDefaultBrowserDialog()
is Command.HideSetAsDefaultBrowserDialog -> hideSetAsDefaultBrowserDialog()
is Command.DismissSetAsDefaultBrowserDialog -> dismissSetAsDefaultBrowserDialog()
is ShowSystemDefaultAppsActivity -> showSystemDefaultAppsActivity(command.intent)
is ShowSystemDefaultBrowserDialog -> showSystemDefaultBrowserDialog(command.intent)
}
Expand Down Expand Up @@ -985,8 +985,8 @@ open class BrowserActivity : DuckDuckGoActivity() {
viewModel.onSetDefaultBrowserDialogShown()
}

override fun onDismissed() {
viewModel.onSetDefaultBrowserDismissed()
override fun onCanceled() {
viewModel.onSetDefaultBrowserDialogCanceled()
}

override fun onSetBrowserButtonClicked() {
Expand All @@ -1001,15 +1001,14 @@ open class BrowserActivity : DuckDuckGoActivity() {
setAsDefaultBrowserDialog = dialog
}

private fun hideSetAsDefaultBrowserDialog() {
private fun dismissSetAsDefaultBrowserDialog() {
setAsDefaultBrowserDialog?.dismiss()
setAsDefaultBrowserDialog = null
}

private fun showSystemDefaultAppsActivity(intent: Intent) {
try {
startDefaultAppsSystemActivityForResult.launch(intent)
viewModel.onSystemDefaultAppsActivityOpened()
} catch (ex: Exception) {
Timber.e(ex)
}
Expand Down
28 changes: 15 additions & 13 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesRemoteFeature
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.browser.BrowserViewModel.Command.HideSetAsDefaultBrowserDialog
import com.duckduckgo.app.browser.BrowserViewModel.Command.DismissSetAsDefaultBrowserDialog
import com.duckduckgo.app.browser.defaultbrowsing.DefaultBrowserDetector
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.Command.OpenMessageDialog
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.Command.OpenSystemDefaultAppsActivity
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.Command.OpenSystemDefaultBrowserDialog
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.SetAsDefaultActionTrigger
import com.duckduckgo.app.browser.omnibar.OmnibarEntryConverter
import com.duckduckgo.app.fire.DataClearer
import com.duckduckgo.app.generalsettings.showonapplaunch.ShowOnAppLaunchFeature
Expand Down Expand Up @@ -115,7 +116,7 @@ class BrowserViewModel @Inject constructor(
data class OpenInNewTab(val url: String) : Command()
data class OpenSavedSite(val url: String) : Command()
data object ShowSetAsDefaultBrowserDialog : Command()
data object HideSetAsDefaultBrowserDialog : Command()
data object DismissSetAsDefaultBrowserDialog : Command()
data class ShowSystemDefaultBrowserDialog(val intent: Intent) : Command()
data class ShowSystemDefaultAppsActivity(val intent: Intent) : Command()
}
Expand Down Expand Up @@ -173,6 +174,9 @@ class BrowserViewModel @Inject constructor(
}
}

private var lastSystemDefaultAppsTrigger: SetAsDefaultActionTrigger = SetAsDefaultActionTrigger.UNKNOWN
private var lastSystemDefaultBrowserDialogTrigger: SetAsDefaultActionTrigger = SetAsDefaultActionTrigger.UNKNOWN

init {
appEnjoymentPromptEmitter.promptType.observeForever(appEnjoymentObserver)
viewModelScope.launch {
Expand All @@ -183,10 +187,12 @@ class BrowserViewModel @Inject constructor(
}

is OpenSystemDefaultAppsActivity -> {
lastSystemDefaultAppsTrigger = it.trigger
command.value = Command.ShowSystemDefaultAppsActivity(it.intent)
}

is OpenSystemDefaultBrowserDialog -> {
lastSystemDefaultBrowserDialogTrigger = it.trigger
command.value = Command.ShowSystemDefaultBrowserDialog(it.intent)
}
}
Expand Down Expand Up @@ -381,17 +387,17 @@ class BrowserViewModel @Inject constructor(
defaultBrowserPromptsExperiment.onMessageDialogShown()
}

fun onSetDefaultBrowserDismissed() {
defaultBrowserPromptsExperiment.onMessageDialogDismissed()
fun onSetDefaultBrowserDialogCanceled() {
defaultBrowserPromptsExperiment.onMessageDialogCanceled()
}

fun onSetDefaultBrowserConfirmationButtonClicked() {
command.value = HideSetAsDefaultBrowserDialog
command.value = DismissSetAsDefaultBrowserDialog
defaultBrowserPromptsExperiment.onMessageDialogConfirmationButtonClicked()
}

fun onSetDefaultBrowserNotNowButtonClicked() {
command.value = HideSetAsDefaultBrowserDialog
command.value = DismissSetAsDefaultBrowserDialog
defaultBrowserPromptsExperiment.onMessageDialogNotNowButtonClicked()
}

Expand All @@ -400,19 +406,15 @@ class BrowserViewModel @Inject constructor(
}

fun onSystemDefaultBrowserDialogSuccess() {
defaultBrowserPromptsExperiment.onSystemDefaultBrowserDialogSuccess()
defaultBrowserPromptsExperiment.onSystemDefaultBrowserDialogSuccess(lastSystemDefaultBrowserDialogTrigger)
}

fun onSystemDefaultBrowserDialogCanceled() {
defaultBrowserPromptsExperiment.onSystemDefaultBrowserDialogCanceled()
}

fun onSystemDefaultAppsActivityOpened() {
defaultBrowserPromptsExperiment.onSystemDefaultAppsActivityOpened()
defaultBrowserPromptsExperiment.onSystemDefaultBrowserDialogCanceled(lastSystemDefaultBrowserDialogTrigger)
}

fun onSystemDefaultAppsActivityClosed() {
defaultBrowserPromptsExperiment.onSystemDefaultAppsActivityClosed()
defaultBrowserPromptsExperiment.onSystemDefaultAppsActivityClosed(lastSystemDefaultAppsTrigger)
}

fun onTabsSwiped() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,32 @@ interface DefaultBrowserPromptsExperiment {
fun onSetAsDefaultPopupMenuItemSelected()

fun onMessageDialogShown()
fun onMessageDialogDismissed()
fun onMessageDialogCanceled()
fun onMessageDialogConfirmationButtonClicked()
fun onMessageDialogNotNowButtonClicked()

fun onSystemDefaultBrowserDialogShown()
fun onSystemDefaultBrowserDialogSuccess()
fun onSystemDefaultBrowserDialogCanceled()
fun onSystemDefaultBrowserDialogSuccess(trigger: SetAsDefaultActionTrigger)
fun onSystemDefaultBrowserDialogCanceled(trigger: SetAsDefaultActionTrigger)

fun onSystemDefaultAppsActivityOpened()
fun onSystemDefaultAppsActivityClosed()
fun onSystemDefaultAppsActivityClosed(trigger: SetAsDefaultActionTrigger)

sealed class Command {
data object OpenMessageDialog : Command()
data class OpenSystemDefaultBrowserDialog(val intent: Intent) : Command()
data class OpenSystemDefaultAppsActivity(val intent: Intent) : Command()
data class OpenSystemDefaultBrowserDialog(
val intent: Intent,
val trigger: SetAsDefaultActionTrigger,
) : Command()

data class OpenSystemDefaultAppsActivity(
val intent: Intent,
val trigger: SetAsDefaultActionTrigger,
) : Command()
}

enum class SetAsDefaultActionTrigger {
DIALOG,
MENU,
UNKNOWN,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ import com.duckduckgo.app.browser.defaultbrowsing.DefaultBrowserDetector
import com.duckduckgo.app.browser.defaultbrowsing.DefaultBrowserSystemSettings
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.Command
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.Command.OpenMessageDialog
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.SetAsDefaultActionTrigger
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.SetAsDefaultActionTrigger.DIALOG
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.SetAsDefaultActionTrigger.MENU
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsExperiment.SetAsDefaultActionTrigger.UNKNOWN
import com.duckduckgo.app.browser.defaultbrowsing.prompts.DefaultBrowserPromptsFeatureToggles.AdditionalPromptsCohortName
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.CONVERTED
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.ENROLLED
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.NOT_ENROLLED
Expand All @@ -36,10 +41,13 @@ import com.duckduckgo.app.global.DefaultRoleBrowserDialog
import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver
import com.duckduckgo.app.onboarding.store.AppStage
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.app.usage.app.AppDaysUsedRepository
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.common.utils.plugins.PluginPoint
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.MetricsPixel
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.privacy.config.api.PrivacyConfigCallbackPlugin
import com.squareup.anvil.annotations.ContributesBinding
Expand Down Expand Up @@ -95,6 +103,8 @@ class DefaultBrowserPromptsExperimentImpl @Inject constructor(
private val userStageStore: UserStageStore,
private val defaultBrowserPromptsDataStore: DefaultBrowserPromptsDataStore,
private val experimentStageEvaluatorPluginPoint: PluginPoint<DefaultBrowserPromptsExperimentStageEvaluator>,
private val metrics: DefaultBrowserPromptsExperimentMetrics,
private val pixel: Pixel,
moshi: Moshi,
) : DefaultBrowserPromptsExperiment, MainProcessLifecycleObserver, PrivacyConfigCallbackPlugin {

Expand Down Expand Up @@ -249,6 +259,24 @@ class DefaultBrowserPromptsExperimentImpl @Inject constructor(
if (newExperimentStage != null) {
defaultBrowserPromptsDataStore.storeExperimentStage(newExperimentStage)

when (newExperimentStage) {
STAGE_1 -> {
metrics.getStageImpressionForStage1()?.fire()
}

STAGE_2 -> {
metrics.getStageImpressionForStage2()?.fire()
}

CONVERTED -> {
fireConversionPixels(currentExperimentStage)
}

else -> {
// no-op
}
}

val action = experimentStageEvaluatorPluginPoint.getPlugins().first { it.targetCohort == activeCohortName }.evaluate(newExperimentStage)
if (action.showMessageDialog) {
_commands.send(OpenMessageDialog)
Expand All @@ -265,20 +293,25 @@ class DefaultBrowserPromptsExperimentImpl @Inject constructor(
}

override fun onSetAsDefaultPopupMenuItemSelected() {
launchBestSelectionWindow()
fireInteractionPixel(AppPixelName.SET_AS_DEFAULT_IN_MENU_CLICK)
launchBestSelectionWindow(trigger = MENU)
}

override fun onMessageDialogShown() {
fireInteractionPixel(AppPixelName.SET_AS_DEFAULT_PROMPT_IMPRESSION)
}

override fun onMessageDialogDismissed() {
override fun onMessageDialogCanceled() {
fireInteractionPixel(AppPixelName.SET_AS_DEFAULT_PROMPT_DISMISSED)
}

override fun onMessageDialogConfirmationButtonClicked() {
launchBestSelectionWindow()
fireInteractionPixel(AppPixelName.SET_AS_DEFAULT_PROMPT_CLICK)
launchBestSelectionWindow(trigger = DIALOG)
}

override fun onMessageDialogNotNowButtonClicked() {
fireInteractionPixel(AppPixelName.SET_AS_DEFAULT_PROMPT_DISMISSED)
}

override fun onSystemDefaultBrowserDialogShown() {
Expand All @@ -287,35 +320,52 @@ class DefaultBrowserPromptsExperimentImpl @Inject constructor(
}
}

override fun onSystemDefaultBrowserDialogSuccess() {
override fun onSystemDefaultBrowserDialogSuccess(trigger: SetAsDefaultActionTrigger) {
appCoroutineScope.launch {
when (trigger) {
DIALOG -> metrics.getDefaultSetViaDialog()?.fire()
MENU -> metrics.getDefaultSetViaMenu()?.fire()
UNKNOWN -> {
Timber.e("Trigger for default browser dialog result wasn't provided.")
}
}
}
}

override fun onSystemDefaultBrowserDialogCanceled() {
override fun onSystemDefaultBrowserDialogCanceled(trigger: SetAsDefaultActionTrigger) {
if (browserSelectionWindowFallbackDeferred?.isActive == true) {
browserSelectionWindowFallbackDeferred?.cancel()
launchSystemDefaultAppsActivity()
launchSystemDefaultAppsActivity(trigger)
}
}

override fun onSystemDefaultAppsActivityOpened() {
}

override fun onSystemDefaultAppsActivityClosed() {
override fun onSystemDefaultAppsActivityClosed(trigger: SetAsDefaultActionTrigger) {
if (defaultBrowserDetector.isDefaultBrowser()) {
appCoroutineScope.launch {
when (trigger) {
DIALOG -> metrics.getDefaultSetViaDialog()?.fire()
MENU -> metrics.getDefaultSetViaMenu()?.fire()
UNKNOWN -> {
Timber.e("Trigger for default apps result wasn't provided.")
}
}
}
}
}

private fun launchBestSelectionWindow() {
private fun launchBestSelectionWindow(trigger: SetAsDefaultActionTrigger) {
val command = defaultRoleBrowserDialog.createIntent(applicationContext)?.let {
Command.OpenSystemDefaultBrowserDialog(intent = it)
Command.OpenSystemDefaultBrowserDialog(intent = it, trigger)
}
if (command != null) {
_commands.trySend(command)
} else {
launchSystemDefaultAppsActivity()
launchSystemDefaultAppsActivity(trigger)
}
}

private fun launchSystemDefaultAppsActivity() {
val command = Command.OpenSystemDefaultAppsActivity(DefaultBrowserSystemSettings.intent())
private fun launchSystemDefaultAppsActivity(trigger: SetAsDefaultActionTrigger) {
val command = Command.OpenSystemDefaultAppsActivity(DefaultBrowserSystemSettings.intent(), trigger)
_commands.trySend(command)
}

Expand Down Expand Up @@ -345,7 +395,41 @@ class DefaultBrowserPromptsExperimentImpl @Inject constructor(
return null
}

private suspend fun fireConversionPixels(currentExperimentStage: ExperimentStage) {
when (currentExperimentStage) {
STAGE_1 -> {
metrics.getDefaultSetForStage1()?.fire()
}

STAGE_2 -> {
metrics.getDefaultSetForStage2()?.fire()
}

else -> {
// no-op
}
}
}

private fun fireInteractionPixel(pixelName: AppPixelName) = appCoroutineScope.launch {
val variant = defaultBrowserPromptsFeatureToggles.defaultBrowserAdditionalPrompts202501().getCohort()?.name?.lowercase() ?: ""
val stage = defaultBrowserPromptsDataStore.experimentStage.first().toString().lowercase()
pixel.fire(
pixel = pixelName,
parameters = mapOf(
PIXEL_PARAM_KEY_VARIANT to variant,
PIXEL_PARAM_KEY_STAGE to stage,
),
)
}

private fun MetricsPixel.fire() = getPixelDefinitions().forEach {
pixel.fire(it.pixelName, it.params)
}

companion object {
const val FALLBACK_TO_DEFAULT_APPS_SCREEN_THRESHOLD_MILLIS = 500L
const val PIXEL_PARAM_KEY_VARIANT = "expVar"
const val PIXEL_PARAM_KEY_STAGE = "expStage"
}
}
Loading

0 comments on commit 37e34fb

Please sign in to comment.