Closes #4979 - Support Fennec web apps (PWAs).
parent
eec8ea980b
commit
5515f1ba5e
|
@ -59,6 +59,15 @@
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
|
<!-- Activity alias from Fennec used by PWA launchers on the home screen -->
|
||||||
|
<activity-alias
|
||||||
|
android:name="org.mozilla.gecko.LauncherActivity"
|
||||||
|
android:targetActivity=".IntentReceiverActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.mozilla.gecko.WEBAPP" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".HomeActivity"
|
android:name=".HomeActivity"
|
||||||
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|layoutDirection|smallestScreenSize|screenLayout"
|
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|layoutDirection|smallestScreenSize|screenLayout"
|
||||||
|
|
|
@ -54,7 +54,8 @@ class Components(private val context: Context) {
|
||||||
useCases.searchUseCases,
|
useCases.searchUseCases,
|
||||||
core.client,
|
core.client,
|
||||||
core.customTabsStore,
|
core.customTabsStore,
|
||||||
migrationStore
|
migrationStore,
|
||||||
|
core.webAppManifestStorage
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,13 +11,14 @@ import mozilla.components.feature.customtabs.CustomTabIntentProcessor
|
||||||
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
|
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
|
||||||
import mozilla.components.feature.intent.processing.TabIntentProcessor
|
import mozilla.components.feature.intent.processing.TabIntentProcessor
|
||||||
import mozilla.components.feature.pwa.ManifestStorage
|
import mozilla.components.feature.pwa.ManifestStorage
|
||||||
import mozilla.components.feature.pwa.intent.WebAppIntentProcessor
|
|
||||||
import mozilla.components.feature.pwa.intent.TrustedWebActivityIntentProcessor
|
import mozilla.components.feature.pwa.intent.TrustedWebActivityIntentProcessor
|
||||||
|
import mozilla.components.feature.pwa.intent.WebAppIntentProcessor
|
||||||
import mozilla.components.feature.search.SearchUseCases
|
import mozilla.components.feature.search.SearchUseCases
|
||||||
import mozilla.components.feature.session.SessionUseCases
|
import mozilla.components.feature.session.SessionUseCases
|
||||||
import mozilla.components.support.migration.MigrationIntentProcessor
|
import mozilla.components.support.migration.MigrationIntentProcessor
|
||||||
import mozilla.components.support.migration.state.MigrationStore
|
import mozilla.components.support.migration.state.MigrationStore
|
||||||
import org.mozilla.fenix.BuildConfig
|
import org.mozilla.fenix.BuildConfig
|
||||||
|
import org.mozilla.fenix.customtabs.FennecWebAppIntentProcessor
|
||||||
import org.mozilla.fenix.home.intent.FennecBookmarkShortcutsIntentProcessor
|
import org.mozilla.fenix.home.intent.FennecBookmarkShortcutsIntentProcessor
|
||||||
import org.mozilla.fenix.test.Mockable
|
import org.mozilla.fenix.test.Mockable
|
||||||
|
|
||||||
|
@ -32,7 +33,8 @@ class IntentProcessors(
|
||||||
private val searchUseCases: SearchUseCases,
|
private val searchUseCases: SearchUseCases,
|
||||||
private val httpClient: Client,
|
private val httpClient: Client,
|
||||||
private val customTabsStore: CustomTabsServiceStore,
|
private val customTabsStore: CustomTabsServiceStore,
|
||||||
private val migrationStore: MigrationStore
|
private val migrationStore: MigrationStore,
|
||||||
|
private val manifestStorage: ManifestStorage
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents.
|
* Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents.
|
||||||
|
@ -66,8 +68,9 @@ class IntentProcessors(
|
||||||
apiKey = BuildConfig.DIGITAL_ASSET_LINKS_TOKEN,
|
apiKey = BuildConfig.DIGITAL_ASSET_LINKS_TOKEN,
|
||||||
store = customTabsStore
|
store = customTabsStore
|
||||||
),
|
),
|
||||||
WebAppIntentProcessor(sessionManager, sessionUseCases.loadUrl, ManifestStorage(context)),
|
WebAppIntentProcessor(sessionManager, sessionUseCases.loadUrl, manifestStorage),
|
||||||
FennecBookmarkShortcutsIntentProcessor(sessionManager, sessionUseCases.loadUrl)
|
FennecBookmarkShortcutsIntentProcessor(sessionManager, sessionUseCases.loadUrl),
|
||||||
|
FennecWebAppIntentProcessor(sessionManager, sessionUseCases.loadUrl, manifestStorage)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.customtabs
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import mozilla.components.browser.session.Session
|
||||||
|
import mozilla.components.browser.session.Session.Source
|
||||||
|
import mozilla.components.browser.session.SessionManager
|
||||||
|
import mozilla.components.concept.engine.EngineSession
|
||||||
|
import mozilla.components.concept.engine.manifest.WebAppManifest
|
||||||
|
import mozilla.components.concept.engine.manifest.WebAppManifestParser
|
||||||
|
import mozilla.components.concept.engine.manifest.getOrNull
|
||||||
|
import mozilla.components.feature.intent.ext.putSessionId
|
||||||
|
import mozilla.components.feature.intent.processing.IntentProcessor
|
||||||
|
import mozilla.components.feature.pwa.ManifestStorage
|
||||||
|
import mozilla.components.feature.pwa.ext.putWebAppManifest
|
||||||
|
import mozilla.components.feature.pwa.ext.toCustomTabConfig
|
||||||
|
import mozilla.components.feature.session.SessionUseCases
|
||||||
|
import mozilla.components.support.utils.toSafeIntent
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy processor for Progressive Web App shortcut intents created by Fennec.
|
||||||
|
*/
|
||||||
|
class FennecWebAppIntentProcessor(
|
||||||
|
private val sessionManager: SessionManager,
|
||||||
|
private val loadUrlUseCase: SessionUseCases.DefaultLoadUrlUseCase,
|
||||||
|
private val storage: ManifestStorage
|
||||||
|
) : IntentProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this intent should launch a progressive web app created in Fennec.
|
||||||
|
*/
|
||||||
|
override fun matches(intent: Intent) =
|
||||||
|
intent.toSafeIntent().action == ACTION_FENNEC_WEBAPP
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the given [Intent] by creating a [Session] with a corresponding web app manifest.
|
||||||
|
*
|
||||||
|
* A custom tab config is also set so a custom tab toolbar can be shown when the user leaves
|
||||||
|
* the scope defined in the manifest.
|
||||||
|
*/
|
||||||
|
override suspend fun process(intent: Intent): Boolean {
|
||||||
|
val safeIntent = intent.toSafeIntent()
|
||||||
|
val url = safeIntent.dataString
|
||||||
|
|
||||||
|
return if (!url.isNullOrEmpty() && matches(intent)) {
|
||||||
|
val webAppManifest = storage.loadManifest(url)
|
||||||
|
?: fromFile(safeIntent.getStringExtra(EXTRA_FENNEC_MANIFEST_PATH))?.also {
|
||||||
|
storage.saveManifest(it)
|
||||||
|
}
|
||||||
|
?: return false
|
||||||
|
|
||||||
|
val session = Session(url, private = false, source = Source.HOME_SCREEN)
|
||||||
|
session.webAppManifest = webAppManifest
|
||||||
|
session.customTabConfig = webAppManifest.toCustomTabConfig()
|
||||||
|
|
||||||
|
sessionManager.add(session)
|
||||||
|
loadUrlUseCase(url, session, EngineSession.LoadUrlFlags.external())
|
||||||
|
intent.flags = FLAG_ACTIVITY_NEW_DOCUMENT
|
||||||
|
intent.putSessionId(session.id)
|
||||||
|
intent.putWebAppManifest(webAppManifest)
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
internal fun fromFile(path: String?): WebAppManifest? {
|
||||||
|
if (path.isNullOrEmpty()) return null
|
||||||
|
|
||||||
|
// Gecko in Fennec added some add some additional data, such as cached_icon, in
|
||||||
|
// the toplevel object. The actual web app manifest is in the "manifest" field.
|
||||||
|
val manifest = JSONObject(File(path).readText())
|
||||||
|
val manifestField = manifest.getJSONObject("manifest")
|
||||||
|
|
||||||
|
return WebAppManifestParser().parse(manifestField).getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ACTION_FENNEC_WEBAPP = "org.mozilla.gecko.WEBAPP"
|
||||||
|
const val EXTRA_FENNEC_MANIFEST_PATH = "MANIFEST_PATH"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue