/* 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.browser.state.store.BrowserStore import mozilla.components.feature.search.ext.legacy import mozilla.components.feature.search.ext.waitForSelectedOrDefaultSearchEngine 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.AndroidKeystoreExperiment import org.mozilla.fenix.GleanMetrics.AppTheme import org.mozilla.fenix.GleanMetrics.Autoplay import org.mozilla.fenix.GleanMetrics.Awesomebar import org.mozilla.fenix.GleanMetrics.BannerOpenInApp 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.ContextualMenu import org.mozilla.fenix.GleanMetrics.CrashReporter import org.mozilla.fenix.GleanMetrics.CustomTab import org.mozilla.fenix.GleanMetrics.DownloadNotification import org.mozilla.fenix.GleanMetrics.DownloadsMisc import org.mozilla.fenix.GleanMetrics.DownloadsManagement import org.mozilla.fenix.GleanMetrics.ErrorPage import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.ExperimentsDefaultBrowser import org.mozilla.fenix.GleanMetrics.FindInPage import org.mozilla.fenix.GleanMetrics.History import org.mozilla.fenix.GleanMetrics.HomeMenu import org.mozilla.fenix.GleanMetrics.HomeScreen import org.mozilla.fenix.GleanMetrics.LoginDialog import org.mozilla.fenix.GleanMetrics.Logins import org.mozilla.fenix.GleanMetrics.MasterPassword 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.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.SetDefaultNewtabExperiment import org.mozilla.fenix.GleanMetrics.SetDefaultSettingExperiment import org.mozilla.fenix.GleanMetrics.SyncAccount import org.mozilla.fenix.GleanMetrics.SyncAuth import org.mozilla.fenix.GleanMetrics.SyncedTabs import org.mozilla.fenix.GleanMetrics.Tab import org.mozilla.fenix.GleanMetrics.Tabs import org.mozilla.fenix.GleanMetrics.TabsTray import org.mozilla.fenix.GleanMetrics.TabsTrayCfr 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 import org.mozilla.fenix.utils.Settings private class EventWrapper>( private val recorder: ((Map?) -> 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.AppAllStartup -> 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( { BrowserSearch.withAds[label].add(1) } ) is Event.SearchAdClicked -> EventWrapper( { BrowserSearch.adClicks[label].add(1) } ) is Event.SearchInContent -> EventWrapper( { BrowserSearch.inContent[label].add(1) } ) is Event.SearchShortcutSelected -> EventWrapper( { SearchShortcuts.selected.record(it) }, { SearchShortcuts.selectedKeys.valueOf(it) } ) is Event.LoginDialogPromptDisplayed -> EventWrapper( { LoginDialog.displayed.record(it) } ) is Event.LoginDialogPromptCancelled -> EventWrapper( { LoginDialog.cancelled.record(it) } ) is Event.LoginDialogPromptSave -> EventWrapper( { LoginDialog.saved.record(it) } ) is Event.LoginDialogPromptNeverSave -> EventWrapper( { LoginDialog.neverSave.record(it) } ) is Event.FindInPageOpened -> EventWrapper( { FindInPage.opened.record(it) } ) is Event.FindInPageClosed -> EventWrapper( { FindInPage.closed.record(it) } ) is Event.FindInPageSearchCommitted -> EventWrapper( { FindInPage.searchedPage.record(it) } ) is Event.ContextMenuItemTapped -> EventWrapper( { ContextMenu.itemTapped.record(it) }, { ContextMenu.itemTappedKeys.valueOf(it) } ) is Event.CrashReporterOpened -> EventWrapper( { 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.SetDefaultBrowserToolbarMenuClicked -> EventWrapper( { ExperimentsDefaultBrowser.toolbarMenuClicked.record(it) } ) is Event.ToolbarMenuShown -> EventWrapper( { Events.toolbarMenuVisible.record(it) } ) is Event.ChangedToDefaultBrowser -> EventWrapper( { Events.defaultBrowserChanged.record(it) } ) is Event.OpenedBookmark -> EventWrapper( { BookmarksManagement.open.record(it) } ) is Event.OpenedBookmarkInNewTab -> EventWrapper( { BookmarksManagement.openInNewTab.record(it) } ) is Event.OpenedBookmarksInNewTabs -> EventWrapper( { BookmarksManagement.openInNewTabs.record(it) } ) is Event.OpenedBookmarkInPrivateTab -> EventWrapper( { BookmarksManagement.openInPrivateTab.record(it) } ) is Event.OpenedBookmarksInPrivateTabs -> EventWrapper( { BookmarksManagement.openInPrivateTabs.record(it) } ) is Event.EditedBookmark -> EventWrapper( { BookmarksManagement.edited.record(it) } ) is Event.MovedBookmark -> EventWrapper( { BookmarksManagement.moved.record(it) } ) is Event.RemoveBookmark -> EventWrapper( { BookmarksManagement.removed.record(it) } ) is Event.RemoveBookmarks -> EventWrapper( { BookmarksManagement.multiRemoved.record(it) } ) is Event.ShareBookmark -> EventWrapper( { BookmarksManagement.shared.record(it) } ) is Event.CopyBookmark -> EventWrapper( { BookmarksManagement.copied.record(it) } ) is Event.AddBookmarkFolder -> EventWrapper( { BookmarksManagement.folderAdd.record(it) } ) is Event.RemoveBookmarkFolder -> EventWrapper( { BookmarksManagement.folderRemove.record(it) } ) is Event.CustomTabsMenuOpened -> EventWrapper( { CustomTab.menu.record(it) } ) is Event.CustomTabsActionTapped -> EventWrapper( { CustomTab.actionButton.record(it) } ) is Event.CustomTabsClosed -> EventWrapper( { CustomTab.closed.record(it) } ) is Event.UriOpened -> EventWrapper( { Events.totalUriCount.add(1) } ) is Event.NormalAndPrivateUriOpened -> EventWrapper( { Events.normalAndPrivateUriCount.add(1) } ) is Event.ErrorPageVisited -> EventWrapper( { ErrorPage.visitedError.record(it) }, { ErrorPage.visitedErrorKeys.valueOf(it) } ) is Event.SyncAuthOpened -> EventWrapper( { SyncAuth.opened.record(it) } ) is Event.SyncAuthClosed -> EventWrapper( { SyncAuth.closed.record(it) } ) is Event.SyncAuthUseEmail -> EventWrapper( { SyncAuth.useEmail.record(it) } ) is Event.SyncAuthUseEmailProblem -> EventWrapper( { SyncAuth.useEmailProblem.record(it) } ) is Event.SyncAuthSignIn -> EventWrapper( { SyncAuth.signIn.record(it) } ) is Event.SyncAuthSignUp -> EventWrapper( { SyncAuth.signUp.record(it) } ) is Event.SyncAuthPaired -> EventWrapper( { SyncAuth.paired.record(it) } ) is Event.SyncAuthOtherExternal -> EventWrapper( { SyncAuth.otherExternal.record(it) } ) is Event.SyncAuthRecovered -> EventWrapper( { SyncAuth.recovered.record(it) } ) is Event.SyncAuthSignOut -> EventWrapper( { SyncAuth.signOut.record(it) } ) is Event.SyncAuthScanPairing -> EventWrapper( { SyncAuth.scanPairing.record(it) } ) is Event.SyncAccountOpened -> EventWrapper( { SyncAccount.opened.record(it) } ) is Event.SyncAccountSyncNow -> EventWrapper( { SyncAccount.syncNow.record(it) } ) is Event.SignInToSendTab -> EventWrapper( { SyncAccount.signInToSendTab.record(it) } ) is Event.SendTab -> EventWrapper( { SyncAccount.sendTab.record(it) } ) is Event.PreferenceToggled -> EventWrapper( { Events.preferenceToggled.record(it) }, { Events.preferenceToggledKeys.valueOf(it) } ) is Event.HistoryOpened -> EventWrapper( { History.opened.record(it) } ) is Event.HistoryItemShared -> EventWrapper( { History.shared.record(it) } ) is Event.HistoryItemOpened -> EventWrapper( { History.openedItem.record(it) } ) is Event.HistoryOpenedInNewTab -> EventWrapper( { History.openedItemInNewTab.record(it) } ) is Event.HistoryOpenedInNewTabs -> EventWrapper( { History.openedItemsInNewTabs.record(it) } ) is Event.HistoryOpenedInPrivateTab -> EventWrapper( { History.openedItemInPrivateTab.record(it) } ) is Event.HistoryOpenedInPrivateTabs -> EventWrapper( { History.openedItemsInPrivateTabs.record(it) } ) is Event.HistoryItemRemoved -> EventWrapper( { History.removed.record(it) } ) is Event.HistoryAllItemsRemoved -> EventWrapper( { History.removedAll.record(it) } ) is Event.CollectionRenamed -> EventWrapper( { Collections.renamed.record(it) } ) is Event.CollectionTabRestored -> EventWrapper( { Collections.tabRestored.record(it) } ) is Event.CollectionAllTabsRestored -> EventWrapper( { Collections.allTabsRestored.record(it) } ) is Event.CollectionTabRemoved -> EventWrapper( { Collections.tabRemoved.record(it) } ) is Event.CollectionShared -> EventWrapper( { Collections.shared.record(it) } ) is Event.CollectionRemoved -> EventWrapper( { Collections.removed.record(it) } ) is Event.CollectionTabSelectOpened -> EventWrapper( { Collections.tabSelectOpened.record(it) } ) is Event.ReaderModeAvailable -> EventWrapper( { ReaderMode.available.record(it) } ) is Event.ReaderModeOpened -> EventWrapper( { ReaderMode.opened.record(it) } ) is Event.ReaderModeClosed -> EventWrapper( { ReaderMode.closed.record(it) } ) is Event.ReaderModeAppearanceOpened -> EventWrapper( { ReaderMode.appearance.record(it) } ) is Event.CollectionTabLongPressed -> EventWrapper( { Collections.longPress.record(it) } ) is Event.CollectionSaveButtonPressed -> EventWrapper( { Collections.saveButton.record(it) }, { Collections.saveButtonKeys.valueOf(it) } ) is Event.CollectionAddTabPressed -> EventWrapper( { Collections.addTabButton.record(it) } ) is Event.CollectionRenamePressed -> EventWrapper( { 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( { SearchWidget.newTabButton.record(it) } ) is Event.SearchWidgetVoiceSearchPressed -> EventWrapper( { SearchWidget.voiceButton.record(it) } ) is Event.PrivateBrowsingSnackbarUndoTapped -> EventWrapper( { PrivateBrowsingMode.snackbarUndo.record(it) } ) is Event.PrivateBrowsingNotificationTapped -> EventWrapper( { PrivateBrowsingMode.notificationTapped.record(it) } ) is Event.PrivateBrowsingCreateShortcut -> EventWrapper( { PrivateBrowsingShortcut.createShortcut.record(it) } ) is Event.PrivateBrowsingAddShortcutCFR -> EventWrapper( { PrivateBrowsingShortcut.cfrAddShortcut.record(it) } ) is Event.PrivateBrowsingCancelCFR -> EventWrapper( { PrivateBrowsingShortcut.cfrCancel.record(it) } ) is Event.PrivateBrowsingPinnedShortcutPrivateTab -> EventWrapper( { PrivateBrowsingShortcut.pinnedShortcutPriv.record(it) } ) is Event.PrivateBrowsingStaticShortcutTab -> EventWrapper( { PrivateBrowsingShortcut.staticShortcutTab.record(it) } ) is Event.PrivateBrowsingStaticShortcutPrivateTab -> EventWrapper( { PrivateBrowsingShortcut.staticShortcutPriv.record(it) } ) is Event.WhatsNewTapped -> EventWrapper( { Events.whatsNewTapped.record(it) } ) is Event.TabMediaPlay -> EventWrapper( { Tab.mediaPlay.record(it) } ) is Event.TabMediaPause -> EventWrapper( { Tab.mediaPause.record(it) } ) is Event.MediaPlayState -> EventWrapper( { MediaState.play.record(it) } ) is Event.MediaPauseState -> EventWrapper( { MediaState.pause.record(it) } ) is Event.MediaStopState -> EventWrapper( { MediaState.stop.record(it) } ) is Event.MediaFullscreenState -> EventWrapper( { MediaState.fullscreen.record(it) } ) is Event.MediaPictureInPictureState -> EventWrapper( { MediaState.pictureInPicture.record(it) } ) is Event.InAppNotificationDownloadOpen -> EventWrapper( { DownloadNotification.inAppOpen.record(it) } ) is Event.InAppNotificationDownloadTryAgain -> EventWrapper( { DownloadNotification.inAppTryAgain.record(it) } ) is Event.NotificationDownloadCancel -> EventWrapper( { DownloadNotification.cancel.record(it) } ) is Event.NotificationDownloadOpen -> EventWrapper( { DownloadNotification.open.record(it) } ) is Event.NotificationDownloadPause -> EventWrapper( { DownloadNotification.pause.record(it) } ) is Event.NotificationDownloadResume -> EventWrapper( { DownloadNotification.resume.record(it) } ) is Event.NotificationDownloadTryAgain -> EventWrapper( { DownloadNotification.tryAgain.record(it) } ) is Event.DownloadAdded -> EventWrapper( { DownloadsMisc.downloadAdded.record(it) } ) is Event.DownloadsScreenOpened -> EventWrapper( { DownloadsManagement.downloadsScreenOpened.record(it) } ) is Event.DownloadsItemOpened -> EventWrapper( { DownloadsManagement.itemOpened.record(it) } ) is Event.DownloadsItemDeleted -> EventWrapper( { DownloadsManagement.itemDeleted.record(it) } ) is Event.NotificationMediaPlay -> EventWrapper( { MediaNotification.play.record(it) } ) is Event.NotificationMediaPause -> EventWrapper( { MediaNotification.pause.record(it) } ) is Event.TrackingProtectionTrackerList -> EventWrapper( { TrackingProtection.etpTrackerList.record(it) } ) is Event.TrackingProtectionIconPressed -> EventWrapper( { TrackingProtection.etpShield.record(it) } ) is Event.TrackingProtectionSettingsPanel -> EventWrapper( { TrackingProtection.panelSettings.record(it) } ) is Event.TrackingProtectionSettings -> EventWrapper( { TrackingProtection.etpSettings.record(it) } ) is Event.TrackingProtectionException -> EventWrapper( { 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( { Logins.openLogins.record(it) } ) is Event.OpenOneLogin -> EventWrapper( { Logins.openIndividualLogin.record(it) } ) is Event.CopyLogin -> EventWrapper( { Logins.copyLogin.record(it) } ) is Event.ViewLoginPassword -> EventWrapper( { Logins.viewPasswordLogin.record(it) } ) is Event.DeleteLogin -> EventWrapper( { Logins.deleteSavedLogin.record(it) } ) is Event.EditLogin -> EventWrapper( { Logins.openLoginEditor.record(it) } ) is Event.EditLoginSave -> EventWrapper( { Logins.saveEditedLogin.record(it) } ) is Event.PrivateBrowsingShowSearchSuggestions -> EventWrapper( { SearchSuggestions.enableInPrivate.record(it) } ) is Event.ToolbarPositionChanged -> EventWrapper( { ToolbarSettings.changedPosition.record(it) }, { ToolbarSettings.changedPositionKeys.valueOf(it) } ) is Event.CustomEngineAdded -> EventWrapper( { UserSpecifiedSearchEngines.customEngineAdded.record(it) } ) is Event.CustomEngineDeleted -> EventWrapper( { UserSpecifiedSearchEngines.customEngineDeleted.record(it) } ) is Event.SaveLoginsSettingChanged -> EventWrapper( { Logins.saveLoginsSettingChanged.record(it) }, { Logins.saveLoginsSettingChangedKeys.valueOf(it) } ) is Event.TopSiteOpenDefault -> EventWrapper( { TopSites.openDefault.record(it) } ) is Event.TopSiteOpenGoogle -> EventWrapper( { TopSites.openGoogleSearchAttribution.record(it) } ) is Event.TopSiteOpenFrecent -> EventWrapper( { TopSites.openFrecency.record(it) } ) is Event.TopSiteOpenPinned -> EventWrapper( { TopSites.openPinned.record(it) } ) is Event.TopSiteOpenInNewTab -> EventWrapper( { TopSites.openInNewTab.record(it) } ) is Event.TopSiteOpenInPrivateTab -> EventWrapper( { TopSites.openInPrivateTab.record(it) } ) is Event.TopSiteRemoved -> EventWrapper( { TopSites.remove.record(it) } ) is Event.TopSiteLongPress -> EventWrapper( { TopSites.longPress.record(it) }, { TopSites.longPressKeys.valueOf(it) } ) is Event.TopSiteSwipeCarousel -> EventWrapper( { TopSites.swipeCarousel.record(it) }, { TopSites.swipeCarouselKeys.valueOf(it) } ) is Event.SupportTapped -> EventWrapper( { AboutPage.supportTapped.record(it) } ) is Event.PrivacyNoticeTapped -> EventWrapper( { AboutPage.privacyNoticeTapped.record(it) } ) is Event.PocketTopSiteClicked -> EventWrapper( { Pocket.pocketTopSiteClicked.record(it) } ) is Event.PocketTopSiteRemoved -> EventWrapper( { Pocket.pocketTopSiteRemoved.record(it) } ) is Event.DarkThemeSelected -> EventWrapper( { AppTheme.darkThemeSelected.record(it) }, { AppTheme.darkThemeSelectedKeys.valueOf(it) } ) is Event.AddonsOpenInSettings -> EventWrapper( { Addons.openAddonsInSettings.record(it) } ) is Event.AddonsOpenInToolbarMenu -> EventWrapper( { Addons.openAddonInToolbarMenu.record(it) }, { Addons.openAddonInToolbarMenuKeys.valueOf(it) } ) is Event.AddonOpenSetting -> EventWrapper( { Addons.openAddonSetting.record(it) }, { Addons.openAddonSettingKeys.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( { VoiceSearch.tapped.record(it) } ) is Event.TabCounterMenuItemTapped -> EventWrapper( { Events.tabCounterMenuAction.record(it) }, { Events.tabCounterMenuActionKeys.valueOf(it) } ) is Event.OnboardingPrivateBrowsing -> EventWrapper( { Onboarding.prefToggledPrivateBrowsing.record(it) } ) is Event.OnboardingPrivacyNotice -> EventWrapper( { Onboarding.privacyNotice.record(it) } ) is Event.OnboardingManualSignIn -> EventWrapper( { Onboarding.fxaManualSignin.record(it) } ) is Event.OnboardingAutoSignIn -> EventWrapper( { Onboarding.fxaAutoSignin.record(it) } ) is Event.OnboardingFinish -> EventWrapper( { 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( { ContextualHintTrackingProtection.display.record(it) } ) is Event.ContextualHintETPDismissed -> EventWrapper( { ContextualHintTrackingProtection.dismiss.record(it) } ) is Event.ContextualHintETPInsideTap -> EventWrapper( { ContextualHintTrackingProtection.insideTap.record(it) } ) is Event.ContextualHintETPOutsideTap -> EventWrapper( { ContextualHintTrackingProtection.outsideTap.record(it) } ) is Event.TabsTrayOpened -> EventWrapper( { TabsTray.opened.record(it) } ) is Event.TabsTrayClosed -> EventWrapper( { TabsTray.closed.record(it) } ) is Event.OpenedExistingTab -> EventWrapper( { TabsTray.openedExistingTab.record(it) } ) is Event.ClosedExistingTab -> EventWrapper( { TabsTray.closedExistingTab.record(it) } ) is Event.TabsTrayPrivateModeTapped -> EventWrapper( { TabsTray.privateModeTapped.record(it) } ) is Event.TabsTrayNormalModeTapped -> EventWrapper( { TabsTray.normalModeTapped.record(it) } ) is Event.TabsTraySyncedModeTapped -> EventWrapper( { TabsTray.syncedModeTapped.record(it) } ) is Event.NewTabTapped -> EventWrapper( { TabsTray.newTabTapped.record(it) } ) is Event.NewPrivateTabTapped -> EventWrapper( { TabsTray.newPrivateTabTapped.record(it) } ) is Event.TabsTrayMenuOpened -> EventWrapper( { TabsTray.menuOpened.record(it) } ) is Event.TabsTraySaveToCollectionPressed -> EventWrapper( { TabsTray.saveToCollection.record(it) } ) is Event.TabsTrayShareAllTabsPressed -> EventWrapper( { TabsTray.shareAllTabs.record(it) } ) is Event.TabsTrayCloseAllTabsPressed -> EventWrapper( { TabsTray.closeAllTabs.record(it) } ) is Event.TabsTrayCfrDismissed -> EventWrapper( { TabsTrayCfr.dismiss.record(it) } ) is Event.TabsTrayCfrTapped -> EventWrapper( { TabsTrayCfr.goToSettings.record(it) } ) is Event.AutoPlaySettingVisited -> EventWrapper( { Autoplay.visitedSetting.record(it) } ) is Event.AutoPlaySettingChanged -> EventWrapper( { Autoplay.settingChanged.record(it) }, { Autoplay.settingChangedKeys.valueOf(it) } ) is Event.ProgressiveWebAppOpenFromHomescreenTap -> EventWrapper( { ProgressiveWebApp.homescreenTap.record(it) } ) is Event.ProgressiveWebAppInstallAsShortcut -> EventWrapper( { 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) } ) is Event.CopyUrlUsed -> EventWrapper( { Events.copyUrlTapped.record(it) } ) is Event.SyncedTabOpened -> EventWrapper( { Events.syncedTabOpened.record(it) } ) is Event.RecentlyClosedTabsOpened -> EventWrapper( { Events.recentlyClosedTabsOpened.record(it) } ) is Event.MasterPasswordMigrationDisplayed -> EventWrapper( { MasterPassword.displayed.record(it) } ) is Event.MasterPasswordMigrationSuccess -> EventWrapper( { MasterPassword.migration.record(it) } ) is Event.TabSettingsOpened -> EventWrapper( { Tabs.settingOpened.record(it) } ) Event.ContextMenuCopyTapped -> EventWrapper( { ContextualMenu.copyTapped.record(it) } ) is Event.ContextMenuSearchTapped -> EventWrapper( { ContextualMenu.searchTapped.record(it) } ) is Event.ContextMenuSelectAllTapped -> EventWrapper( { ContextualMenu.selectAllTapped.record(it) } ) is Event.ContextMenuShareTapped -> EventWrapper( { ContextualMenu.shareTapped.record(it) } ) Event.HaveOpenTabs -> EventWrapper( { Metrics.hasOpenTabs.set(true) } ) Event.HaveNoOpenTabs -> EventWrapper( { Metrics.hasOpenTabs.set(false) } ) is Event.BannerOpenInAppDisplayed -> EventWrapper( { BannerOpenInApp.displayed.record(it) } ) is Event.BannerOpenInAppDismissed -> EventWrapper( { BannerOpenInApp.dismissed.record(it) } ) is Event.BannerOpenInAppGoToSettings -> EventWrapper( { BannerOpenInApp.goToSettings.record(it) } ) is Event.SyncedTabSuggestionClicked -> EventWrapper( { SyncedTabs.syncedTabsSuggestionClicked.record(it) } ) is Event.BookmarkSuggestionClicked -> EventWrapper( { Awesomebar.bookmarkSuggestionClicked.record(it) } ) is Event.ClipboardSuggestionClicked -> EventWrapper( { Awesomebar.clipboardSuggestionClicked.record(it) } ) is Event.HistorySuggestionClicked -> EventWrapper( { Awesomebar.historySuggestionClicked.record(it) } ) is Event.SearchActionClicked -> EventWrapper( { Awesomebar.searchActionClicked.record(it) } ) is Event.SearchSuggestionClicked -> EventWrapper( { Awesomebar.searchSuggestionClicked.record(it) } ) is Event.OpenedTabSuggestionClicked -> EventWrapper( { Awesomebar.openedTabSuggestionClicked.record(it) } ) is Event.SecurePrefsExperimentFailure -> EventWrapper( { AndroidKeystoreExperiment.experimentFailure.record(it) }, { AndroidKeystoreExperiment.experimentFailureKeys.valueOf(it) } ) is Event.SecurePrefsGetFailure -> EventWrapper( { AndroidKeystoreExperiment.getFailure.record(it) }, { AndroidKeystoreExperiment.getFailureKeys.valueOf(it) } ) is Event.SecurePrefsGetSuccess -> EventWrapper( { AndroidKeystoreExperiment.getResult.record(it) }, { AndroidKeystoreExperiment.getResultKeys.valueOf(it) } ) is Event.SecurePrefsWriteFailure -> EventWrapper( { AndroidKeystoreExperiment.writeFailure.record(it) }, { AndroidKeystoreExperiment.writeFailureKeys.valueOf(it) } ) is Event.SecurePrefsWriteSuccess -> EventWrapper( { AndroidKeystoreExperiment.writeSuccess.record(it) } ) is Event.SecurePrefsReset -> EventWrapper( { AndroidKeystoreExperiment.reset.record(it) } ) is Event.HomeMenuSettingsItemClicked -> EventWrapper( { HomeMenu.settingsItemClicked.record(it) } ) is Event.CloseExperimentCardClicked -> EventWrapper( { SetDefaultNewtabExperiment.closeExperimentCardClicked.record(it) } ) is Event.SetDefaultBrowserClicked -> EventWrapper( { SetDefaultNewtabExperiment.setDefaultBrowserClicked.record(it) } ) is Event.SetDefaultBrowserSettingsScreenClicked -> EventWrapper( { SetDefaultSettingExperiment.setDefaultBrowserClicked.record(it) } ) is Event.HomeScreenDisplayed -> EventWrapper( { HomeScreen.homeScreenDisplayed.record(it) } ) // Don't record other events in Glean: is Event.AddBookmark -> 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.SyncAuthFromSharedReuse, Event.SyncAuthFromSharedCopy -> null } class GleanMetricsService( private val context: Context, private val store: Lazy, private val browsersCache: BrowsersCache = BrowsersCache, private val mozillaProductDetector: MozillaProductDetector = MozillaProductDetector ) : 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(context.settings()) } } /** * This function is called before the metrics ping is sent. Part of this function depends on * shared preferences to be updated so the correct value is sent with the metrics ping. * * The reason we're using shared preferences to track some of these values is due to the * limitations of the metrics ping. Events are only sent in a metrics ping if the user have made * changes between each ping. However, in some cases we want current values to be sent even if * the user have not changed anything between pings. */ internal fun setStartupMetrics(settings: Settings) { setPreferenceMetrics() with(Metrics) { defaultBrowser.set(browsersCache.all(context).isDefaultBrowser) mozillaProductDetector.getMozillaBrowserDefault(context)?.also { defaultMozBrowser.set(it) } mozillaProducts.set(mozillaProductDetector.getInstalledMozillaProducts(context)) adjustCampaign.set(settings.adjustCampaignId) adjustAdGroup.set(settings.adjustAdGroup) adjustCreative.set(settings.adjustCreative) adjustNetwork.set(settings.adjustNetwork) searchWidgetInstalled.set(settings.searchWidgetInstalled) val openTabsCount = settings.openTabsCount hasOpenTabs.set(openTabsCount > 0) if (openTabsCount > 0) { tabsOpenCount.add(openTabsCount) } val topSitesSize = settings.topSitesSize hasTopSites.set(topSitesSize > 0) if (topSitesSize > 0) { topSitesCount.add(topSitesSize) } val installedAddonSize = settings.installedAddonsCount Addons.hasInstalledAddons.set(installedAddonSize > 0) if (installedAddonSize > 0) { Addons.installedAddons.set(settings.installedAddonsList.split(',')) } val enabledAddonSize = settings.enabledAddonsCount Addons.hasEnabledAddons.set(enabledAddonSize > 0) if (enabledAddonSize > 0) { Addons.enabledAddons.set(settings.enabledAddonsList.split(',')) } val desktopBookmarksSize = settings.desktopBookmarksSize hasDesktopBookmarks.set(desktopBookmarksSize > 0) if (desktopBookmarksSize > 0) { desktopBookmarksCount.add(desktopBookmarksSize) } val mobileBookmarksSize = settings.mobileBookmarksSize hasMobileBookmarks.set(mobileBookmarksSize > 0) if (mobileBookmarksSize > 0) { mobileBookmarksCount.add(mobileBookmarksSize) } toolbarPosition.set( when (settings.toolbarPosition) { ToolbarPosition.BOTTOM -> Event.ToolbarPositionChanged.Position.BOTTOM.name ToolbarPosition.TOP -> Event.ToolbarPositionChanged.Position.TOP.name } ) tabViewSetting.set(settings.getTabViewPingString()) closeTabSetting.set(settings.getTabTimeoutPingString()) } store.value.waitForSelectedOrDefaultSearchEngine { searchEngine -> if (searchEngine != null) { SearchDefaultEngine.apply { code.set(searchEngine.id) name.set(searchEngine.name) submissionUrl.set(searchEngine.legacy().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. with(Preferences) { 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() 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 { return listOf(this.toString()) }