[fenix] For https://github.com/mozilla-mobile/fenix/issues/19876 - Part 1: Refactor...
[gecko.git] / mobile / android / fenix / app / src / main / java / org / mozilla / fenix / customtabs / ExternalAppBrowserFragment.kt
blobe89aed2d88c444e09e09d794d1036decf918ed3b
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
41 /**
42  * Fragment used for browsing the web within external apps.
43  */
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,
66                 toolbar = toolbar,
67                 sessionId = customTabSessionId,
68                 activity = activity,
69                 onItemTapped = { browserToolbarInteractor.onBrowserToolbarMenuItemTapped(it) },
70                 isPrivate = tab.content.private,
71                 shouldReverseItems = !activity.settings().shouldUseBottomToolbar
72             ),
73             owner = this,
74             view = view
75         )
77         windowFeature.set(
78             feature = CustomTabWindowFeature(
79                 activity,
80                 components.core.store,
81                 customTabSessionId
82             ) { uri ->
83                 val intent =
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
90                 }
91                 activity.startActivity(intent)
92             },
93             owner = this,
94             view = view
95         )
97         hideToolbarFeature.set(
98             feature = WebAppHideToolbarFeature(
99                 store = requireComponents.core.store,
100                 customTabsStore = requireComponents.core.customTabsStore,
101                 tabId = customTabSessionId,
102                 manifest = manifest
103             ) { toolbarVisible ->
104                 browserToolbarView.view.isVisible = toolbarVisible
105                 webAppToolbarShouldBeVisible = toolbarVisible
106                 if (!toolbarVisible) {
107                     engineView.setDynamicToolbarMaxHeight(0)
108                     val browserEngine =
109                         swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams
110                     browserEngine.bottomMargin = 0
111                 }
112             },
113             owner = this,
114             view = toolbar
115         )
117         if (manifest != null) {
118             activity.lifecycle.addObservers(
119                 WebAppActivityFeature(
120                     activity,
121                     components.core.icons,
122                     manifest
123                 ),
124                 ManifestUpdateFeature(
125                     activity.applicationContext,
126                     requireComponents.core.store,
127                     requireComponents.core.webAppShortcutManager,
128                     requireComponents.core.webAppManifestStorage,
129                     customTabSessionId,
130                     manifest
131                 )
132             )
133             viewLifecycleOwner.lifecycle.addObserver(
134                 WebAppSiteControlsFeature(
135                     activity.applicationContext,
136                     requireComponents.core.store,
137                     requireComponents.useCases.sessionUseCases.reload,
138                     customTabSessionId,
139                     manifest,
140                     WebAppSiteControlsBuilder(
141                         requireComponents.core.store,
142                         requireComponents.useCases.sessionUseCases.reload,
143                         customTabSessionId,
144                         manifest
145                     )
146                 )
147             )
148         } else {
149             viewLifecycleOwner.lifecycle.addObserver(
150                 PoweredByNotification(
151                     activity.applicationContext,
152                     requireComponents.core.store,
153                     customTabSessionId
154                 )
155             )
156         }
157     }
159     override fun onResume() {
160         super.onResume()
161         val currTimeMs = SystemClock.elapsedRealtimeNanos() / MS_PRECISION
162         requireComponents.analytics.metrics.track(
163             Event.ProgressiveWebAppForeground(currTimeMs)
164         )
165     }
167     override fun onPause() {
168         super.onPause()
169         val currTimeMs = SystemClock.elapsedRealtimeNanos() / MS_PRECISION
170         requireComponents.analytics.metrics.track(
171             Event.ProgressiveWebAppBackground(currTimeMs)
172         )
173     }
175     override fun removeSessionIfNeeded(): Boolean {
176         return customTabsIntegration.onBackPressed() || super.removeSessionIfNeeded()
177     }
179     override fun navToQuickSettingsSheet(tab: SessionState, sitePermissions: SitePermissions?) {
180         val directions = ExternalAppBrowserFragmentDirections
181             .actionGlobalQuickSettingsSheetDialogFragment(
182                 sessionId = tab.id,
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
190             )
191         nav(R.id.externalAppBrowserFragment, directions)
192     }
194     override fun navToTrackingProtectionPanel(tab: SessionState) {
195         requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
196             val isEnabled = tab.trackingProtection.enabled && !contains
197             val directions =
198                 ExternalAppBrowserFragmentDirections
199                     .actionGlobalTrackingProtectionPanelDialogFragment(
200                         sessionId = tab.id,
201                         url = tab.content.url,
202                         trackingProtectionEnabled = isEnabled,
203                         gravity = getAppropriateLayoutGravity()
204                     )
205             nav(R.id.externalAppBrowserFragment, directions)
206         }
207     }
209     override fun getContextMenuCandidates(
210         context: Context,
211         view: View
212     ): List<ContextMenuCandidate> = CustomTabContextMenuCandidate.defaultCandidates(
213         context,
214         context.components.useCases.contextMenuUseCases,
215         view,
216         FenixSnackbarDelegate(view)
217     )
219     companion object {
220         // We only care about millisecond precision for telemetry events
221         internal const val MS_PRECISION = 1_000_000L
222     }