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 Aug 23, 2019
1 parent 66e1495 commit e108a5c
Show file tree
Hide file tree
Showing 18 changed files with 187 additions and 48 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ dependencies {
implementation Deps.mozilla_feature_media
implementation Deps.mozilla_feature_prompts
implementation Deps.mozilla_feature_push
implementation Deps.mozilla_feature_pwa
implementation Deps.mozilla_feature_qr
implementation Deps.mozilla_feature_search
implementation Deps.mozilla_feature_session
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,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 @@ -108,6 +108,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
2 changes: 2 additions & 0 deletions app/src/main/java/org/mozilla/fenix/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
)
}

open fun getDirections(customTabSessionId: String?) = NavGraphDirections.actionGlobalBrowser(customTabSessionId)

private fun load(
searchTermOrURL: String,
newTab: Boolean,
Expand Down
27 changes: 16 additions & 11 deletions app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.customtabs.AuthCustomTabActivity
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.utils.Settings

/**
* 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")
private fun tabIntentProcessor(isPrivate: Boolean) =
if (isPrivate) components.utils.privateIntentProcessor else components.utils.intentProcessor

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -40,17 +45,14 @@ class IntentReceiverActivity : Activity() {
// the HomeActivity.
val intent = intent?.let { Intent(intent) } ?: Intent()

val intentProcessors = listOf(
components.utils.customTabIntentProcessor,
if (isPrivate) components.utils.privateIntentProcessor else components.utils.intentProcessor
)
val intentProcessors = components.utils.externalAppIntentProcessors + tabIntentProcessor(isPrivate)

if (intent.getBooleanExtra(SPEECH_PROCESSING, false)) {
previousIntent = intent
displaySpeechRecognizer()
} else {
intentProcessors.any { it.process(intent) }
setIntentActivity(intent)
setIntentActivity(intent, isPrivate)

startActivity(intent)

Expand All @@ -59,18 +61,21 @@ class IntentReceiverActivity : Activity() {
}
}

private fun setIntentActivity(intent: Intent) {
/**
* Sets the activity that this [intent] will launch.
*/
private fun setIntentActivity(intent: Intent, isPrivate: Boolean) {
val openToBrowser = when {
components.utils.customTabIntentProcessor.matches(intent) -> {
components.utils.externalAppIntentProcessors.any { it.matches(intent) } -> {
val activityClass = if (intent.hasExtra(getString(R.string.intent_extra_auth))) {
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(isPrivate).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 @@ -323,15 +323,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 @@ -488,6 +480,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
4 changes: 3 additions & 1 deletion app/src/main/java/org/mozilla/fenix/components/Components.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ class Components(private val context: Context) {
val services by lazy { Services(backgroundServices.accountManager) }
val core by lazy { Core(context) }
val search by lazy { Search(context) }
val useCases by lazy { UseCases(context, core.sessionManager, core.engine.settings, search.searchEngineManager) }
val useCases by lazy {
UseCases(context, core.sessionManager, core.engine.settings, search.searchEngineManager, core.client)
}
val utils by lazy { Utilities(context, core.sessionManager, useCases.sessionUseCases, useCases.searchUseCases) }
val analytics by lazy { Analytics(context) }
val publicSuffixList by lazy { PublicSuffixList(context) }
Expand Down
7 changes: 6 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 @@ -8,7 +8,9 @@ import android.content.Context
import mozilla.components.browser.search.SearchEngineManager
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.Settings
import mozilla.components.concept.fetch.Client
import mozilla.components.feature.app.links.AppLinksUseCases
import mozilla.components.feature.pwa.WebAppUseCases
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.session.SettingsUseCases
Expand All @@ -24,7 +26,8 @@ class UseCases(
private val context: Context,
private val sessionManager: SessionManager,
private val engineSettings: Settings,
private val searchEngineManager: SearchEngineManager
private val searchEngineManager: SearchEngineManager,
private val httpClient: Client
) {
/**
* Use cases that provide engine interactions for a given browser session.
Expand All @@ -47,4 +50,6 @@ class UseCases(
val settingsUseCases by lazy { SettingsUseCases(engineSettings, sessionManager) }

val appLinksUseCases by lazy { AppLinksUseCases(context.applicationContext) }

val webAppUseCases by lazy { WebAppUseCases(context, sessionManager, httpClient) }
}
15 changes: 11 additions & 4 deletions app/src/main/java/org/mozilla/fenix/components/Utilities.kt
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.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 All @@ -23,18 +25,23 @@ class Utilities(
private val searchUseCases: SearchUseCases
) {
/**
* Provides intent processing functionality for CustomTab, ACTION_VIEW
* and ACTION_SEND intents.
* Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents.
*/
val intentProcessor by lazy {
TabIntentProcessor(sessionManager, sessionUseCases.loadUrl, searchUseCases.newTabSearch, isPrivate = false)
}

/**
* Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents in private tabs.
*/
val privateIntentProcessor by lazy {
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)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ sealed class Event {
enum class Item {
SETTINGS, LIBRARY, HELP, DESKTOP_VIEW_ON, DESKTOP_VIEW_OFF, FIND_IN_PAGE, NEW_TAB,
NEW_PRIVATE_TAB, SHARE, REPORT_SITE_ISSUE, BACK, FORWARD, RELOAD, STOP, OPEN_IN_FENIX,
SAVE_TO_COLLECTION
SAVE_TO_COLLECTION, ADD_TO_HOMESCREEN
}

override val extras: Map<Events.browserMenuActionKeys, String>?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import androidx.core.widget.NestedScrollView
import androidx.navigation.NavController
import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.launch
import mozilla.components.browser.session.Session
import mozilla.components.concept.engine.EngineView
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
Expand Down Expand Up @@ -86,6 +88,11 @@ class DefaultBrowserToolbarController(
item.isChecked,
currentSession
)
ToolbarMenu.Item.AddToHomescreen -> {
MainScope().launch {
context.components.useCases.webAppUseCases.addToHomescreen()
}
}
ToolbarMenu.Item.Share -> {
val currentUrl = currentSession?.url
currentUrl?.apply {
Expand Down Expand Up @@ -175,6 +182,7 @@ class DefaultBrowserToolbarController(
ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX
ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE
ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION
ToolbarMenu.Item.AddToHomescreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
}

context.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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 mozilla.components.browser.menu.item.SimpleBrowserMenuItem
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.theme.ThemeManager
Expand Down Expand Up @@ -133,10 +134,20 @@ class DefaultToolbarMenu(
onItemTapped.invoke(ToolbarMenu.Item.Library)
},

BrowserMenuSwitch(context.getString(R.string.browser_menu_desktop_site),
requestDesktopStateProvider, { checked ->
onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked))
}),
BrowserMenuSwitch(
context.getString(R.string.browser_menu_desktop_site),
requestDesktopStateProvider
) { checked ->
onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked))
},

SimpleBrowserMenuItem(
context.getString(R.string.browser_menu_add_to_homescreen)
) {
onItemTapped.invoke(ToolbarMenu.Item.AddToHomescreen)
}.apply {
visible = ::shouldShowAddToHomescreen
},

BrowserMenuImageText(
context.getString(R.string.browser_menu_find_in_page),
Expand Down Expand Up @@ -201,4 +212,9 @@ class DefaultToolbarMenu(

items
}

private fun shouldShowAddToHomescreen(): Boolean {
return context.components.useCases.webAppUseCases.isPinningSupported() &&
context.components.core.sessionManager.selectedSession != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface ToolbarMenu {
object ReportIssue : Item()
object OpenInFenix : Item()
object SaveToCollection : Item()
object AddToHomescreen : Item()
}

val menuBuilder: BrowserMenuBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ package org.mozilla.fenix.customtabs

import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.service.fxa.manager.FxaAccountManager
import org.mozilla.fenix.ext.components

class AuthCustomTabActivity : CustomTabActivity() {
private lateinit var accountManager: FxaAccountManager
/**
* A special custom tab for signing into a Firefox Account. The activity is closed once the user is signed in.
*/
class AuthCustomTabActivity : ExternalAppBrowserActivity() {

// Navigate away from this activity when we have successful authentication
private val accountStateObserver = object : AccountObserver {
/**
* Navigate away from this activity when we have successful authentication
*/
override fun onAuthenticated(account: OAuthAccount, newAccount: Boolean) {
this@AuthCustomTabActivity.finish()
finish()
}
}

override fun onResume() {
super.onResume()
accountManager = this.components.backgroundServices.accountManager
val accountManager = this.components.backgroundServices.accountManager
accountManager.register(accountStateObserver, this, true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
package org.mozilla.fenix.customtabs

import androidx.navigation.NavDestination
import androidx.navigation.NavDirections
import mozilla.components.browser.session.intent.getSessionId
import mozilla.components.concept.engine.manifest.WebAppManifestParser
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 @@ -15,7 +19,12 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.theme.CustomTabThemeManager
import java.security.InvalidParameterException

open class CustomTabActivity : HomeActivity() {
/**
* Activity that holds the [BrowserFragment] that is launched within an external app,
* such as custom tabs and progressive web apps.
*/
open class ExternalAppBrowserActivity : HomeActivity() {

final override fun getSentryBreadcrumbMessage(destination: NavDestination): String {
val fragmentName = resources.getResourceEntryName(destination.id)
return "Changing to fragment $fragmentName, isCustomTab: true"
Expand All @@ -28,12 +37,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
Loading

0 comments on commit e108a5c

Please sign in to comment.