Skip to content

Commit

Permalink
Closes mozilla-mobile#2293 - Open custom tabs when leaving scope
Browse files Browse the repository at this point in the history
Refactors PWA code so that it uses a BrowserFragment subclass.
  • Loading branch information
NotWoods committed Jul 30, 2019
1 parent 600a86f commit 36b3303
Show file tree
Hide file tree
Showing 32 changed files with 727 additions and 319 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* 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/. */

@file:Suppress("TooManyFunctions")

package mozilla.components.concept.engine.manifest

import android.graphics.Color
Expand Down Expand Up @@ -95,6 +97,14 @@ class WebAppManifestParser {
}
}

/**
* Returns the encapsulated value if this instance represents success or `null` if it is failure.
*/
fun WebAppManifestParser.Result.getOrNull(): WebAppManifest? = when (this) {
is WebAppManifestParser.Result.Success -> manifest
is WebAppManifestParser.Result.Failure -> null
}

private fun parseDisplayMode(json: JSONObject): WebAppManifest.DisplayMode {
return when (json.optString("display")) {
"standalone" -> WebAppManifest.DisplayMode.STANDALONE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class WebAppManifestParserTest {

@Test
fun `getOrNull returns parsed manifest`() {
val sucessfulResult = WebAppManifestParser().parse(loadManifest("example_mdn.json"))
assertNotNull(sucessfulResult.getOrNull())

val failedResult = WebAppManifestParser().parse(loadManifest("invalid_json.json"))
assertNull(failedResult.getOrNull())
}

@Test
fun `Parsing example manifest from MDN`() {
val json = loadManifest("example_mdn.json")
Expand Down
3 changes: 3 additions & 0 deletions components/feature/pwa/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ dependencies {
implementation project(':browser-session')
implementation project(':concept-engine')
implementation project(':concept-fetch')
implementation project(':feature-session')
implementation project(':support-base')
implementation project(':support-ktx')
implementation project(':support-utils')

implementation Dependencies.androidx_browser
implementation Dependencies.androidx_core_ktx
implementation Dependencies.kotlin_stdlib
implementation Dependencies.kotlin_coroutines
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.manifest.WebAppManifest
import mozilla.components.feature.pwa.intent.WebAppIntentProcessor.Companion.ACTION_VIEW_PWA
import mozilla.components.support.base.log.logger.Logger

/**
Expand Down Expand Up @@ -76,16 +77,14 @@ class WebAppLauncherActivity : AppCompatActivity(), CoroutineScope by MainScope(

@VisibleForTesting(otherwise = PRIVATE)
internal fun launchWebAppShell(startUrl: Uri) {
val intent = Intent(AbstractWebAppShellActivity.INTENT_ACTION).apply {
data = startUrl
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
val intent = Intent(ACTION_VIEW_PWA, startUrl).apply {
`package` = packageName
}

try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
logger.error("Packages does not handle AbstractWebAppShellActivity intent. Can't launch web app.", e)
logger.error("Packages does not handle ACTION_VIEW_PWA intent. Can't launch as web app.", e)
// Fall back to normal browser
launchBrowser(startUrl)
}
Expand All @@ -97,6 +96,6 @@ class WebAppLauncherActivity : AppCompatActivity(), CoroutineScope by MainScope(
}

companion object {
const val INTENT_ACTION = "mozilla.components.feature.pwa.PWA_LAUNCHER"
internal const val ACTION_PWA_LAUNCHER = "mozilla.components.feature.pwa.PWA_LAUNCHER"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import mozilla.components.browser.icons.utils.IconMemoryCache
import mozilla.components.browser.session.Session
import mozilla.components.concept.engine.manifest.WebAppManifest
import mozilla.components.concept.fetch.Client
import mozilla.components.feature.pwa.WebAppLauncherActivity.Companion.ACTION_PWA_LAUNCHER
import mozilla.components.feature.pwa.ext.installableManifest

private val pwaIconMemoryCache = IconMemoryCache()
Expand Down Expand Up @@ -93,9 +94,8 @@ class WebAppShortcutManager(
* Create a new basic pinned website shortcut using info from the session.
*/
fun buildBasicShortcut(context: Context, session: Session): ShortcutInfoCompat? {
val shortcutIntent = Intent(context, WebAppLauncherActivity::class.java).apply {
action = WebAppLauncherActivity.INTENT_ACTION
data = session.url.toUri()
val shortcutIntent = Intent(Intent.ACTION_VIEW, session.url.toUri()).apply {
`package` = context.packageName
}

val builder = ShortcutInfoCompat.Builder(context, session.url)
Expand All @@ -114,8 +114,10 @@ class WebAppShortcutManager(
*/
suspend fun buildWebAppShortcut(context: Context, manifest: WebAppManifest): ShortcutInfoCompat? {
val shortcutIntent = Intent(context, WebAppLauncherActivity::class.java).apply {
action = WebAppLauncherActivity.INTENT_ACTION
action = ACTION_PWA_LAUNCHER
data = manifest.startUrl.toUri()
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
`package` = context.packageName
}

val shortLabel = manifest.shortName ?: manifest.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ import mozilla.components.concept.engine.manifest.WebAppManifest.Orientation
*/
fun Activity.applyOrientation(manifest: WebAppManifest?) {
requestedOrientation = when (manifest?.orientation) {
Orientation.NATURAL, Orientation.ANY -> ActivityInfo.SCREEN_ORIENTATION_USER
Orientation.NATURAL, Orientation.ANY, null -> ActivityInfo.SCREEN_ORIENTATION_USER
Orientation.LANDSCAPE -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
Orientation.LANDSCAPE_PRIMARY -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
Orientation.LANDSCAPE_SECONDARY -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
Orientation.PORTRAIT -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
Orientation.PORTRAIT_PRIMARY -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
Orientation.PORTRAIT_SECONDARY -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
else -> ActivityInfo.SCREEN_ORIENTATION_USER
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* 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 mozilla.components.feature.pwa.ext

import android.os.Bundle
import mozilla.components.concept.engine.manifest.WebAppManifest
import mozilla.components.concept.engine.manifest.WebAppManifestParser
import mozilla.components.concept.engine.manifest.getOrNull

internal const val EXTRA_WEB_APP_MANIFEST = "mozilla.components.feature.pwa.EXTRA_WEB_APP_MANIFEST"

/**
* Serializes and inserts a [WebAppManifest] value into the mapping of this [Bundle],
* replacing any existing web app manifest.
*/
fun Bundle.putWebAppManifest(webAppManifest: WebAppManifest?) {
val json = webAppManifest?.let { WebAppManifestParser().serialize(it).toString() }
putString(EXTRA_WEB_APP_MANIFEST, json)
}

/**
* Parses and returns the [WebAppManifest] associated with this [Bundle],
* or null if no mapping of the desired type exists.
*/
fun Bundle.getWebAppManifest(): WebAppManifest? {
return getString(EXTRA_WEB_APP_MANIFEST)?.let { json ->
WebAppManifestParser().parse(json).getOrNull()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* 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 mozilla.components.feature.pwa.ext

import android.content.Intent
import mozilla.components.concept.engine.manifest.WebAppManifest
import mozilla.components.concept.engine.manifest.WebAppManifestParser

/**
* Add extended [WebAppManifest] data to the intent.
*/
fun Intent.putWebAppManifest(webAppManifest: WebAppManifest) {
val json = WebAppManifestParser().serialize(webAppManifest)
putExtra(EXTRA_WEB_APP_MANIFEST, json.toString())
}

/**
* Retrieve extended [WebAppManifest] data from the intent.
*/
fun Intent.getWebAppManifest(): WebAppManifest? {
return extras?.getWebAppManifest()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package mozilla.components.feature.pwa.ext

import android.app.ActivityManager.TaskDescription
import android.graphics.Bitmap
import mozilla.components.browser.session.tab.CustomTabConfig
import mozilla.components.concept.engine.manifest.WebAppManifest

/**
Expand All @@ -15,5 +16,16 @@ import mozilla.components.concept.engine.manifest.WebAppManifest
* Instead we use the deprecated constructor.
*/
@Suppress("Deprecation")
fun WebAppManifest.asTaskDescription(icon: Bitmap?) =
fun WebAppManifest.toTaskDescription(icon: Bitmap?) =
TaskDescription(name, icon, themeColor ?: 0)

fun WebAppManifest.toCustomTabConfig() =
CustomTabConfig(
id = startUrl,
toolbarColor = themeColor,
closeButtonIcon = null,
enableUrlbarHiding = true,
actionButtonConfig = null,
showShareMenuItem = true,
menuItems = emptyList()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* 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 mozilla.components.feature.pwa.feature

import android.app.Activity
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.icons.extension.toIconRequest
import mozilla.components.concept.engine.manifest.WebAppManifest
import mozilla.components.feature.pwa.ext.applyOrientation
import mozilla.components.feature.pwa.ext.toTaskDescription
import mozilla.components.support.ktx.android.view.enterToImmersiveMode

/**
* Feature used to handle window effects for "standalone" and "fullscreen" web apps.
*/
class WebAppActivityFeature(
private val activity: Activity,
private val icons: BrowserIcons,
private val manifest: WebAppManifest
) : LifecycleObserver {

private val scope = MainScope()

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
if (manifest.display == WebAppManifest.DisplayMode.FULLSCREEN) {
activity.enterToImmersiveMode()
}

activity.applyOrientation(manifest)

scope.launch {
updateRecentEntry()
}
}

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
scope.cancel()
}

@VisibleForTesting
internal suspend fun updateRecentEntry() {
val icon = icons.loadIcon(manifest.toIconRequest()).await()

activity.setTaskDescription(manifest.toTaskDescription(icon.bitmap))
}
}
Loading

0 comments on commit 36b3303

Please sign in to comment.