1
0
Fork 0
fenix/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt

891 lines
37 KiB
Kotlin

/* 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 mozilla.components.service.fxa.manager.SyncEnginesStorage
import mozilla.components.service.glean.Glean
import mozilla.components.service.glean.private.NoExtraKeys
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.GleanMetrics.AboutPage
import org.mozilla.fenix.GleanMetrics.Addons
import org.mozilla.fenix.GleanMetrics.AppTheme
import org.mozilla.fenix.GleanMetrics.Autoplay
import org.mozilla.fenix.GleanMetrics.BookmarksManagement
import org.mozilla.fenix.GleanMetrics.BrowserSearch
import org.mozilla.fenix.GleanMetrics.Collections
import org.mozilla.fenix.GleanMetrics.ContextMenu
import org.mozilla.fenix.GleanMetrics.ContextualHintTrackingProtection
import org.mozilla.fenix.GleanMetrics.CrashReporter
import org.mozilla.fenix.GleanMetrics.CustomTab
import org.mozilla.fenix.GleanMetrics.DownloadNotification
import org.mozilla.fenix.GleanMetrics.ErrorPage
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.FindInPage
import org.mozilla.fenix.GleanMetrics.History
import org.mozilla.fenix.GleanMetrics.LoginDialog
import org.mozilla.fenix.GleanMetrics.Logins
import org.mozilla.fenix.GleanMetrics.MediaNotification
import org.mozilla.fenix.GleanMetrics.MediaState
import org.mozilla.fenix.GleanMetrics.Metrics
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.GleanMetrics.Pings
import org.mozilla.fenix.GleanMetrics.Pocket
import org.mozilla.fenix.GleanMetrics.Preferences
import org.mozilla.fenix.GleanMetrics.PrivateBrowsingMode
import org.mozilla.fenix.GleanMetrics.PrivateBrowsingShortcut
import org.mozilla.fenix.GleanMetrics.ProgressiveWebApp
import org.mozilla.fenix.GleanMetrics.QrScanner
import org.mozilla.fenix.GleanMetrics.ReaderMode
import org.mozilla.fenix.GleanMetrics.SearchDefaultEngine
import org.mozilla.fenix.GleanMetrics.SearchShortcuts
import org.mozilla.fenix.GleanMetrics.SearchSuggestions
import org.mozilla.fenix.GleanMetrics.SearchWidget
import org.mozilla.fenix.GleanMetrics.SearchWidgetCfr
import org.mozilla.fenix.GleanMetrics.SyncAccount
import org.mozilla.fenix.GleanMetrics.SyncAuth
import org.mozilla.fenix.GleanMetrics.Tab
import org.mozilla.fenix.GleanMetrics.TabsTray
import org.mozilla.fenix.GleanMetrics.Tip
import org.mozilla.fenix.GleanMetrics.ToolbarSettings
import org.mozilla.fenix.GleanMetrics.TopSites
import org.mozilla.fenix.GleanMetrics.TrackingProtection
import org.mozilla.fenix.GleanMetrics.UserSpecifiedSearchEngines
import org.mozilla.fenix.GleanMetrics.VoiceSearch
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.BrowsersCache
private class EventWrapper<T : Enum<T>>(
private val recorder: ((Map<T, String>?) -> Unit),
private val keyMapper: ((String) -> T)? = null
) {
/**
* Converts snake_case string to camelCase.
*/
private fun String.asCamelCase(): String {
val parts = split("_")
val builder = StringBuilder()
for ((index, part) in parts.withIndex()) {
if (index == 0) {
builder.append(part)
} else {
builder.append(part[0].toUpperCase())
builder.append(part.substring(1))
}
}
return builder.toString()
}
fun track(event: Event) {
val extras = if (keyMapper != null) {
event.extras?.mapKeys { (key) ->
keyMapper.invoke(key.toString().asCamelCase())
}
} else {
null
}
this.recorder(extras)
}
}
private val Event.wrapper: EventWrapper<*>?
get() = when (this) {
is Event.OpenedApp -> EventWrapper(
{ Events.appOpened.record(it) },
{ Events.appOpenedKeys.valueOf(it) }
)
is Event.AppReceivedIntent -> EventWrapper(
{ Events.appReceivedIntent.record(it) },
{ Events.appReceivedIntentKeys.valueOf(it) }
)
is Event.AppOpenedAllSourceStartup -> EventWrapper(
{ Events.appOpenedAllStartup.record(it) },
{ Events.appOpenedAllStartupKeys.valueOf(it) }
)
is Event.SearchBarTapped -> EventWrapper(
{ Events.searchBarTapped.record(it) },
{ Events.searchBarTappedKeys.valueOf(it) }
)
is Event.EnteredUrl -> EventWrapper(
{ Events.enteredUrl.record(it) },
{ Events.enteredUrlKeys.valueOf(it) }
)
is Event.PerformedSearch -> EventWrapper(
{
Metrics.searchCount[this.eventSource.countLabel].add(1)
Events.performedSearch.record(it)
},
{ Events.performedSearchKeys.valueOf(it) }
)
is Event.SearchWithAds -> EventWrapper<NoExtraKeys>(
{
BrowserSearch.withAds[label].add(1)
}
)
is Event.SearchAdClicked -> EventWrapper<NoExtraKeys>(
{
BrowserSearch.adClicks[label].add(1)
}
)
is Event.SearchInContent -> EventWrapper<NoExtraKeys>(
{
BrowserSearch.inContent[label].add(1)
}
)
is Event.SearchShortcutSelected -> EventWrapper(
{ SearchShortcuts.selected.record(it) },
{ SearchShortcuts.selectedKeys.valueOf(it) }
)
is Event.LoginDialogPromptDisplayed -> EventWrapper<NoExtraKeys>(
{ LoginDialog.displayed.record(it) }
)
is Event.LoginDialogPromptCancelled -> EventWrapper<NoExtraKeys>(
{ LoginDialog.cancelled.record(it) }
)
is Event.LoginDialogPromptSave -> EventWrapper<NoExtraKeys>(
{ LoginDialog.saved.record(it) }
)
is Event.LoginDialogPromptNeverSave -> EventWrapper<NoExtraKeys>(
{ LoginDialog.neverSave.record(it) }
)
is Event.FindInPageOpened -> EventWrapper<NoExtraKeys>(
{ FindInPage.opened.record(it) }
)
is Event.FindInPageClosed -> EventWrapper<NoExtraKeys>(
{ FindInPage.closed.record(it) }
)
is Event.FindInPageSearchCommitted -> EventWrapper<NoExtraKeys>(
{ FindInPage.searchedPage.record(it) }
)
is Event.ContextMenuItemTapped -> EventWrapper(
{ ContextMenu.itemTapped.record(it) },
{ ContextMenu.itemTappedKeys.valueOf(it) }
)
is Event.CrashReporterOpened -> EventWrapper<NoExtraKeys>(
{ CrashReporter.opened.record(it) }
)
is Event.CrashReporterClosed -> EventWrapper(
{ CrashReporter.closed.record(it) },
{ CrashReporter.closedKeys.valueOf(it) }
)
is Event.BrowserMenuItemTapped -> EventWrapper(
{ Events.browserMenuAction.record(it) },
{ Events.browserMenuActionKeys.valueOf(it) }
)
is Event.OpenedBookmarkInNewTab -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.openInNewTab.record(it) }
)
is Event.OpenedBookmarksInNewTabs -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.openInNewTabs.record(it) }
)
is Event.OpenedBookmarkInPrivateTab -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.openInPrivateTab.record(it) }
)
is Event.OpenedBookmarksInPrivateTabs -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.openInPrivateTabs.record(it) }
)
is Event.EditedBookmark -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.edited.record(it) }
)
is Event.MovedBookmark -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.moved.record(it) }
)
is Event.RemoveBookmark -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.removed.record(it) }
)
is Event.RemoveBookmarks -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.multiRemoved.record(it) }
)
is Event.ShareBookmark -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.shared.record(it) }
)
is Event.CopyBookmark -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.copied.record(it) }
)
is Event.AddBookmarkFolder -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.folderAdd.record(it) }
)
is Event.RemoveBookmarkFolder -> EventWrapper<NoExtraKeys>(
{ BookmarksManagement.folderRemove.record(it) }
)
is Event.CustomTabsMenuOpened -> EventWrapper<NoExtraKeys>(
{ CustomTab.menu.record(it) }
)
is Event.CustomTabsActionTapped -> EventWrapper<NoExtraKeys>(
{ CustomTab.actionButton.record(it) }
)
is Event.CustomTabsClosed -> EventWrapper<NoExtraKeys>(
{ CustomTab.closed.record(it) }
)
is Event.UriOpened -> EventWrapper<NoExtraKeys>(
{ Events.totalUriCount.add(1) }
)
is Event.QRScannerOpened -> EventWrapper<NoExtraKeys>(
{ QrScanner.opened.record(it) }
)
is Event.QRScannerPromptDisplayed -> EventWrapper<NoExtraKeys>(
{ QrScanner.promptDisplayed.record(it) }
)
is Event.QRScannerNavigationAllowed -> EventWrapper<NoExtraKeys>(
{ QrScanner.navigationAllowed.record(it) }
)
is Event.QRScannerNavigationDenied -> EventWrapper<NoExtraKeys>(
{ QrScanner.navigationDenied.record(it) }
)
is Event.ErrorPageVisited -> EventWrapper(
{ ErrorPage.visitedError.record(it) },
{ ErrorPage.visitedErrorKeys.valueOf(it) }
)
is Event.SyncAuthOpened -> EventWrapper<NoExtraKeys>(
{ SyncAuth.opened.record(it) }
)
is Event.SyncAuthClosed -> EventWrapper<NoExtraKeys>(
{ SyncAuth.closed.record(it) }
)
is Event.SyncAuthUseEmail -> EventWrapper<NoExtraKeys>(
{ SyncAuth.useEmail.record(it) }
)
is Event.SyncAuthUseEmailProblem -> EventWrapper<NoExtraKeys>(
{ SyncAuth.useEmailProblem.record(it) }
)
is Event.SyncAuthSignIn -> EventWrapper<NoExtraKeys>(
{ SyncAuth.signIn.record(it) }
)
is Event.SyncAuthSignUp -> EventWrapper<NoExtraKeys>(
{ SyncAuth.signUp.record(it) }
)
is Event.SyncAuthPaired -> EventWrapper<NoExtraKeys>(
{ SyncAuth.paired.record(it) }
)
is Event.SyncAuthOtherExternal -> EventWrapper<NoExtraKeys>(
{ SyncAuth.otherExternal.record(it) }
)
is Event.SyncAuthFromShared -> EventWrapper<NoExtraKeys>(
{ SyncAuth.autoLogin.record(it) }
)
is Event.SyncAuthRecovered -> EventWrapper<NoExtraKeys>(
{ SyncAuth.recovered.record(it) }
)
is Event.SyncAuthSignOut -> EventWrapper<NoExtraKeys>(
{ SyncAuth.signOut.record(it) }
)
is Event.SyncAuthScanPairing -> EventWrapper<NoExtraKeys>(
{ SyncAuth.scanPairing.record(it) }
)
is Event.SyncAccountOpened -> EventWrapper<NoExtraKeys>(
{ SyncAccount.opened.record(it) }
)
is Event.SyncAccountClosed -> EventWrapper<NoExtraKeys>(
{ SyncAccount.closed.record(it) }
)
is Event.SyncAccountSyncNow -> EventWrapper<NoExtraKeys>(
{ SyncAccount.syncNow.record(it) }
)
is Event.SignInToSendTab -> EventWrapper<NoExtraKeys>(
{ SyncAccount.signInToSendTab.record(it) }
)
is Event.SendTab -> EventWrapper<NoExtraKeys>(
{ SyncAccount.sendTab.record(it) }
)
is Event.PreferenceToggled -> EventWrapper(
{ Events.preferenceToggled.record(it) },
{ Events.preferenceToggledKeys.valueOf(it) }
)
is Event.HistoryOpened -> EventWrapper<NoExtraKeys>(
{ History.opened.record(it) }
)
is Event.HistoryItemShared -> EventWrapper<NoExtraKeys>(
{ History.shared.record(it) }
)
is Event.HistoryItemOpened -> EventWrapper<NoExtraKeys>(
{ History.openedItem.record(it) }
)
is Event.HistoryItemRemoved -> EventWrapper<NoExtraKeys>(
{ History.removed.record(it) }
)
is Event.HistoryAllItemsRemoved -> EventWrapper<NoExtraKeys>(
{ History.removedAll.record(it) }
)
is Event.CollectionRenamed -> EventWrapper<NoExtraKeys>(
{ Collections.renamed.record(it) }
)
is Event.CollectionTabRestored -> EventWrapper<NoExtraKeys>(
{ Collections.tabRestored.record(it) }
)
is Event.CollectionAllTabsRestored -> EventWrapper<NoExtraKeys>(
{ Collections.allTabsRestored.record(it) }
)
is Event.CollectionTabRemoved -> EventWrapper<NoExtraKeys>(
{ Collections.tabRemoved.record(it) }
)
is Event.CollectionShared -> EventWrapper<NoExtraKeys>(
{ Collections.shared.record(it) }
)
is Event.CollectionRemoved -> EventWrapper<NoExtraKeys>(
{ Collections.removed.record(it) }
)
is Event.CollectionTabSelectOpened -> EventWrapper<NoExtraKeys>(
{ Collections.tabSelectOpened.record(it) }
)
is Event.ReaderModeAvailable -> EventWrapper<NoExtraKeys>(
{ ReaderMode.available.record(it) }
)
is Event.ReaderModeOpened -> EventWrapper<NoExtraKeys>(
{ ReaderMode.opened.record(it) }
)
is Event.ReaderModeClosed -> EventWrapper<NoExtraKeys>(
{ ReaderMode.closed.record(it) }
)
is Event.ReaderModeAppearanceOpened -> EventWrapper<NoExtraKeys>(
{ ReaderMode.appearance.record(it) }
)
is Event.CollectionTabLongPressed -> EventWrapper<NoExtraKeys>(
{ Collections.longPress.record(it) }
)
is Event.CollectionSaveButtonPressed -> EventWrapper(
{ Collections.saveButton.record(it) },
{ Collections.saveButtonKeys.valueOf(it) }
)
is Event.CollectionAddTabPressed -> EventWrapper<NoExtraKeys>(
{ Collections.addTabButton.record(it) }
)
is Event.CollectionRenamePressed -> EventWrapper<NoExtraKeys>(
{ Collections.renameButton.record(it) }
)
is Event.CollectionSaved -> EventWrapper(
{ Collections.saved.record(it) },
{ Collections.savedKeys.valueOf(it) }
)
is Event.CollectionTabsAdded -> EventWrapper(
{ Collections.tabsAdded.record(it) },
{ Collections.tabsAddedKeys.valueOf(it) }
)
is Event.SearchWidgetNewTabPressed -> EventWrapper<NoExtraKeys>(
{ SearchWidget.newTabButton.record(it) }
)
is Event.SearchWidgetVoiceSearchPressed -> EventWrapper<NoExtraKeys>(
{ SearchWidget.voiceButton.record(it) }
)
is Event.PrivateBrowsingGarbageIconTapped -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingMode.garbageIcon.record(it) }
)
is Event.PrivateBrowsingSnackbarUndoTapped -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingMode.snackbarUndo.record(it) }
)
is Event.PrivateBrowsingNotificationTapped -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingMode.notificationTapped.record(it) }
)
is Event.PrivateBrowsingNotificationOpenTapped -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingMode.notificationOpen.record(it) }
)
is Event.PrivateBrowsingNotificationDeleteAndOpenTapped -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingMode.notificationDelete.record(it) }
)
is Event.PrivateBrowsingCreateShortcut -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingShortcut.createShortcut.record(it) }
)
is Event.PrivateBrowsingAddShortcutCFR -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingShortcut.cfrAddShortcut.record(it) }
)
is Event.PrivateBrowsingCancelCFR -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingShortcut.cfrCancel.record(it) }
)
is Event.PrivateBrowsingPinnedShortcutPrivateTab -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingShortcut.pinnedShortcutPriv.record(it) }
)
is Event.PrivateBrowsingStaticShortcutTab -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingShortcut.staticShortcutTab.record(it) }
)
is Event.PrivateBrowsingStaticShortcutPrivateTab -> EventWrapper<NoExtraKeys>(
{ PrivateBrowsingShortcut.staticShortcutPriv.record(it) }
)
is Event.WhatsNewTapped -> EventWrapper<NoExtraKeys>(
{ Events.whatsNewTapped.record(it) }
)
is Event.TabMediaPlay -> EventWrapper<NoExtraKeys>(
{ Tab.mediaPlay.record(it) }
)
is Event.TabMediaPause -> EventWrapper<NoExtraKeys>(
{ Tab.mediaPause.record(it) }
)
is Event.MediaPlayState -> EventWrapper<NoExtraKeys>(
{ MediaState.play.record(it) }
)
is Event.MediaPauseState -> EventWrapper<NoExtraKeys>(
{ MediaState.pause.record(it) }
)
is Event.MediaStopState -> EventWrapper<NoExtraKeys>(
{ MediaState.stop.record(it) }
)
is Event.InAppNotificationDownloadOpen -> EventWrapper<NoExtraKeys>(
{ DownloadNotification.inAppOpen.record(it) }
)
is Event.InAppNotificationDownloadTryAgain -> EventWrapper<NoExtraKeys>(
{ DownloadNotification.inAppTryAgain.record(it) }
)
is Event.NotificationDownloadCancel -> EventWrapper<NoExtraKeys>(
{ DownloadNotification.cancel.record(it) }
)
is Event.NotificationDownloadOpen -> EventWrapper<NoExtraKeys>(
{ DownloadNotification.open.record(it) }
)
is Event.NotificationDownloadPause -> EventWrapper<NoExtraKeys>(
{ DownloadNotification.pause.record(it) }
)
is Event.NotificationDownloadResume -> EventWrapper<NoExtraKeys>(
{ DownloadNotification.resume.record(it) }
)
is Event.NotificationDownloadTryAgain -> EventWrapper<NoExtraKeys>(
{ DownloadNotification.tryAgain.record(it) }
)
is Event.NotificationMediaPlay -> EventWrapper<NoExtraKeys>(
{ MediaNotification.play.record(it) }
)
is Event.NotificationMediaPause -> EventWrapper<NoExtraKeys>(
{ MediaNotification.pause.record(it) }
)
is Event.TrackingProtectionTrackerList -> EventWrapper<NoExtraKeys>(
{ TrackingProtection.etpTrackerList.record(it) }
)
is Event.TrackingProtectionIconPressed -> EventWrapper<NoExtraKeys>(
{ TrackingProtection.etpShield.record(it) }
)
is Event.TrackingProtectionSettingsPanel -> EventWrapper<NoExtraKeys>(
{ TrackingProtection.panelSettings.record(it) }
)
is Event.TrackingProtectionSettings -> EventWrapper<NoExtraKeys>(
{ TrackingProtection.etpSettings.record(it) }
)
is Event.TrackingProtectionException -> EventWrapper<NoExtraKeys>(
{ TrackingProtection.exceptionAdded.record(it) }
)
is Event.TrackingProtectionSettingChanged -> EventWrapper(
{ TrackingProtection.etpSettingChanged.record(it) },
{ TrackingProtection.etpSettingChangedKeys.valueOf(it) }
)
is Event.OpenedLink -> EventWrapper(
{ Events.openedLink.record(it) },
{ Events.openedLinkKeys.valueOf(it) }
)
is Event.OpenLogins -> EventWrapper<NoExtraKeys>(
{ Logins.openLogins.record(it) }
)
is Event.OpenOneLogin -> EventWrapper<NoExtraKeys>(
{ Logins.openIndividualLogin.record(it) }
)
is Event.CopyLogin -> EventWrapper<NoExtraKeys>(
{ Logins.copyLogin.record(it) }
)
is Event.ViewLoginPassword -> EventWrapper<NoExtraKeys>(
{ Logins.viewPasswordLogin.record(it) }
)
is Event.DeleteLogin -> EventWrapper<NoExtraKeys>(
{ Logins.deleteSavedLogin.record(it) }
)
is Event.EditLogin -> EventWrapper<NoExtraKeys>(
{ Logins.openLoginEditor.record(it) }
)
is Event.EditLoginSave -> EventWrapper<NoExtraKeys>(
{ Logins.saveEditedLogin.record(it) }
)
is Event.PrivateBrowsingShowSearchSuggestions -> EventWrapper<NoExtraKeys>(
{ SearchSuggestions.enableInPrivate.record(it) }
)
is Event.ToolbarPositionChanged -> EventWrapper(
{ ToolbarSettings.changedPosition.record(it) },
{ ToolbarSettings.changedPositionKeys.valueOf(it) }
)
is Event.CustomEngineAdded -> EventWrapper<NoExtraKeys>(
{ UserSpecifiedSearchEngines.customEngineAdded.record(it) }
)
is Event.CustomEngineDeleted -> EventWrapper<NoExtraKeys>(
{ UserSpecifiedSearchEngines.customEngineDeleted.record(it) }
)
is Event.SaveLoginsSettingChanged -> EventWrapper(
{ Logins.saveLoginsSettingChanged.record(it) },
{ Logins.saveLoginsSettingChangedKeys.valueOf(it) }
)
is Event.TopSiteOpenDefault -> EventWrapper<NoExtraKeys>(
{ TopSites.openDefault.record(it) }
)
is Event.TopSiteOpenInNewTab -> EventWrapper<NoExtraKeys>(
{ TopSites.openInNewTab.record(it) }
)
is Event.TopSiteOpenInPrivateTab -> EventWrapper<NoExtraKeys>(
{ TopSites.openInPrivateTab.record(it) }
)
is Event.TopSiteRemoved -> EventWrapper<NoExtraKeys>(
{ TopSites.remove.record(it) }
)
is Event.SupportTapped -> EventWrapper<NoExtraKeys>(
{ AboutPage.supportTapped.record(it) }
)
is Event.PrivacyNoticeTapped -> EventWrapper<NoExtraKeys>(
{ AboutPage.privacyNoticeTapped.record(it) }
)
is Event.RightsTapped -> EventWrapper<NoExtraKeys>(
{ AboutPage.rightsTapped.record(it) }
)
is Event.LicensingTapped -> EventWrapper<NoExtraKeys>(
{ AboutPage.licensingTapped.record(it) }
)
is Event.LibrariesThatWeUseTapped -> EventWrapper<NoExtraKeys>(
{ AboutPage.librariesTapped.record(it) }
)
is Event.PocketTopSiteClicked -> EventWrapper<NoExtraKeys>(
{ Pocket.pocketTopSiteClicked.record(it) }
)
is Event.PocketTopSiteRemoved -> EventWrapper<NoExtraKeys>(
{ Pocket.pocketTopSiteRemoved.record(it) }
)
is Event.DarkThemeSelected -> EventWrapper(
{ AppTheme.darkThemeSelected.record(it) },
{ AppTheme.darkThemeSelectedKeys.valueOf(it) }
)
is Event.AddonsOpenInSettings -> EventWrapper<NoExtraKeys>(
{ Addons.openAddonsInSettings.record(it) }
)
is Event.AddonsOpenInToolbarMenu -> EventWrapper(
{ Addons.openAddonInToolbarMenu.record(it) },
{ Addons.openAddonInToolbarMenuKeys.valueOf(it) }
)
is Event.TipDisplayed -> EventWrapper(
{ Tip.displayed.record(it) },
{ Tip.displayedKeys.valueOf(it) }
)
is Event.TipPressed -> EventWrapper(
{ Tip.pressed.record(it) },
{ Tip.pressedKeys.valueOf(it) }
)
is Event.TipClosed -> EventWrapper(
{ Tip.closed.record(it) },
{ Tip.closedKeys.valueOf(it) }
)
is Event.VoiceSearchTapped -> EventWrapper<NoExtraKeys>(
{ VoiceSearch.tapped.record(it) }
)
is Event.SearchWidgetCFRDisplayed -> EventWrapper<NoExtraKeys>(
{ SearchWidgetCfr.displayed.record(it) }
)
is Event.SearchWidgetCFRCanceled -> EventWrapper<NoExtraKeys>(
{ SearchWidgetCfr.canceled.record(it) }
)
is Event.SearchWidgetCFRNotNowPressed -> EventWrapper<NoExtraKeys>(
{ SearchWidgetCfr.notNowPressed.record(it) }
)
is Event.SearchWidgetCFRAddWidgetPressed -> EventWrapper<NoExtraKeys>(
{ SearchWidgetCfr.addWidgetPressed.record(it) }
)
is Event.TabCounterMenuItemTapped -> EventWrapper(
{ Events.tabCounterMenuAction.record(it) },
{ Events.tabCounterMenuActionKeys.valueOf(it) }
)
is Event.OnboardingWhatsNew -> EventWrapper<NoExtraKeys>(
{ Onboarding.whatsNew.record(it) }
)
is Event.OnboardingPrivateBrowsing -> EventWrapper<NoExtraKeys>(
{ Onboarding.prefToggledPrivateBrowsing.record(it) }
)
is Event.OnboardingPrivacyNotice -> EventWrapper<NoExtraKeys>(
{ Onboarding.privacyNotice.record(it) }
)
is Event.OnboardingManualSignIn -> EventWrapper<NoExtraKeys>(
{ Onboarding.fxaManualSignin.record(it) }
)
is Event.OnboardingAutoSignIn -> EventWrapper<NoExtraKeys>(
{ Onboarding.fxaAutoSignin.record(it) }
)
is Event.OnboardingFinish -> EventWrapper<NoExtraKeys>(
{ Onboarding.finish.record(it) }
)
is Event.OnboardingTrackingProtection -> EventWrapper(
{ Onboarding.prefToggledTrackingProt.record(it) },
{ Onboarding.prefToggledTrackingProtKeys.valueOf(it) }
)
is Event.OnboardingThemePicker -> EventWrapper(
{ Onboarding.prefToggledThemePicker.record(it) },
{ Onboarding.prefToggledThemePickerKeys.valueOf(it) }
)
is Event.OnboardingToolbarPosition -> EventWrapper(
{ Onboarding.prefToggledToolbarPosition.record(it) },
{ Onboarding.prefToggledToolbarPositionKeys.valueOf(it) }
)
is Event.ContextualHintETPDisplayed -> EventWrapper<NoExtraKeys>(
{ ContextualHintTrackingProtection.display.record(it) }
)
is Event.ContextualHintETPDismissed -> EventWrapper<NoExtraKeys>(
{ ContextualHintTrackingProtection.dismiss.record(it) }
)
is Event.ContextualHintETPInsideTap -> EventWrapper<NoExtraKeys>(
{ ContextualHintTrackingProtection.insideTap.record(it) }
)
is Event.ContextualHintETPOutsideTap -> EventWrapper<NoExtraKeys>(
{ ContextualHintTrackingProtection.outsideTap.record(it) }
)
is Event.TabsTrayOpened -> EventWrapper<NoExtraKeys>(
{ TabsTray.opened.record(it) }
)
is Event.TabsTrayClosed -> EventWrapper<NoExtraKeys>(
{ TabsTray.closed.record(it) }
)
is Event.OpenedExistingTab -> EventWrapper<NoExtraKeys>(
{ TabsTray.openedExistingTab.record(it) }
)
is Event.ClosedExistingTab -> EventWrapper<NoExtraKeys>(
{ TabsTray.closedExistingTab.record(it) }
)
is Event.TabsTrayPrivateModeTapped -> EventWrapper<NoExtraKeys>(
{ TabsTray.privateModeTapped.record(it) }
)
is Event.TabsTrayNormalModeTapped -> EventWrapper<NoExtraKeys>(
{ TabsTray.normalModeTapped.record(it) }
)
is Event.NewTabTapped -> EventWrapper<NoExtraKeys>(
{ TabsTray.newTabTapped.record(it) }
)
is Event.NewPrivateTabTapped -> EventWrapper<NoExtraKeys>(
{ TabsTray.newPrivateTabTapped.record(it) }
)
is Event.TabsTrayMenuOpened -> EventWrapper<NoExtraKeys>(
{ TabsTray.menuOpened.record(it) }
)
is Event.TabsTraySaveToCollectionPressed -> EventWrapper<NoExtraKeys>(
{ TabsTray.saveToCollection.record(it) }
)
is Event.TabsTrayShareAllTabsPressed -> EventWrapper<NoExtraKeys>(
{ TabsTray.shareAllTabs.record(it) }
)
is Event.TabsTrayCloseAllTabsPressed -> EventWrapper<NoExtraKeys>(
{ TabsTray.closeAllTabs.record(it) }
)
Event.AutoPlaySettingVisited -> EventWrapper<NoExtraKeys>(
{ Autoplay.visitedSetting.record(it) }
)
is Event.AutoPlaySettingChanged -> EventWrapper(
{ Autoplay.settingChanged.record(it) },
{ Autoplay.settingChangedKeys.valueOf(it) }
)
is Event.ProgressiveWebAppOpenFromHomescreenTap -> EventWrapper<NoExtraKeys>(
{ ProgressiveWebApp.homescreenTap.record(it) }
)
is Event.ProgressiveWebAppInstallAsShortcut -> EventWrapper<NoExtraKeys>(
{ ProgressiveWebApp.installTap.record(it) }
)
is Event.ProgressiveWebAppForeground -> EventWrapper(
{ ProgressiveWebApp.foreground.record(it) },
{ ProgressiveWebApp.foregroundKeys.valueOf(it) }
)
is Event.ProgressiveWebAppBackground -> EventWrapper(
{ ProgressiveWebApp.background.record(it) },
{ ProgressiveWebApp.backgroundKeys.valueOf(it) }
)
// Don't record other events in Glean:
is Event.AddBookmark -> null
is Event.OpenedBookmark -> null
is Event.OpenedAppFirstRun -> null
is Event.InteractWithSearchURLArea -> null
is Event.ClearedPrivateData -> null
is Event.DismissedOnboarding -> null
is Event.FennecToFenixMigrated -> null
is Event.AddonInstalled -> null
is Event.SearchWidgetInstalled -> null
is Event.ChangedToDefaultBrowser -> null
}
class GleanMetricsService(private val context: Context) : MetricsService {
override val type = MetricServiceType.Data
private val logger = Logger("GleanMetricsService")
private var initialized = false
private val activationPing = ActivationPing(context)
private val installationPing = FirstSessionPing(context)
override fun start() {
logger.debug("Enabling Glean.")
// Initialization of Glean already happened in FenixApplication.
Glean.setUploadEnabled(true)
if (initialized) return
initialized = true
// The code below doesn't need to execute immediately, so we'll add them to the visual
// completeness task queue to be run later.
context.components.performance.visualCompletenessQueue.queue.runIfReadyOrQueue {
// We have to initialize Glean *on* the main thread, because it registers lifecycle
// observers. However, the activation ping must be sent *off* of the main thread,
// because it calls Google ad APIs that must be called *off* of the main thread.
// These two things actually happen in parallel, but that should be ok because Glean
// can handle events being recorded before it's initialized.
Glean.registerPings(Pings)
// setStartupMetrics is not a fast function. It does not need to be done before we can consider
// ourselves initialized. So, let's do it, well, later.
setStartupMetrics()
}
}
internal fun setStartupMetrics() {
setPreferenceMetrics()
Metrics.apply {
defaultBrowser.set(BrowsersCache.all(context).isDefaultBrowser)
MozillaProductDetector.getMozillaBrowserDefault(context)?.also {
defaultMozBrowser.set(it)
}
mozillaProducts.set(MozillaProductDetector.getInstalledMozillaProducts(context))
adjustCampaign.set(context.settings().adjustCampaignId)
adjustAdGroup.set(context.settings().adjustAdGroup)
adjustCreative.set(context.settings().adjustCreative)
adjustNetwork.set(context.settings().adjustNetwork)
searchWidgetInstalled.set(context.settings().searchWidgetInstalled)
val openTabsCount = context.settings().openTabsCount
hasOpenTabs.set(openTabsCount > 0)
if (openTabsCount > 0) {
tabsOpenCount.add(openTabsCount)
}
val topSitesSize = context.settings().topSitesSize
hasTopSites.set(topSitesSize > 0)
if (topSitesSize > 0) {
topSitesCount.add(topSitesSize)
}
toolbarPosition.set(
when (context.settings().toolbarPosition) {
ToolbarPosition.BOTTOM -> Event.ToolbarPositionChanged.Position.BOTTOM.name
ToolbarPosition.TOP -> Event.ToolbarPositionChanged.Position.TOP.name
}
)
}
SearchDefaultEngine.apply {
val defaultEngine = context
.components
.search
.searchEngineManager
.defaultSearchEngine ?: return@apply
code.set(defaultEngine.identifier)
name.set(defaultEngine.name)
submissionUrl.set(defaultEngine.buildSearchUrl(""))
}
activationPing.checkAndSend()
installationPing.checkAndSend()
}
private fun setPreferenceMetrics() {
// We purposefully make all of our preferences the string_list format to make data analysis
// simpler. While it makes things like booleans a bit more complicated, it means all our
// preferences can be analyzed with the same dashboard and compared.
Preferences.apply {
showSearchSuggestions.set(context.settings().shouldShowSearchSuggestions.toStringList())
remoteDebugging.set(context.settings().isRemoteDebuggingEnabled.toStringList())
telemetry.set(context.settings().isTelemetryEnabled.toStringList())
searchBrowsingHistory.set(context.settings().shouldShowHistorySuggestions.toStringList())
searchBookmarks.set(context.settings().shouldShowBookmarkSuggestions.toStringList())
showClipboardSuggestions.set(context.settings().shouldShowClipboardSuggestions.toStringList())
showSearchShortcuts.set(context.settings().shouldShowSearchShortcuts.toStringList())
openLinksInAPrivateTab.set(context.settings().openLinksInAPrivateTab.toStringList())
searchSuggestionsPrivate.set(context.settings().shouldShowSearchSuggestionsInPrivate.toStringList())
showVoiceSearch.set(context.settings().shouldShowVoiceSearch.toStringList())
openLinksInApp.set(context.settings().openLinksInExternalApp.toStringList())
val isLoggedIn =
context.components.backgroundServices.accountManager.accountProfile() != null
sync.set(isLoggedIn.toStringList())
val syncedItems = SyncEnginesStorage(context).getStatus().entries.filter {
it.value
}.map { it.key.nativeName }
syncItems.set(syncedItems)
val toolbarPositionSelection =
if (context.settings().shouldUseFixedTopToolbar) {
"fixed_top"
} else if (context.settings().shouldUseBottomToolbar) {
"bottom"
} else {
"top"
}
toolbarPosition.set(listOf(toolbarPositionSelection))
val etpSelection =
if (!context.settings().shouldUseTrackingProtection) {
""
} else if (context.settings().useStandardTrackingProtection) {
"standard"
} else if (context.settings().useStrictTrackingProtection) {
"strict"
} else if (context.settings().useCustomTrackingProtection) {
"custom"
} else {
""
}
trackingProtection.set(listOf(etpSelection))
val accessibilitySelection = mutableListOf<String>()
if (context.settings().switchServiceIsEnabled) { accessibilitySelection.add("switch") }
if (context.settings().touchExplorationIsEnabled) {
accessibilitySelection.add("touch exploration")
}
accessibilityServices.set(accessibilitySelection.toList())
val themeSelection =
if (context.settings().shouldUseLightTheme) {
"light"
} else if (context.settings().shouldUseDarkTheme) {
"dark"
} else if (context.settings().shouldFollowDeviceTheme) {
"system"
} else if (context.settings().shouldUseAutoBatteryTheme) {
"battery"
} else {
""
}
theme.set(listOf(themeSelection))
}
}
override fun stop() {
Glean.setUploadEnabled(false)
}
override fun track(event: Event) {
event.wrapper?.track(event)
}
override fun shouldTrack(event: Event): Boolean {
return event.wrapper != null
}
}
// Helper function for making our booleans fit into the string list formatting
fun Boolean.toStringList(): List<String> {
return listOf(this.toString())
}