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.focus
7 import android.app.PendingIntent
8 import android.content.Context
9 import android.content.Intent
10 import android.os.Build
11 import androidx.compose.runtime.Composable
12 import androidx.compose.ui.platform.LocalContext
13 import mozilla.components.browser.engine.gecko.cookiebanners.GeckoCookieBannersStorage
14 import mozilla.components.browser.icons.BrowserIcons
15 import mozilla.components.browser.state.engine.EngineMiddleware
16 import mozilla.components.browser.state.store.BrowserStore
17 import mozilla.components.concept.engine.DefaultSettings
18 import mozilla.components.concept.engine.Engine
19 import mozilla.components.concept.fetch.Client
20 import mozilla.components.feature.app.links.AppLinksInterceptor
21 import mozilla.components.feature.app.links.AppLinksUseCases
22 import mozilla.components.feature.contextmenu.ContextMenuUseCases
23 import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
24 import mozilla.components.feature.downloads.DownloadMiddleware
25 import mozilla.components.feature.downloads.DownloadsUseCases
26 import mozilla.components.feature.media.MediaSessionFeature
27 import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
28 import mozilla.components.feature.prompts.PromptMiddleware
29 import mozilla.components.feature.search.SearchUseCases
30 import mozilla.components.feature.search.middleware.AdsTelemetryMiddleware
31 import mozilla.components.feature.search.middleware.SearchMiddleware
32 import mozilla.components.feature.search.region.RegionMiddleware
33 import mozilla.components.feature.search.telemetry.ads.AdsTelemetry
34 import mozilla.components.feature.search.telemetry.incontent.InContentTelemetry
35 import mozilla.components.feature.session.SessionUseCases
36 import mozilla.components.feature.session.SettingsUseCases
37 import mozilla.components.feature.session.TrackingProtectionUseCases
38 import mozilla.components.feature.tabs.CustomTabsUseCases
39 import mozilla.components.feature.tabs.TabsUseCases
40 import mozilla.components.feature.top.sites.PinnedSiteStorage
41 import mozilla.components.feature.top.sites.TopSitesUseCases
42 import mozilla.components.feature.webcompat.WebCompatFeature
43 import mozilla.components.feature.webcompat.reporter.WebCompatReporterFeature
44 import mozilla.components.lib.crash.CrashReporter
45 import mozilla.components.lib.crash.sentry.SentryService
46 import mozilla.components.lib.crash.service.CrashReporterService
47 import mozilla.components.lib.crash.service.GleanCrashReporterService
48 import mozilla.components.lib.crash.service.MozillaSocorroService
49 import mozilla.components.lib.publicsuffixlist.PublicSuffixList
50 import mozilla.components.service.location.LocationService
51 import mozilla.components.service.location.MozillaLocationService
52 import mozilla.components.service.nimbus.NimbusApi
53 import mozilla.components.support.locale.LocaleManager
54 import org.mozilla.focus.activity.MainActivity
55 import org.mozilla.focus.browser.BlockedTrackersMiddleware
56 import org.mozilla.focus.cfr.CfrMiddleware
57 import org.mozilla.focus.components.EngineProvider
58 import org.mozilla.focus.downloads.DownloadService
59 import org.mozilla.focus.engine.AppContentInterceptor
60 import org.mozilla.focus.engine.ClientWrapper
61 import org.mozilla.focus.engine.SanityCheckMiddleware
62 import org.mozilla.focus.experiments.createNimbus
63 import org.mozilla.focus.ext.components
64 import org.mozilla.focus.ext.settings
65 import org.mozilla.focus.media.MediaSessionService
66 import org.mozilla.focus.notification.PrivateNotificationMiddleware
67 import org.mozilla.focus.search.SearchFilterMiddleware
68 import org.mozilla.focus.search.SearchMigration
69 import org.mozilla.focus.state.AppState
70 import org.mozilla.focus.state.AppStore
71 import org.mozilla.focus.state.Screen
72 import org.mozilla.focus.telemetry.GleanMetricsService
73 import org.mozilla.focus.telemetry.TelemetryMiddleware
74 import org.mozilla.focus.telemetry.startuptelemetry.AppStartReasonProvider
75 import org.mozilla.focus.telemetry.startuptelemetry.StartupActivityLog
76 import org.mozilla.focus.telemetry.startuptelemetry.StartupStateProvider
77 import org.mozilla.focus.topsites.DefaultTopSitesStorage
78 import org.mozilla.focus.utils.Settings
79 import java.util.Locale
82 * Helper object for lazily initializing components.
86 private val engineOverride: Engine? = null,
87 private val clientOverride: Client? = null,
89 val appStore: AppStore by lazy {
92 screen = if (context.settings.isFirstRun) Screen.FirstRun else Screen.Home,
93 topSites = emptyList(),
98 val appStartReasonProvider by lazy { AppStartReasonProvider() }
100 val startupActivityLog by lazy { StartupActivityLog() }
102 val startupStateProvider by lazy { StartupStateProvider(startupActivityLog, appStartReasonProvider) }
104 val settings by lazy { Settings(context) }
106 val engineDefaultSettings by lazy {
108 requestInterceptor = AppContentInterceptor(context),
109 trackingProtectionPolicy = settings.createTrackingProtectionPolicy(),
110 javascriptEnabled = !settings.shouldBlockJavaScript(),
111 remoteDebuggingEnabled = settings.shouldEnableRemoteDebugging(),
112 webFontsEnabled = !settings.shouldBlockWebFonts(),
113 httpsOnlyMode = settings.getHttpsOnlyMode(),
114 preferredColorScheme = settings.getPreferredColorScheme(),
115 cookieBannerHandlingModePrivateBrowsing = settings.getCurrentCookieBannerOptionFromSharePref().mode,
119 val engine: Engine by lazy {
120 engineOverride ?: EngineProvider.createEngine(context, engineDefaultSettings).apply {
121 this@Components.settings.setupSafeBrowsing(this)
122 WebCompatFeature.install(this)
123 WebCompatReporterFeature.install(this, "focus-geckoview")
127 val client: ClientWrapper by lazy {
128 ClientWrapper(clientOverride ?: EngineProvider.createClient(context))
131 val trackingProtectionUseCases by lazy { TrackingProtectionUseCases(store, engine) }
133 val settingsUseCases by lazy { SettingsUseCases(engine, store) }
135 @Suppress("DEPRECATION")
136 private val locationService: LocationService by lazy {
137 if (BuildConfig.MLS_TOKEN.isEmpty()) {
138 LocationService.default()
140 MozillaLocationService(context, client.unwrap(), BuildConfig.MLS_TOKEN)
147 PrivateNotificationMiddleware(context),
148 TelemetryMiddleware(),
149 DownloadMiddleware(context, DownloadService::class.java),
150 SanityCheckMiddleware(),
151 // We are currently using the default location service. We should consider using
152 // an actual implementation:
153 // https://github.com/mozilla-mobile/focus-android/issues/4781
154 RegionMiddleware(context, locationService),
155 SearchMiddleware(context, migration = SearchMigration(context)),
156 SearchFilterMiddleware(),
158 AdsTelemetryMiddleware(adsTelemetry),
159 BlockedTrackersMiddleware(context),
160 RecordingDevicesMiddleware(context),
161 CfrMiddleware(context),
162 ) + EngineMiddleware.create(
164 // We are disabling automatic suspending of engine sessions under memory pressure.
165 // Instead we solely rely on GeckoView and the Android system to reclaim memory
166 // when needed. For details, see:
167 // https://bugzilla.mozilla.org/show_bug.cgi?id=1752594
168 // https://github.com/mozilla-mobile/fenix/issues/12731
169 // https://github.com/mozilla-mobile/android-components/issues/11300
170 // https://github.com/mozilla-mobile/android-components/issues/11653
171 trimMemoryAutomatically = false,
174 MediaSessionFeature(context, MediaSessionService::class.java, this).start()
179 * The [CustomTabsServiceStore] holds global custom tabs related data.
181 val customTabsStore by lazy { CustomTabsServiceStore() }
183 val sessionUseCases: SessionUseCases by lazy { SessionUseCases(store) }
185 val tabsUseCases: TabsUseCases by lazy { TabsUseCases(store) }
187 val cookieBannerStorage: GeckoCookieBannersStorage by lazy { EngineProvider.createCookieBannerStorage(context) }
189 val publicSuffixList by lazy { PublicSuffixList(context) }
191 val searchUseCases: SearchUseCases by lazy {
192 SearchUseCases(store, tabsUseCases, sessionUseCases)
195 val contextMenuUseCases: ContextMenuUseCases by lazy { ContextMenuUseCases(store) }
197 val downloadsUseCases: DownloadsUseCases by lazy { DownloadsUseCases(store) }
199 val appLinksUseCases: AppLinksUseCases by lazy { AppLinksUseCases(context.applicationContext) }
201 val customTabsUseCases: CustomTabsUseCases by lazy { CustomTabsUseCases(store, sessionUseCases.loadUrl) }
203 val crashReporter: CrashReporter by lazy { createCrashReporter(context) }
205 val metrics: GleanMetricsService by lazy { GleanMetricsService(context) }
207 val experiments: NimbusApi by lazy {
208 createNimbus(context, BuildConfig.NIMBUS_ENDPOINT)
211 val adsTelemetry: AdsTelemetry by lazy { AdsTelemetry() }
213 val searchTelemetry: InContentTelemetry by lazy { InContentTelemetry() }
215 val icons by lazy { BrowserIcons(context, client) }
217 val topSitesStorage by lazy { DefaultTopSitesStorage(PinnedSiteStorage(context)) }
219 val topSitesUseCases: TopSitesUseCases by lazy { TopSitesUseCases(topSitesStorage) }
221 val appLinksInterceptor by lazy {
224 interceptLinkClicks = true,
226 context.settings.openLinksInExternalApp
232 private fun createCrashReporter(context: Context): CrashReporter {
233 val services = mutableListOf<CrashReporterService>()
235 if (BuildConfig.SENTRY_TOKEN.isNotEmpty()) {
236 val sentryService = SentryService(
238 BuildConfig.SENTRY_TOKEN,
240 "build_flavor" to BuildConfig.FLAVOR,
241 "build_type" to BuildConfig.BUILD_TYPE,
242 "locale_lang_tag" to getLocaleTag(context),
244 environment = BuildConfig.BUILD_TYPE,
245 sendEventForNativeCrashes = false, // Do not send native crashes to Sentry
248 services.add(sentryService)
251 val socorroService = MozillaSocorroService(
254 version = org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION,
255 buildId = org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID,
256 vendor = org.mozilla.geckoview.BuildConfig.MOZ_APP_VENDOR,
257 releaseChannel = org.mozilla.geckoview.BuildConfig.MOZ_UPDATE_CHANNEL,
259 services.add(socorroService)
261 val intent = Intent(context, MainActivity::class.java).apply {
262 flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
265 val crashReportingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
266 PendingIntent.FLAG_MUTABLE
268 0 // No flags. Default behavior.
271 val pendingIntent = PendingIntent.getActivity(
275 crashReportingIntentFlags,
278 return CrashReporter(
281 telemetryServices = listOf(GleanCrashReporterService(context)),
282 promptConfiguration = CrashReporter.PromptConfiguration(
283 appName = context.resources.getString(R.string.app_name),
285 shouldPrompt = CrashReporter.Prompt.ALWAYS,
287 nonFatalCrashIntent = pendingIntent,
291 private fun getLocaleTag(context: Context): String {
292 val currentLocale = LocaleManager.getCurrentLocale(context)
293 return if (currentLocale != null) {
294 currentLocale.toLanguageTag()
296 Locale.getDefault().toLanguageTag()
301 * Returns the [Components] object from within a [Composable].
303 val components: Components
305 get() = LocalContext.current.components