[focus] For #1797578 - Create toggle option in the toolbar that allow a site to be...
[gecko.git] / mobile / android / focus-android / app / src / main / java / org / mozilla / focus / Components.kt
blob945c1f50895664514f11e69d56167782b2cd91ea
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
81 /**
82  * Helper object for lazily initializing components.
83  */
84 class Components(
85     context: Context,
86     private val engineOverride: Engine? = null,
87     private val clientOverride: Client? = null,
88 ) {
89     val appStore: AppStore by lazy {
90         AppStore(
91             AppState(
92                 screen = if (context.settings.isFirstRun) Screen.FirstRun else Screen.Home,
93                 topSites = emptyList(),
94             ),
95         )
96     }
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 {
107         DefaultSettings(
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,
116         )
117     }
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")
124         }
125     }
127     val client: ClientWrapper by lazy {
128         ClientWrapper(clientOverride ?: EngineProvider.createClient(context))
129     }
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()
139         } else {
140             MozillaLocationService(context, client.unwrap(), BuildConfig.MLS_TOKEN)
141         }
142     }
144     val store by lazy {
145         BrowserStore(
146             middleware = listOf(
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(),
157                 PromptMiddleware(),
158                 AdsTelemetryMiddleware(adsTelemetry),
159                 BlockedTrackersMiddleware(context),
160                 RecordingDevicesMiddleware(context),
161                 CfrMiddleware(context),
162             ) + EngineMiddleware.create(
163                 engine,
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,
172             ),
173         ).apply {
174             MediaSessionFeature(context, MediaSessionService::class.java, this).start()
175         }
176     }
178     /**
179      * The [CustomTabsServiceStore] holds global custom tabs related data.
180      */
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)
193     }
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)
209     }
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 {
222         AppLinksInterceptor(
223             context,
224             interceptLinkClicks = true,
225             launchInApp = {
226                 context.settings.openLinksInExternalApp
227             },
228         )
229     }
232 private fun createCrashReporter(context: Context): CrashReporter {
233     val services = mutableListOf<CrashReporterService>()
235     if (BuildConfig.SENTRY_TOKEN.isNotEmpty()) {
236         val sentryService = SentryService(
237             context,
238             BuildConfig.SENTRY_TOKEN,
239             tags = mapOf(
240                 "build_flavor" to BuildConfig.FLAVOR,
241                 "build_type" to BuildConfig.BUILD_TYPE,
242                 "locale_lang_tag" to getLocaleTag(context),
243             ),
244             environment = BuildConfig.BUILD_TYPE,
245             sendEventForNativeCrashes = false, // Do not send native crashes to Sentry
246         )
248         services.add(sentryService)
249     }
251     val socorroService = MozillaSocorroService(
252         context,
253         appName = "Focus",
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,
258     )
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
263     }
265     val crashReportingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
266         PendingIntent.FLAG_MUTABLE
267     } else {
268         0 // No flags. Default behavior.
269     }
271     val pendingIntent = PendingIntent.getActivity(
272         context,
273         0,
274         intent,
275         crashReportingIntentFlags,
276     )
278     return CrashReporter(
279         context = context,
280         services = services,
281         telemetryServices = listOf(GleanCrashReporterService(context)),
282         promptConfiguration = CrashReporter.PromptConfiguration(
283             appName = context.resources.getString(R.string.app_name),
284         ),
285         shouldPrompt = CrashReporter.Prompt.ALWAYS,
286         enabled = true,
287         nonFatalCrashIntent = pendingIntent,
288     )
291 private fun getLocaleTag(context: Context): String {
292     val currentLocale = LocaleManager.getCurrentLocale(context)
293     return if (currentLocale != null) {
294         currentLocale.toLanguageTag()
295     } else {
296         Locale.getDefault().toLanguageTag()
297     }
301  * Returns the [Components] object from within a [Composable].
302  */
303 val components: Components
304     @Composable
305     get() = LocalContext.current.components