1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.fenix
7 import android.annotation.SuppressLint
9 import android.os.Build
10 import android.os.Build.VERSION.SDK_INT
11 import android.os.StrictMode
12 import android.os.SystemClock
13 import android.util.Log.INFO
14 import androidx.annotation.CallSuper
15 import androidx.annotation.VisibleForTesting
16 import androidx.appcompat.app.AppCompatDelegate
17 import androidx.core.app.NotificationManagerCompat
18 import androidx.core.content.getSystemService
19 import androidx.lifecycle.ProcessLifecycleOwner
20 import androidx.work.Configuration.Builder
21 import androidx.work.Configuration.Provider
22 import kotlinx.coroutines.Deferred
23 import kotlinx.coroutines.DelicateCoroutinesApi
24 import kotlinx.coroutines.Dispatchers
25 import kotlinx.coroutines.GlobalScope
26 import kotlinx.coroutines.async
27 import kotlinx.coroutines.launch
28 import mozilla.appservices.Megazord
29 import mozilla.components.browser.state.action.SystemAction
30 import mozilla.components.browser.state.selector.selectedTab
31 import mozilla.components.browser.state.state.searchEngines
32 import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
33 import mozilla.components.browser.state.store.BrowserStore
34 import mozilla.components.browser.storage.sync.GlobalPlacesDependencyProvider
35 import mozilla.components.concept.base.crash.Breadcrumb
36 import mozilla.components.concept.engine.webextension.WebExtension
37 import mozilla.components.concept.engine.webextension.isUnsupported
38 import mozilla.components.concept.push.PushProcessor
39 import mozilla.components.concept.storage.FrecencyThresholdOption
40 import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker
41 import mozilla.components.feature.addons.update.GlobalAddonDependencyProvider
42 import mozilla.components.feature.autofill.AutofillUseCases
43 import mozilla.components.feature.search.ext.buildSearchUrl
44 import mozilla.components.feature.search.ext.waitForSelectedOrDefaultSearchEngine
45 import mozilla.components.feature.top.sites.TopSitesFrecencyConfig
46 import mozilla.components.feature.top.sites.TopSitesProviderConfig
47 import mozilla.components.lib.crash.CrashReporter
48 import mozilla.components.service.fxa.manager.SyncEnginesStorage
49 import mozilla.components.service.glean.Glean
50 import mozilla.components.service.glean.config.Configuration
51 import mozilla.components.service.glean.net.ConceptFetchHttpUploader
52 import mozilla.components.support.base.facts.register
53 import mozilla.components.support.base.log.Log
54 import mozilla.components.support.base.log.logger.Logger
55 import mozilla.components.support.ktx.android.content.isMainProcess
56 import mozilla.components.support.ktx.android.content.runOnlyInMainProcess
57 import mozilla.components.support.locale.LocaleAwareApplication
58 import mozilla.components.support.rusterrors.initializeRustErrors
59 import mozilla.components.support.rusthttp.RustHttpConfig
60 import mozilla.components.support.rustlog.RustLog
61 import mozilla.components.support.utils.logElapsedTime
62 import mozilla.components.support.webextensions.WebExtensionSupport
63 import org.mozilla.fenix.GleanMetrics.Addons
64 import org.mozilla.fenix.GleanMetrics.AndroidAutofill
65 import org.mozilla.fenix.GleanMetrics.CustomizeHome
66 import org.mozilla.fenix.GleanMetrics.Events.marketingNotificationAllowed
67 import org.mozilla.fenix.GleanMetrics.GleanBuildInfo
68 import org.mozilla.fenix.GleanMetrics.Metrics
69 import org.mozilla.fenix.GleanMetrics.PerfStartup
70 import org.mozilla.fenix.GleanMetrics.Preferences
71 import org.mozilla.fenix.GleanMetrics.SearchDefaultEngine
72 import org.mozilla.fenix.GleanMetrics.TopSites
73 import org.mozilla.fenix.components.Components
74 import org.mozilla.fenix.components.Core
75 import org.mozilla.fenix.components.appstate.AppAction
76 import org.mozilla.fenix.components.metrics.MetricServiceType
77 import org.mozilla.fenix.components.metrics.MozillaProductDetector
78 import org.mozilla.fenix.components.toolbar.ToolbarPosition
79 import org.mozilla.fenix.experiments.maybeFetchExperiments
80 import org.mozilla.fenix.ext.areNotificationsEnabledSafe
81 import org.mozilla.fenix.ext.containsQueryParameters
82 import org.mozilla.fenix.ext.getCustomGleanServerUrlIfAvailable
83 import org.mozilla.fenix.ext.isCustomEngine
84 import org.mozilla.fenix.ext.isKnownSearchDomain
85 import org.mozilla.fenix.ext.isNotificationChannelEnabled
86 import org.mozilla.fenix.ext.setCustomEndpointIfAvailable
87 import org.mozilla.fenix.ext.settings
88 import org.mozilla.fenix.nimbus.FxNimbus
89 import org.mozilla.fenix.onboarding.MARKETING_CHANNEL_ID
90 import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
91 import org.mozilla.fenix.perf.ProfilerMarkerFactProcessor
92 import org.mozilla.fenix.perf.StartupTimeline
93 import org.mozilla.fenix.perf.StorageStatsMetrics
94 import org.mozilla.fenix.perf.runBlockingIncrement
95 import org.mozilla.fenix.push.PushFxaIntegration
96 import org.mozilla.fenix.push.WebPushEngineIntegration
97 import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
98 import org.mozilla.fenix.session.VisibilityLifecycleCallback
99 import org.mozilla.fenix.settings.CustomizationFragment
100 import org.mozilla.fenix.telemetry.TelemetryLifecycleObserver
101 import org.mozilla.fenix.utils.BrowsersCache
102 import org.mozilla.fenix.utils.Settings
103 import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD
104 import org.mozilla.fenix.wallpapers.Wallpaper
105 import java.util.UUID
106 import java.util.concurrent.TimeUnit
109 *The main application class for Fenix. Records data to measure initialization performance.
110 * Installs [CrashReporter], initializes [Glean] in fenix builds and setup Megazord in the main process.
112 @Suppress("Registered", "TooManyFunctions", "LargeClass")
113 open class FenixApplication : LocaleAwareApplication(), Provider {
115 recordOnInit() // DO NOT MOVE ANYTHING ABOVE HERE: the timing of this measurement is critical.
118 private val logger = Logger("FenixApplication")
120 open val components by lazy { Components(this) }
122 var visibilityLifecycleCallback: VisibilityLifecycleCallback? = null
125 override fun onCreate() {
126 // We measure ourselves to avoid a call into Glean before its loaded.
127 val start = SystemClock.elapsedRealtimeNanos()
131 setupInAllProcesses()
133 if (!isMainProcess()) {
134 // If this is not the main process then do not continue with the initialization here. Everything that
135 // follows only needs to be done in our app's main process and should not be done in other processes like
136 // a GeckoView child process or the crash handling process. Most importantly we never want to end up in a
137 // situation where we create a GeckoRuntime from the Gecko child process.
141 // DO NOT ADD ANYTHING ABOVE HERE.
142 setupInMainProcessOnly()
143 // DO NOT ADD ANYTHING UNDER HERE.
145 // DO NOT MOVE ANYTHING BELOW THIS elapsedRealtimeNanos CALL.
146 val stop = SystemClock.elapsedRealtimeNanos()
147 val durationMillis = TimeUnit.NANOSECONDS.toMillis(stop - start)
149 // We avoid blocking the main thread on startup by calling into Glean on the background thread.
150 @OptIn(DelicateCoroutinesApi::class)
151 GlobalScope.launch(Dispatchers.IO) {
152 PerfStartup.applicationOnCreate.accumulateSamples(listOf(durationMillis))
156 @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
157 protected open fun initializeGlean() {
158 val telemetryEnabled = settings().isTelemetryEnabled
160 logger.debug("Initializing Glean (uploadEnabled=$telemetryEnabled})")
162 // for performance reasons, this is only available in Nightly or Debug builds
163 val customEndpoint = if (Config.channel.isNightlyOrDebug) {
164 // for testing, if custom glean server url is set in the secret menu, use it to initialize Glean
165 getCustomGleanServerUrlIfAvailable(this)
170 val configuration = Configuration(
171 channel = BuildConfig.BUILD_TYPE,
172 httpClient = ConceptFetchHttpUploader(
173 lazy(LazyThreadSafetyMode.NONE) { components.core.client },
178 applicationContext = this,
179 configuration = configuration.setCustomEndpointIfAvailable(customEndpoint),
180 uploadEnabled = telemetryEnabled,
181 buildInfo = GleanBuildInfo.buildInfo,
184 // We avoid blocking the main thread on startup by setting startup metrics on the background thread.
185 val store = components.core.store
186 GlobalScope.launch(Dispatchers.IO) {
187 setStartupMetrics(store, settings())
192 open fun setupInAllProcesses() {
193 setupCrashReporting()
195 // We want the log messages of all builds to go to Android logcat
196 Log.addSink(FenixLogSink(logsDebug = Config.channel.isDebug))
200 open fun setupInMainProcessOnly() {
201 // ⚠️ DO NOT ADD ANYTHING ABOVE THIS LINE.
202 // Especially references to the engine/BrowserStore which can alter the app initialization.
203 // See: https://github.com/mozilla-mobile/fenix/issues/26320
205 // We can initialize Nimbus before Glean because Glean will queue messages
206 // before it's initialized.
209 ProfilerMarkerFactProcessor.create { components.core.engine.profiler }.register()
212 // Make sure the engine is initialized and ready to use.
213 components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
214 components.core.engine.warmUp()
217 // We need to always initialize Glean and do it early here.
220 // Attention: Do not invoke any code from a-s in this scope.
221 val megazordSetup = finishSetupMegazord()
224 components.strictMode.enableStrictMode(true)
227 initializeWebExtensionSupport()
228 if (FeatureFlags.storageMaintenanceFeature) {
229 // Make sure to call this function before registering a storage worker
230 // (e.g. components.core.historyStorage.registerStorageMaintenanceWorker())
231 // as the storage maintenance worker needs a places storage globally when
232 // it is needed while the app is not running and WorkManager wakes up the app
233 // for the periodic task.
234 GlobalPlacesDependencyProvider.initialize(components.core.historyStorage)
236 restoreBrowserState()
240 // Just to make sure it is impossible for any application-services pieces
241 // to invoke parts of itself that require complete megazord initialization
242 // before that process completes, we wait here, if necessary.
243 if (!megazordSetup.isCompleted) {
244 runBlockingIncrement { megazordSetup.await() }
249 startMetricsIfEnabled()
252 visibilityLifecycleCallback = VisibilityLifecycleCallback(getSystemService())
253 registerActivityLifecycleCallbacks(visibilityLifecycleCallback)
254 registerActivityLifecycleCallbacks(MarkersActivityLifecycleCallbacks(components.core.engine))
256 components.appStartReasonProvider.registerInAppOnCreate(this)
257 components.startupActivityLog.registerInAppOnCreate(this)
258 initVisualCompletenessQueueAndQueueTasks()
260 ProcessLifecycleOwner.get().lifecycle.addObserver(TelemetryLifecycleObserver(components.core.store))
262 components.analytics.metricsStorage.tryRegisterAsUsageRecorder(this)
267 @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
268 private fun restoreBrowserState() = GlobalScope.launch(Dispatchers.Main) {
269 val store = components.core.store
270 val sessionStorage = components.core.sessionStorage
272 components.useCases.tabsUseCases.restore(sessionStorage, settings().getTabTimeout())
274 // Now that we have restored our previous state (if there's one) let's setup auto saving the state while
276 sessionStorage.autoSave(store)
277 .periodicallyInForeground(interval = 30, unit = TimeUnit.SECONDS)
278 .whenGoingToBackground()
279 .whenSessionsChange()
282 private fun restoreDownloads() {
283 components.useCases.downloadUseCases.restoreDownloads()
286 private fun initVisualCompletenessQueueAndQueueTasks() {
287 val queue = components.performance.visualCompletenessQueue.queue
290 registerActivityLifecycleCallbacks(PerformanceActivityLifecycleCallbacks(queue))
293 @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
294 fun queueInitStorageAndServices() {
295 components.performance.visualCompletenessQueue.queue.runIfReadyOrQueue {
296 GlobalScope.launch(Dispatchers.IO) {
297 logger.info("Running post-visual completeness tasks...")
298 logElapsedTime(logger, "Storage initialization") {
299 components.core.historyStorage.warmUp()
300 components.core.bookmarksStorage.warmUp()
301 components.core.passwordsStorage.warmUp()
302 components.core.autofillStorage.warmUp()
304 // Populate the top site cache to improve initial load experience
305 // of the home fragment when the app is launched to a tab. The actual
306 // database call is not expensive. However, the additional context
307 // switches delay rendering top sites when the cache is empty, which
308 // we can prevent with this.
309 components.core.topSitesStorage.getTopSites(
310 totalSites = components.settings.topSitesMaxLimit,
311 frecencyConfig = TopSitesFrecencyConfig(
312 FrecencyThresholdOption.SKIP_ONE_TIME_PAGES,
315 .containsQueryParameters(components.settings.frecencyFilterQuery)
317 providerConfig = TopSitesProviderConfig(
318 showProviderTopSites = components.settings.showContileFeature,
319 maxThreshold = TOP_SITES_PROVIDER_MAX_THRESHOLD,
323 // This service uses `historyStorage`, and so we can only touch it when we know
324 // it's safe to touch `historyStorage. By 'safe', we mainly mean that underlying
325 // places library will be able to load, which requires first running Megazord.init().
326 // The visual completeness tasks are scheduled after the Megazord.init() call.
327 components.core.historyMetadataService.cleanup(
328 System.currentTimeMillis() - Core.HISTORY_METADATA_MAX_AGE_IN_MS,
332 // Account manager initialization needs to happen on the main thread.
333 GlobalScope.launch(Dispatchers.Main) {
334 logElapsedTime(logger, "Kicking-off account manager") {
335 components.backgroundServices.accountManager
342 if (SDK_INT >= Build.VERSION_CODES.O) { // required by StorageStatsMetrics.
343 queue.runIfReadyOrQueue {
344 // Because it may be slow to capture the storage stats, it might be preferred to
345 // create a WorkManager task for this metric, however, I ran out of
346 // implementation time and WorkManager is harder to test.
347 StorageStatsMetrics.report(this.applicationContext)
352 @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
353 fun queueReviewPrompt() {
354 GlobalScope.launch(Dispatchers.IO) {
355 components.reviewPromptController.trackApplicationLaunch()
359 @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
360 fun queueRestoreLocale() {
361 components.performance.visualCompletenessQueue.queue.runIfReadyOrQueue {
362 GlobalScope.launch(Dispatchers.IO) {
363 components.useCases.localeUseCases.restore()
368 fun queueStorageMaintenance() {
369 if (FeatureFlags.storageMaintenanceFeature) {
370 queue.runIfReadyOrQueue {
371 // Make sure GlobalPlacesDependencyProvider.initialize(components.core.historyStorage)
372 // is called before this call. When app is not running and WorkManager wakes up
373 // the app for the periodic task, it will require a globally provided places storage
374 // to run the maintenance on.
375 components.core.historyStorage.registerStorageMaintenanceWorker()
380 @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
381 fun queueNimbusFetchInForeground() {
382 queue.runIfReadyOrQueue {
383 GlobalScope.launch(Dispatchers.IO) {
384 components.analytics.experiments.maybeFetchExperiments(
385 context = this@FenixApplication,
393 // We init these items in the visual completeness queue to avoid them initing in the critical
394 // startup path, before the UI finishes drawing (i.e. visual completeness).
395 queueInitStorageAndServices()
399 queueStorageMaintenance()
400 queueNimbusFetchInForeground()
403 private fun startMetricsIfEnabled() {
404 if (settings().isTelemetryEnabled) {
405 components.analytics.metrics.start(MetricServiceType.Data)
408 if (settings().isMarketingTelemetryEnabled) {
409 components.analytics.metrics.start(MetricServiceType.Marketing)
413 protected open fun setupLeakCanary() {
414 // no-op, LeakCanary is disabled by default
417 open fun updateLeakCanaryState(isEnabled: Boolean) {
418 // no-op, LeakCanary is disabled by default
421 private fun setupPush() {
422 // Sets the PushFeature as the singleton instance for push messages to go to.
423 // We need the push feature setup here to deliver messages in the case where the service
424 // starts up the app first.
425 components.push.feature?.let {
426 Logger.info("AutoPushFeature is configured, initializing it...")
428 // Install the AutoPush singleton to receive messages.
429 PushProcessor.install(it)
431 WebPushEngineIntegration(components.core.engine, it).start()
433 // Perform a one-time initialization of the account manager if a message is received.
434 PushFxaIntegration(it, lazy { components.backgroundServices.accountManager }).launch()
436 // Initialize the service. This could potentially be done in a coroutine in the future.
441 private fun setupCrashReporting() {
448 protected open fun initializeNimbus() {
451 // This lazily constructs the Nimbus object…
452 val nimbus = components.analytics.experiments
453 // … which we then can populate the feature configuration.
454 FxNimbus.initialize { nimbus }
458 * Initiate Megazord sequence! Megazord Battle Mode!
460 * The application-services combined libraries are known as the "megazord". We use the default `full`
461 * megazord - it contains everything that fenix needs, and (currently) nothing more.
463 * Documentation on what megazords are, and why they're needed:
464 * - https://github.com/mozilla/application-services/blob/master/docs/design/megazords.md
465 * - https://mozilla.github.io/application-services/docs/applications/consuming-megazord-libraries.html
467 * This is the initialization of the megazord without setting up networking, i.e. needing the
468 * engine for networking. This should do the minimum work necessary as it is done on the main
469 * thread, early in the app startup sequence.
471 private fun beginSetupMegazord() {
472 // Note: Megazord.init() must be called as soon as possible ...
475 initializeRustErrors(components.analytics.crashReporter)
476 // ... but RustHttpConfig.setClient() and RustLog.enable() can be called later.
478 // Once application-services has switched to using the new
479 // error reporting system, RustLog shouldn't input a CrashReporter
481 // (https://github.com/mozilla/application-services/issues/4981).
482 RustLog.enable(components.analytics.crashReporter)
485 @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
486 private fun finishSetupMegazord(): Deferred<Unit> {
487 return GlobalScope.async(Dispatchers.IO) {
488 if (Config.channel.isDebug) {
489 RustHttpConfig.allowEmulatorLoopback()
491 RustHttpConfig.setClient(lazy { components.core.client })
493 // Now viaduct (the RustHttp client) is initialized we can ask Nimbus to fetch
494 // experiments recipes from the server.
498 private fun restoreMessaging() {
499 if (settings().isExperimentationEnabled) {
500 components.appStore.dispatch(AppAction.MessagingAction.Restore)
504 override fun onTrimMemory(level: Int) {
505 super.onTrimMemory(level)
507 // Additional logging and breadcrumb to debug memory issues:
508 // https://github.com/mozilla-mobile/fenix/issues/12731
510 logger.info("onTrimMemory(), level=$level, main=${isMainProcess()}")
512 components.analytics.crashReporter.recordCrashBreadcrumb(
515 message = "onTrimMemory()",
517 "level" to level.toString(),
518 "main" to isMainProcess().toString(),
520 level = Breadcrumb.Level.INFO,
524 runOnlyInMainProcess {
525 components.core.icons.onTrimMemory(level)
526 components.core.store.dispatch(SystemAction.LowMemoryAction(level))
530 @SuppressLint("WrongConstant")
531 // Suppressing erroneous lint warning about using MODE_NIGHT_AUTO_BATTERY, a likely library bug
532 private fun setDayNightTheme() {
533 val settings = this.settings()
535 settings.shouldUseLightTheme -> {
536 AppCompatDelegate.setDefaultNightMode(
537 AppCompatDelegate.MODE_NIGHT_NO,
540 settings.shouldUseDarkTheme -> {
541 AppCompatDelegate.setDefaultNightMode(
542 AppCompatDelegate.MODE_NIGHT_YES,
545 SDK_INT < Build.VERSION_CODES.P && settings.shouldUseAutoBatteryTheme -> {
546 AppCompatDelegate.setDefaultNightMode(
547 AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY,
550 SDK_INT >= Build.VERSION_CODES.P && settings.shouldFollowDeviceTheme -> {
551 AppCompatDelegate.setDefaultNightMode(
552 AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
555 // First run of app no default set, set the default to Follow System for 28+ and Normal Mode otherwise
557 if (SDK_INT >= Build.VERSION_CODES.P) {
558 AppCompatDelegate.setDefaultNightMode(
559 AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
561 settings.shouldFollowDeviceTheme = true
563 AppCompatDelegate.setDefaultNightMode(
564 AppCompatDelegate.MODE_NIGHT_NO,
566 settings.shouldUseLightTheme = true
573 * If unified search is enabled try to migrate the topic specific engine to the
574 * first general or custom search engine available.
576 @Suppress("NestedBlockDepth")
577 private fun migrateTopicSpecificSearchEngines() {
578 if (settings().showUnifiedSearchFeature) {
579 components.core.store.state.search.selectedOrDefaultSearchEngine.let { currentSearchEngine ->
580 if (currentSearchEngine?.isGeneral == false) {
581 components.core.store.state.search.searchEngines.firstOrNull() { nextSearchEngine ->
582 nextSearchEngine.isGeneral
584 components.useCases.searchUseCases.selectSearchEngine(it)
591 @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
592 private fun warmBrowsersCache() {
593 // We avoid blocking the main thread for BrowsersCache on startup by loading it on
594 // background thread.
595 GlobalScope.launch(Dispatchers.Default) {
596 BrowsersCache.all(this@FenixApplication)
600 private fun initializeWebExtensionSupport() {
602 GlobalAddonDependencyProvider.initialize(
603 components.addonManager,
604 components.addonUpdater,
605 onCrash = { exception ->
606 components.analytics.crashReporter.submitCaughtException(exception)
609 WebExtensionSupport.initialize(
610 components.core.engine,
611 components.core.store,
612 onNewTabOverride = { _, engineSession, url ->
613 val shouldCreatePrivateSession =
614 components.core.store.state.selectedTab?.content?.private
615 ?: components.settings.openLinksInAPrivateTab
617 components.useCases.tabsUseCases.addTab(
620 engineSession = engineSession,
621 private = shouldCreatePrivateSession,
624 onCloseTabOverride = { _, sessionId ->
625 components.useCases.tabsUseCases.removeTab(sessionId)
627 onSelectTabOverride = { _, sessionId ->
628 components.useCases.tabsUseCases.selectTab(sessionId)
630 onExtensionsLoaded = { extensions ->
631 components.addonUpdater.registerForFutureUpdates(extensions)
632 subscribeForNewAddonsIfNeeded(components.supportedAddonsChecker, extensions)
634 onUpdatePermissionRequest = components.addonUpdater::onUpdatePermissionRequest,
636 } catch (e: UnsupportedOperationException) {
637 Logger.error("Failed to initialize web extension support", e)
642 internal fun subscribeForNewAddonsIfNeeded(
643 checker: DefaultSupportedAddonsChecker,
644 installedExtensions: List<WebExtension>,
646 val hasUnsupportedAddons = installedExtensions.any { it.isUnsupported() }
647 if (hasUnsupportedAddons) {
648 checker.registerForChecks()
650 // As checks are a persistent subscriptions, we have to make sure
651 // we remove any previous subscriptions.
652 checker.unregisterForChecks()
657 * This function is called right after Glean is initialized. Part of this function depends on
658 * shared preferences to be updated so the correct value is sent with the metrics ping.
660 * The reason we're using shared preferences to track these values is due to the limitations of
661 * the current metrics ping design. The values set here will be sent in every metrics ping even
662 * if these values have not changed since the last startup.
664 @Suppress("ComplexMethod", "LongMethod")
666 internal fun setStartupMetrics(
667 browserStore: BrowserStore,
669 browsersCache: BrowsersCache = BrowsersCache,
670 mozillaProductDetector: MozillaProductDetector = MozillaProductDetector,
672 setPreferenceMetrics(settings)
674 // Set this early to guarantee it's in every ping from here on.
676 when (Config.channel.isMozillaOnline) {
677 true -> "MozillaOnline"
682 defaultBrowser.set(browsersCache.all(applicationContext).isDefaultBrowser)
683 mozillaProductDetector.getMozillaBrowserDefault(applicationContext)?.also {
684 defaultMozBrowser.set(it)
687 if (settings.contileContextId.isEmpty()) {
688 settings.contileContextId = TopSites.contextId.generateAndSet().toString()
690 TopSites.contextId.set(UUID.fromString(settings.contileContextId))
694 mozillaProductDetector.getInstalledMozillaProducts(
699 adjustCampaign.set(settings.adjustCampaignId)
700 adjustAdGroup.set(settings.adjustAdGroup)
701 adjustCreative.set(settings.adjustCreative)
702 adjustNetwork.set(settings.adjustNetwork)
704 searchWidgetInstalled.set(settings.searchWidgetInstalled)
706 val openTabsCount = settings.openTabsCount
707 hasOpenTabs.set(openTabsCount > 0)
708 if (openTabsCount > 0) {
709 tabsOpenCount.add(openTabsCount)
712 val topSitesSize = settings.topSitesSize
713 hasTopSites.set(topSitesSize > 0)
714 if (topSitesSize > 0) {
715 topSitesCount.add(topSitesSize)
718 val installedAddonSize = settings.installedAddonsCount
719 Addons.hasInstalledAddons.set(installedAddonSize > 0)
720 if (installedAddonSize > 0) {
721 Addons.installedAddons.set(settings.installedAddonsList.split(','))
724 val enabledAddonSize = settings.enabledAddonsCount
725 Addons.hasEnabledAddons.set(enabledAddonSize > 0)
726 if (enabledAddonSize > 0) {
727 Addons.enabledAddons.set(settings.enabledAddonsList.split(','))
730 val desktopBookmarksSize = settings.desktopBookmarksSize
731 hasDesktopBookmarks.set(desktopBookmarksSize > 0)
732 if (desktopBookmarksSize > 0) {
733 desktopBookmarksCount.add(desktopBookmarksSize)
736 val mobileBookmarksSize = settings.mobileBookmarksSize
737 hasMobileBookmarks.set(mobileBookmarksSize > 0)
738 if (mobileBookmarksSize > 0) {
739 mobileBookmarksCount.add(mobileBookmarksSize)
743 when (settings.toolbarPosition) {
744 ToolbarPosition.BOTTOM -> CustomizationFragment.Companion.Position.BOTTOM.name
745 ToolbarPosition.TOP -> CustomizationFragment.Companion.Position.TOP.name
749 tabViewSetting.set(settings.getTabViewPingString())
750 closeTabSetting.set(settings.getTabTimeoutPingString())
752 val installSourcePackage = if (SDK_INT >= Build.VERSION_CODES.R) {
753 packageManager.getInstallSourceInfo(packageName).installingPackageName
755 @Suppress("DEPRECATION")
756 packageManager.getInstallerPackageName(packageName)
758 installSource.set(installSourcePackage.orEmpty())
760 val isDefaultTheCurrentWallpaper =
761 Wallpaper.nameIsDefault(settings.currentWallpaperName)
763 defaultWallpaper.set(isDefaultTheCurrentWallpaper)
765 val notificationManagerCompat = NotificationManagerCompat.from(applicationContext)
766 notificationsAllowed.set(notificationManagerCompat.areNotificationsEnabledSafe())
767 marketingNotificationAllowed.set(
768 notificationManagerCompat.isNotificationChannelEnabled(MARKETING_CHANNEL_ID),
772 with(AndroidAutofill) {
773 val autofillUseCases = AutofillUseCases()
774 supported.set(autofillUseCases.isSupported(applicationContext))
775 enabled.set(autofillUseCases.isEnabled(applicationContext))
778 browserStore.waitForSelectedOrDefaultSearchEngine { searchEngine ->
781 !searchEngine.isCustomEngine() || searchEngine.isKnownSearchDomain()
783 SearchDefaultEngine.apply {
784 code.set(searchEngine.id)
785 name.set(searchEngine.name)
786 searchUrl.set(searchEngine.buildSearchUrl(""))
789 SearchDefaultEngine.apply {
790 code.set(searchEngine.id)
795 migrateTopicSpecificSearchEngines()
800 @Suppress("ComplexMethod")
801 private fun setPreferenceMetrics(
805 searchSuggestionsEnabled.set(settings.shouldShowSearchSuggestions)
806 remoteDebuggingEnabled.set(settings.isRemoteDebuggingEnabled)
807 studiesEnabled.set(settings.isExperimentationEnabled)
808 telemetryEnabled.set(settings.isTelemetryEnabled)
809 browsingHistorySuggestion.set(settings.shouldShowHistorySuggestions)
810 bookmarksSuggestion.set(settings.shouldShowBookmarkSuggestions)
811 clipboardSuggestionsEnabled.set(settings.shouldShowClipboardSuggestions)
812 searchShortcutsEnabled.set(settings.shouldShowSearchShortcuts)
813 voiceSearchEnabled.set(settings.shouldShowVoiceSearch)
814 openLinksInAppEnabled.set(settings.openLinksInExternalApp)
815 signedInSync.set(settings.signedInFxaAccount)
817 val syncedItems = SyncEnginesStorage(applicationContext).getStatus().entries.filter {
819 }.map { it.key.nativeName }
820 syncItems.set(syncedItems)
822 toolbarPositionSetting.set(
824 settings.shouldUseFixedTopToolbar -> "fixed_top"
825 settings.shouldUseBottomToolbar -> "bottom"
830 enhancedTrackingProtection.set(
832 !settings.shouldUseTrackingProtection -> ""
833 settings.useStandardTrackingProtection -> "standard"
834 settings.useStrictTrackingProtection -> "strict"
835 settings.useCustomTrackingProtection -> "custom"
839 etpCustomCookiesSelection.set(settings.blockCookiesSelectionInCustomTrackingProtection)
841 val accessibilitySelection = mutableListOf<String>()
843 if (settings.switchServiceIsEnabled) {
844 accessibilitySelection.add("switch")
847 if (settings.touchExplorationIsEnabled) {
848 accessibilitySelection.add("touch exploration")
851 accessibilityServices.set(accessibilitySelection.toList())
855 settings.shouldUseLightTheme -> "light"
856 settings.shouldUseDarkTheme -> "dark"
857 settings.shouldFollowDeviceTheme -> "system"
858 settings.shouldUseAutoBatteryTheme -> "battery"
863 inactiveTabsEnabled.set(settings.inactiveTabsAreEnabled)
865 reportHomeScreenMetrics(settings)
869 internal fun reportHomeScreenMetrics(settings: Settings) {
870 reportOpeningScreenMetrics(settings)
871 reportHomeScreenSectionMetrics(settings)
874 private fun reportOpeningScreenMetrics(settings: Settings) {
875 CustomizeHome.openingScreen.set(
877 settings.alwaysOpenTheHomepageWhenOpeningTheApp -> "homepage"
878 settings.alwaysOpenTheLastTabWhenOpeningTheApp -> "last tab"
879 settings.openHomepageAfterFourHoursOfInactivity -> "homepage after four hours"
885 private fun reportHomeScreenSectionMetrics(settings: Settings) {
886 // These settings are backed by Nimbus features.
887 // We break them out here so they can be recorded when
888 // `nimbus.applyPendingExperiments()` is called.
889 CustomizeHome.jumpBackIn.set(settings.showRecentTabsFeature)
890 CustomizeHome.recentlySaved.set(settings.showRecentBookmarksFeature)
891 CustomizeHome.mostVisitedSites.set(settings.showTopSitesFeature)
892 CustomizeHome.recentlyVisited.set(settings.historyMetadataUIFeature)
893 CustomizeHome.pocket.set(settings.showPocketRecommendationsFeature)
894 CustomizeHome.sponsoredPocket.set(settings.showPocketSponsoredStories)
895 CustomizeHome.contile.set(settings.showContileFeature)
898 protected fun recordOnInit() {
899 // This gets called by more than one process. Ideally we'd only run this in the main process
900 // but the code to check which process we're in crashes because the Context isn't valid yet.
902 // This method is not covered by our internal crash reporting: be very careful when modifying it.
903 StartupTimeline.onApplicationInit() // DO NOT MOVE ANYTHING ABOVE HERE: the timing is critical.
906 override fun onConfigurationChanged(config: android.content.res.Configuration) {
907 // Workaround for androidx appcompat issue where follow system day/night mode config changes
908 // are not triggered when also using createConfigurationContext like we do in LocaleManager
909 // https://issuetracker.google.com/issues/143570309#comment3
910 applicationContext.resources.configuration.uiMode = config.uiMode
912 if (isMainProcess()) {
913 // We can only do this on the main process as resetAfter will access components.core, which
914 // will initialize the engine and create an additional GeckoRuntime from the Gecko
915 // child process, causing a crash.
917 // There's a strict mode violation in A-Cs LocaleAwareApplication which
918 // reads from shared prefs: https://github.com/mozilla-mobile/android-components/issues/8816
919 components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
920 super.onConfigurationChanged(config)
923 super.onConfigurationChanged(config)
927 override fun getWorkManagerConfiguration() = Builder().setMinimumLoggingLevel(INFO).build()
929 @OptIn(DelicateCoroutinesApi::class)
930 open fun downloadWallpapers() {
932 components.useCases.wallpaperUseCases.initialize()