diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c29b36552a47..4bdd7a7263e0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -66,7 +66,7 @@
+
+
+
+
+
+
diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
index 443263e49f55..2d2213e36444 100644
--- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
+++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
@@ -52,4 +52,9 @@ object FeatureFlags {
* Gives option in Settings to Delete Browsing Data on new menu option Quit
*/
val deleteDataOnQuit = nightly or debug
+
+ /**
+ * Allows Progressive Web Apps to be installed to the device home screen.
+ */
+ val progressiveWebApps = nightly or debug
}
diff --git a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt
index 25642d3520bb..a3821b5396e5 100644
--- a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt
+++ b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt
@@ -10,21 +10,24 @@ import android.os.Bundle
import android.speech.RecognizerIntent
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
+import mozilla.components.feature.intent.processing.TabIntentProcessor
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.customtabs.AuthCustomTabActivity
import org.mozilla.fenix.customtabs.AuthCustomTabActivity.Companion.EXTRA_AUTH_CUSTOM_TAB
-import org.mozilla.fenix.customtabs.CustomTabActivity
+import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.home.intent.StartSearchIntentProcessor
+/**
+ * Processes incoming intents and sends them to the corresponding activity.
+ */
class IntentReceiverActivity : Activity() {
// Holds the intent that initially started this activity
// so that it can persist through the speech activity.
private var previousIntent: Intent? = null
- @Suppress("ComplexMethod")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -39,17 +42,17 @@ class IntentReceiverActivity : Activity() {
// the HomeActivity.
val intent = intent?.let { Intent(intent) } ?: Intent()
- val intentProcessors = listOf(
- components.intentProcessors.customTabIntentProcessor,
- components.intentProcessors.intentProcessor
- )
+ val tabIntentProcessor = components.intentProcessors.intentProcessor
+
+ val intentProcessors = components.intentProcessors.externalAppIntentProcessors +
+ tabIntentProcessor
if (intent.getBooleanExtra(SPEECH_PROCESSING, false)) {
previousIntent = intent
displaySpeechRecognizer()
} else {
intentProcessors.any { it.process(intent) }
- setIntentActivity(intent)
+ setIntentActivity(intent, tabIntentProcessor)
startActivity(intent)
@@ -58,19 +61,22 @@ class IntentReceiverActivity : Activity() {
}
}
- private fun setIntentActivity(intent: Intent) {
+ /**
+ * Sets the activity that this [intent] will launch.
+ */
+ private fun setIntentActivity(intent: Intent, tabIntentProcessor: TabIntentProcessor) {
val openToBrowser = when {
- components.intentProcessors.customTabIntentProcessor.matches(intent) -> {
+ components.intentProcessors.externalAppIntentProcessors.any { it.matches(intent) } -> {
// TODO this needs to change: https://github.com/mozilla-mobile/fenix/issues/5225
val activityClass = if (intent.hasExtra(EXTRA_AUTH_CUSTOM_TAB)) {
AuthCustomTabActivity::class
} else {
- CustomTabActivity::class
+ ExternalAppBrowserActivity::class
}
intent.setClassName(applicationContext, activityClass.java.name)
true
}
- intent.action == Intent.ACTION_VIEW || intent.action == Intent.ACTION_SEND -> {
+ tabIntentProcessor.matches(intent) -> {
intent.setClassName(applicationContext, HomeActivity::class.java.name)
// This Intent was launched from history (recent apps). Android will redeliver the
// original Intent (which might be a VIEW intent). However if there's no active browsing
diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
index bef44d40bd45..79c736f31863 100644
--- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
@@ -344,15 +344,7 @@ abstract class BaseBrowserFragment : Fragment(), BackHandler, SessionManager.Obs
toolbar.visibility = View.VISIBLE
nestedScrollQuickAction.visibility = View.VISIBLE
}
- view.swipeRefresh.apply {
- val (topMargin, bottomMargin) = if (inFullScreen) 0 to 0 else getEngineMargins()
- (layoutParams as CoordinatorLayout.LayoutParams).setMargins(
- 0,
- topMargin,
- 0,
- bottomMargin
- )
- }
+ updateLayoutMargins(inFullScreen)
},
owner = this,
view = view
@@ -526,6 +518,13 @@ abstract class BaseBrowserFragment : Fragment(), BackHandler, SessionManager.Obs
*/
protected abstract fun getAppropriateLayoutGravity(): Int
+ protected fun updateLayoutMargins(inFullScreen: Boolean) {
+ view?.swipeRefresh?.apply {
+ val (topMargin, bottomMargin) = if (inFullScreen) 0 to 0 else getEngineMargins()
+ (layoutParams as CoordinatorLayout.LayoutParams).setMargins(0, topMargin, 0, bottomMargin)
+ }
+ }
+
/**
* Updates the site permissions rules based on user settings.
*/
diff --git a/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt b/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt
index 99f7720babc4..59e7c14c9bab 100644
--- a/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt
@@ -8,6 +8,8 @@ import android.content.Context
import mozilla.components.browser.session.SessionManager
import mozilla.components.feature.customtabs.CustomTabIntentProcessor
import mozilla.components.feature.intent.processing.TabIntentProcessor
+import mozilla.components.feature.pwa.ManifestStorage
+import mozilla.components.feature.pwa.intent.WebAppIntentProcessor
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionUseCases
import org.mozilla.fenix.test.Mockable
@@ -36,7 +38,10 @@ class IntentProcessors(
TabIntentProcessor(sessionManager, sessionUseCases.loadUrl, searchUseCases.newTabSearch, isPrivate = true)
}
- val customTabIntentProcessor by lazy {
- CustomTabIntentProcessor(sessionManager, sessionUseCases.loadUrl, context.resources)
+ val externalAppIntentProcessors by lazy {
+ listOf(
+ WebAppIntentProcessor(sessionManager, sessionUseCases.loadUrl, ManifestStorage(context)),
+ CustomTabIntentProcessor(sessionManager, sessionUseCases.loadUrl, context.resources)
+ )
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt
index 850c9e907de9..2b6a6e591392 100644
--- a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt
@@ -17,6 +17,7 @@ import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.session.SettingsUseCases
import mozilla.components.feature.tabs.TabsUseCases
+import org.mozilla.fenix.FeatureFlags.progressiveWebApps
import org.mozilla.fenix.test.Mockable
/**
@@ -53,7 +54,9 @@ class UseCases(
val appLinksUseCases by lazy { AppLinksUseCases(context.applicationContext) }
- val webAppUseCases by lazy { WebAppUseCases(context, sessionManager, httpClient, supportWebApps = false) }
+ val webAppUseCases by lazy {
+ WebAppUseCases(context, sessionManager, httpClient, supportWebApps = progressiveWebApps)
+ }
val downloadUseCases by lazy { DownloadsUseCases(sessionManager) }
diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt
index 6c7a8d618e55..5003aaff843a 100644
--- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt
@@ -11,9 +11,9 @@ import mozilla.components.browser.menu.item.BrowserMenuHighlightableItem
import mozilla.components.browser.menu.item.BrowserMenuImageText
import mozilla.components.browser.menu.item.BrowserMenuItemToolbar
import mozilla.components.browser.menu.item.BrowserMenuSwitch
+import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
-import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.theme.ThemeManager
diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/AuthCustomTabActivity.kt b/app/src/main/java/org/mozilla/fenix/customtabs/AuthCustomTabActivity.kt
index 8e74944d4a68..cf1e30f560ac 100644
--- a/app/src/main/java/org/mozilla/fenix/customtabs/AuthCustomTabActivity.kt
+++ b/app/src/main/java/org/mozilla/fenix/customtabs/AuthCustomTabActivity.kt
@@ -12,7 +12,7 @@ import org.mozilla.fenix.ext.components
/**
* A special custom tab for signing into a Firefox Account. The activity is closed once the user is signed in.
*/
-class AuthCustomTabActivity : CustomTabActivity() {
+class AuthCustomTabActivity : ExternalAppBrowserActivity() {
private val accountStateObserver = object : AccountObserver {
/**
diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabActivity.kt b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
similarity index 65%
rename from app/src/main/java/org/mozilla/fenix/customtabs/CustomTabActivity.kt
rename to app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
index cf3e162d0ad6..1d2fc1f0a5c6 100644
--- a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabActivity.kt
+++ b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
@@ -5,8 +5,12 @@
package org.mozilla.fenix.customtabs
import androidx.navigation.NavDestination
+import androidx.navigation.NavDirections
import mozilla.components.browser.session.runWithSession
+import mozilla.components.concept.engine.manifest.WebAppManifestParser
import mozilla.components.feature.intent.ext.getSessionId
+import mozilla.components.feature.pwa.ext.getTrustedScope
+import mozilla.components.feature.pwa.ext.getWebAppManifest
import mozilla.components.support.utils.SafeIntent
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
@@ -17,7 +21,12 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.theme.CustomTabThemeManager
import java.security.InvalidParameterException
-open class CustomTabActivity : HomeActivity() {
+/**
+ * Activity that holds the [ExternalAppBrowserFragment] that is launched within an external app,
+ * such as custom tabs and progressive web apps.
+ */
+open class ExternalAppBrowserActivity : HomeActivity() {
+
final override fun getBreadcrumbMessage(destination: NavDestination): String {
val fragmentName = resources.getResourceEntryName(destination.id)
return "Changing to fragment $fragmentName, isCustomTab: true"
@@ -30,12 +39,23 @@ open class CustomTabActivity : HomeActivity() {
override fun getNavDirections(
from: BrowserDirection,
customTabSessionId: String?
- ) = when (from) {
- BrowserDirection.FromGlobal ->
- NavGraphDirections.actionGlobalExternalAppBrowser(customTabSessionId)
- else -> throw InvalidParameterException(
- "Tried to navigate to ExternalAppBrowserFragment from $from"
- )
+ ): NavDirections {
+ val manifest = intent.getWebAppManifest()
+ val manifestJson = manifest?.let { WebAppManifestParser().serialize(it).toString() }
+ val trustedScopes = listOfNotNull(
+ manifest?.getTrustedScope()?.toString()
+ ).toTypedArray()
+ return when (from) {
+ BrowserDirection.FromGlobal ->
+ NavGraphDirections.actionGlobalExternalAppBrowser(
+ activeSessionId = customTabSessionId,
+ webAppManifest = manifestJson,
+ trustedScopes = trustedScopes
+ )
+ else -> throw InvalidParameterException(
+ "Tried to navigate to ExternalAppBrowserFragment from $from"
+ )
+ }
}
final override fun createBrowsingModeManager() =
diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt
index 03d2805621ab..a7deb8f622d0 100644
--- a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt
@@ -6,11 +6,19 @@ package org.mozilla.fenix.customtabs
import android.view.Gravity
import android.view.View
+import androidx.core.net.toUri
+import androidx.core.view.isGone
+import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.component_search.*
import kotlinx.android.synthetic.main.fragment_browser.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.browser.session.Session
+import mozilla.components.concept.engine.manifest.WebAppManifestParser
+import mozilla.components.concept.engine.manifest.getOrNull
+import mozilla.components.feature.pwa.feature.WebAppActivityFeature
+import mozilla.components.feature.pwa.feature.WebAppHideToolbarFeature
+import mozilla.components.feature.pwa.feature.WebAppSiteControlsFeature
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.BackHandler
@@ -29,7 +37,10 @@ import org.mozilla.fenix.ext.requireComponents
@ExperimentalCoroutinesApi
class ExternalAppBrowserFragment : BaseBrowserFragment(), BackHandler {
+ private val args by navArgs()
+
private val customTabsIntegration = ViewBoundFeatureWrapper()
+ private val hideToolbarFeature = ViewBoundFeatureWrapper()
override fun initializeUI(view: View): Session? {
return super.initializeUI(view)?.also {
@@ -41,18 +52,61 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), BackHandler {
requireComponents.core.sessionManager,
toolbar,
customTabSessionId,
- activity,
+ activity!!,
view.nestedScrollQuickAction,
view.swipeRefresh,
onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) }
),
owner = this,
view = view)
+
+ val trustedScopes = args.trustedScopes.toList().map { it.toUri() }
+ hideToolbarFeature.set(
+ feature = WebAppHideToolbarFeature(
+ requireComponents.core.sessionManager,
+ toolbar,
+ customTabSessionId,
+ trustedScopes
+ ),
+ owner = this,
+ view = toolbar)
+ // Hot-fix until there's a hideToolbarFeature.onHideStateChanged
+ if (trustedScopes.isNotEmpty()) {
+ getSessionById()?.register(object : Session.Observer {
+ override fun onUrlChanged(session: Session, url: String) {
+ updateLayoutMargins(false)
+ }
+ })
+ }
+
+ val manifest = args.webAppManifest?.let { json ->
+ WebAppManifestParser().parse(json).getOrNull()
+ }
+ if (manifest != null) {
+ activity?.lifecycle?.addObserver(
+ WebAppActivityFeature(
+ activity!!,
+ requireComponents.core.icons,
+ manifest
+ )
+ )
+ activity?.lifecycle?.addObserver(
+ WebAppSiteControlsFeature(
+ context?.applicationContext!!,
+ requireComponents.core.sessionManager,
+ requireComponents.useCases.sessionUseCases.reload,
+ customTabSessionId,
+ manifest
+ )
+ )
+ }
}
consumeFrom(browserStore) {
browserToolbarView.update(it)
}
+
+ updateLayoutMargins(false)
}
}
@@ -91,8 +145,13 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), BackHandler {
}
override fun getEngineMargins(): Pair {
- val toolbarSize = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height)
- return toolbarSize to 0
+ val toolbarHidden = toolbar.isGone
+ return if (toolbarHidden) {
+ 0 to 0
+ } else {
+ val toolbarSize = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height)
+ toolbarSize to 0
+ }
}
override fun getAppropriateLayoutGravity() = Gravity.TOP
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
index 36a70dae92a7..4a83451fe076 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -202,10 +202,9 @@
android:id="@+id/externalAppBrowserFragment"
android:name="org.mozilla.fenix.customtabs.ExternalAppBrowserFragment"
tools:layout="@layout/fragment_browser">
-
+
+
+
diff --git a/app/src/test/java/org/mozilla/fenix/customtabs/CustomTabActivityTest.kt b/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt
similarity index 93%
rename from app/src/test/java/org/mozilla/fenix/customtabs/CustomTabActivityTest.kt
rename to app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt
index 04ebd6c1a4ae..6eeea7e4f49a 100644
--- a/app/src/test/java/org/mozilla/fenix/customtabs/CustomTabActivityTest.kt
+++ b/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt
@@ -18,11 +18,11 @@ import org.robolectric.annotation.Config
@ObsoleteCoroutinesApi
@RunWith(RobolectricTestRunner::class)
@Config(application = TestApplication::class)
-class CustomTabActivityTest {
+class ExternalAppBrowserActivityTest {
@Test
fun getIntentSource() {
- val activity = CustomTabActivity()
+ val activity = ExternalAppBrowserActivity()
val launcherIntent = Intent(Intent.ACTION_MAIN).apply {
addCategory(Intent.CATEGORY_LAUNCHER)