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.customtabs
7 import android.content.Context
8 import android.content.Intent
9 import android.os.SystemClock
10 import android.view.View
11 import androidx.coordinatorlayout.widget.CoordinatorLayout
12 import androidx.core.view.isVisible
13 import androidx.navigation.fragment.navArgs
14 import kotlinx.android.synthetic.main.component_browser_top_toolbar.*
15 import kotlinx.android.synthetic.main.fragment_browser.*
16 import kotlinx.coroutines.ExperimentalCoroutinesApi
17 import mozilla.components.browser.state.state.SessionState
18 import mozilla.components.concept.engine.manifest.WebAppManifestParser
19 import mozilla.components.concept.engine.manifest.getOrNull
20 import mozilla.components.feature.contextmenu.ContextMenuCandidate
21 import mozilla.components.feature.customtabs.CustomTabWindowFeature
22 import mozilla.components.feature.pwa.feature.ManifestUpdateFeature
23 import mozilla.components.feature.pwa.feature.WebAppActivityFeature
24 import mozilla.components.feature.pwa.feature.WebAppHideToolbarFeature
25 import mozilla.components.feature.pwa.feature.WebAppSiteControlsFeature
26 import mozilla.components.feature.sitepermissions.SitePermissions
27 import mozilla.components.support.base.feature.UserInteractionHandler
28 import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
29 import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
30 import org.mozilla.fenix.BuildConfig
31 import org.mozilla.fenix.R
32 import org.mozilla.fenix.browser.BaseBrowserFragment
33 import org.mozilla.fenix.browser.CustomTabContextMenuCandidate
34 import org.mozilla.fenix.browser.FenixSnackbarDelegate
35 import org.mozilla.fenix.components.metrics.Event
36 import org.mozilla.fenix.ext.components
37 import org.mozilla.fenix.ext.nav
38 import org.mozilla.fenix.ext.requireComponents
39 import org.mozilla.fenix.ext.settings
42 * Fragment used for browsing the web within external apps.
44 @ExperimentalCoroutinesApi
45 class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
47 private val args by navArgs<ExternalAppBrowserFragmentArgs>()
49 private val customTabsIntegration = ViewBoundFeatureWrapper<CustomTabsIntegration>()
50 private val windowFeature = ViewBoundFeatureWrapper<CustomTabWindowFeature>()
51 private val hideToolbarFeature = ViewBoundFeatureWrapper<WebAppHideToolbarFeature>()
53 @Suppress("LongMethod", "ComplexMethod")
54 override fun initializeUI(view: View, tab: SessionState) {
55 super.initializeUI(view, tab)
57 val customTabSessionId = customTabSessionId ?: return
58 val activity = requireActivity()
59 val components = activity.components
60 val manifest = args.webAppManifest?.let { json -> WebAppManifestParser().parse(json).getOrNull() }
62 customTabsIntegration.set(
63 feature = CustomTabsIntegration(
64 store = requireComponents.core.store,
65 useCases = requireComponents.useCases.customTabsUseCases,
67 sessionId = customTabSessionId,
69 onItemTapped = { browserToolbarInteractor.onBrowserToolbarMenuItemTapped(it) },
70 isPrivate = tab.content.private,
71 shouldReverseItems = !activity.settings().shouldUseBottomToolbar
78 feature = CustomTabWindowFeature(
80 components.core.store,
84 Intent.parseUri("${BuildConfig.DEEP_LINK_SCHEME}://open?url=$uri", 0)
85 if (intent.action == Intent.ACTION_VIEW) {
86 intent.addCategory(Intent.CATEGORY_BROWSABLE)
87 intent.component = null
88 intent.selector = null
89 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
91 activity.startActivity(intent)
97 hideToolbarFeature.set(
98 feature = WebAppHideToolbarFeature(
99 store = requireComponents.core.store,
100 customTabsStore = requireComponents.core.customTabsStore,
101 tabId = customTabSessionId,
103 ) { toolbarVisible ->
104 browserToolbarView.view.isVisible = toolbarVisible
105 webAppToolbarShouldBeVisible = toolbarVisible
106 if (!toolbarVisible) {
107 engineView.setDynamicToolbarMaxHeight(0)
109 swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams
110 browserEngine.bottomMargin = 0
117 if (manifest != null) {
118 activity.lifecycle.addObservers(
119 WebAppActivityFeature(
121 components.core.icons,
124 ManifestUpdateFeature(
125 activity.applicationContext,
126 requireComponents.core.store,
127 requireComponents.core.webAppShortcutManager,
128 requireComponents.core.webAppManifestStorage,
133 viewLifecycleOwner.lifecycle.addObserver(
134 WebAppSiteControlsFeature(
135 activity.applicationContext,
136 requireComponents.core.store,
137 requireComponents.useCases.sessionUseCases.reload,
140 WebAppSiteControlsBuilder(
141 requireComponents.core.store,
142 requireComponents.useCases.sessionUseCases.reload,
149 viewLifecycleOwner.lifecycle.addObserver(
150 PoweredByNotification(
151 activity.applicationContext,
152 requireComponents.core.store,
159 override fun onResume() {
161 val currTimeMs = SystemClock.elapsedRealtimeNanos() / MS_PRECISION
162 requireComponents.analytics.metrics.track(
163 Event.ProgressiveWebAppForeground(currTimeMs)
167 override fun onPause() {
169 val currTimeMs = SystemClock.elapsedRealtimeNanos() / MS_PRECISION
170 requireComponents.analytics.metrics.track(
171 Event.ProgressiveWebAppBackground(currTimeMs)
175 override fun removeSessionIfNeeded(): Boolean {
176 return customTabsIntegration.onBackPressed() || super.removeSessionIfNeeded()
179 override fun navToQuickSettingsSheet(tab: SessionState, sitePermissions: SitePermissions?) {
180 val directions = ExternalAppBrowserFragmentDirections
181 .actionGlobalQuickSettingsSheetDialogFragment(
183 url = tab.content.url,
184 title = tab.content.title,
185 isSecured = tab.content.securityInfo.secure,
186 sitePermissions = sitePermissions,
187 gravity = getAppropriateLayoutGravity(),
188 certificateName = tab.content.securityInfo.issuer,
189 permissionHighlights = tab.content.permissionHighlights
191 nav(R.id.externalAppBrowserFragment, directions)
194 override fun navToTrackingProtectionPanel(tab: SessionState) {
195 requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
196 val isEnabled = tab.trackingProtection.enabled && !contains
198 ExternalAppBrowserFragmentDirections
199 .actionGlobalTrackingProtectionPanelDialogFragment(
201 url = tab.content.url,
202 trackingProtectionEnabled = isEnabled,
203 gravity = getAppropriateLayoutGravity()
205 nav(R.id.externalAppBrowserFragment, directions)
209 override fun getContextMenuCandidates(
212 ): List<ContextMenuCandidate> = CustomTabContextMenuCandidate.defaultCandidates(
214 context.components.useCases.contextMenuUseCases,
216 FenixSnackbarDelegate(view)
220 // We only care about millisecond precision for telemetry events
221 internal const val MS_PRECISION = 1_000_000L