Skip to content

Commit

Permalink
Closes mozilla-mobile#778 - Progressive Web Apps!
Browse files Browse the repository at this point in the history
  • Loading branch information
NotWoods committed Sep 19, 2019
1 parent 2db2d9c commit 9ccf3e9
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 42 deletions.
8 changes: 7 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
</activity>

<activity
android:name=".customtabs.CustomTabActivity"
android:name=".customtabs.ExternalAppBrowserActivity"
android:autoRemoveFromRecents="false"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
android:exported="false"
Expand Down Expand Up @@ -113,6 +113,12 @@
<data android:mimeType="text/plain" />
</intent-filter>

<intent-filter>
<action android:name="mozilla.components.feature.pwa.VIEW_PWA" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.ASSIST" />
<category android:name="android.intent.category.DEFAULT" />
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
28 changes: 17 additions & 11 deletions app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
)
}
}
5 changes: 4 additions & 1 deletion app/src/main/java/org/mozilla/fenix/components/UseCases.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,7 +37,10 @@ import org.mozilla.fenix.ext.requireComponents
@ExperimentalCoroutinesApi
class ExternalAppBrowserFragment : BaseBrowserFragment(), BackHandler {

private val args by navArgs<ExternalAppBrowserFragmentArgs>()

private val customTabsIntegration = ViewBoundFeatureWrapper<CustomTabsIntegration>()
private val hideToolbarFeature = ViewBoundFeatureWrapper<WebAppHideToolbarFeature>()

override fun initializeUI(view: View): Session? {
return super.initializeUI(view)?.also {
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -91,8 +145,13 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), BackHandler {
}

override fun getEngineMargins(): Pair<Int, Int> {
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
Expand Down
7 changes: 3 additions & 4 deletions app/src/main/res/navigation/nav_graph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,9 @@
android:id="@+id/externalAppBrowserFragment"
android:name="org.mozilla.fenix.customtabs.ExternalAppBrowserFragment"
tools:layout="@layout/fragment_browser">
<argument
android:name="activeSessionId"
app:argType="string"
app:nullable="true" />
<argument android:name="activeSessionId" app:argType="string" app:nullable="true" />
<argument android:name="webAppManifest" app:argType="string" app:nullable="true"/>
<argument android:name="trustedScopes" app:argType="string[]"/>
<action
android:id="@+id/action_externalAppBrowserFragment_to_shareFragment"
app:destination="@id/shareFragment" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 9ccf3e9

Please sign in to comment.