Loading components/concept/engine/src/main/java/mozilla/components/concept/engine/manifest/Size.kt +2 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ data class Size( val width: Int, val height: Int ) { override fun toString() = if (this == ANY) "any" else "${width}x$height" companion object { /** * Represents the "any" size. Loading components/concept/engine/src/main/java/mozilla/components/concept/engine/manifest/WebAppManifestParser.kt +49 −2 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import android.graphics.Color import androidx.annotation.ColorInt import mozilla.components.support.ktx.android.org.json.asSequence import mozilla.components.support.ktx.android.org.json.tryGetString import org.json.JSONArray import org.json.JSONException import org.json.JSONObject Loading Loading @@ -48,7 +49,7 @@ class WebAppManifestParser { name = name, shortName = shortName, startUrl = json.getString("start_url"), display = getDisplayMode(json), display = parseDisplayMode(json), backgroundColor = parseColor(json.tryGetString("background_color")), description = json.tryGetString("description"), icons = parseIcons(json), Loading @@ -62,11 +63,38 @@ class WebAppManifestParser { Result.Failure(e) } } /** * Parses the provided JSON and returns a [WebAppManifest] (wrapped in [Result.Success] if parsing was successful. * Otherwise [Result.Failure]. */ fun parse(json: String): Result { return try { parse(JSONObject(json)) } catch (e: JSONException) { Result.Failure(e) } } fun serialize(manifest: WebAppManifest) = JSONObject().apply { put("name", manifest.name) putOpt("short_name", manifest.shortName) put("start_url", manifest.startUrl) putOpt("display", serializeEnumName(manifest.display.name)) putOpt("background_color", serializeColor(manifest.backgroundColor)) putOpt("description", manifest.description) putOpt("icons", serializeIcons(manifest.icons)) putOpt("scope", manifest.scope) putOpt("theme_color", serializeColor(manifest.themeColor)) putOpt("dir", serializeEnumName(manifest.dir.name)) putOpt("lang", manifest.lang) putOpt("orientation", serializeEnumName(manifest.orientation.name)) } } private val whitespace = "\\s+".toRegex() private fun getDisplayMode(json: JSONObject): WebAppManifest.DisplayMode { private fun parseDisplayMode(json: JSONObject): WebAppManifest.DisplayMode { return when (json.optString("display")) { "standalone" -> WebAppManifest.DisplayMode.STANDALONE "fullscreen" -> WebAppManifest.DisplayMode.FULLSCREEN Loading Loading @@ -148,3 +176,22 @@ private fun parseOrientation(json: JSONObject) = when (json.optString("orientati "landscape-secondary" -> WebAppManifest.Orientation.LANDSCAPE_SECONDARY else -> WebAppManifest.Orientation.ANY } private fun serializeEnumName(name: String) = name.toLowerCase().replace('_', '-') @Suppress("MagicNumber") private fun serializeColor(color: Int?): String? = color?.let { String.format("#%06X", 0xFFFFFF and color) } private fun serializeIcons(icons: List<WebAppManifest.Icon>): JSONArray { val list = icons.map { icon -> JSONObject().apply { put("src", icon.src) put("sizes", icon.sizes.joinToString(" ") { it.toString() }) putOpt("type", icon.type) put("purpose", icon.purpose.joinToString(" ") { serializeEnumName(it.name) }) } } return JSONArray(list) } components/concept/engine/src/test/java/mozilla/components/concept/engine/manifest/WebAppManifestParserTest.kt +45 −4 Original line number Diff line number Diff line Loading @@ -270,6 +270,14 @@ class WebAppManifestParserTest { assertTrue(result is WebAppManifestParser.Result.Failure) } @Test fun `Parsing invalid JSON string`() { val json = loadManifestAsString("invalid_json.json") val result = WebAppManifestParser().parse(json) assertTrue(result is WebAppManifestParser.Result.Failure) } @Test fun `Parsing invalid JSON missing name fields`() { val json = loadManifest("invalid_missing_name.json") Loading Loading @@ -324,12 +332,45 @@ class WebAppManifestParserTest { } } private fun loadManifest(fileName: String): JSONObject = JSONObject(javaClass.getResourceAsStream("/manifests/$fileName")!! @Test fun `Serializing minimal manifest`() { val manifest = WebAppManifest(name = "Mozilla", startUrl = "https://mozilla.org") val json = WebAppManifestParser().serialize(manifest) assertEquals("Mozilla", json.getString("name")) assertEquals("https://mozilla.org", json.getString("start_url")) } @Test fun `Serialize and parse W3 typical manifest`() { val result = WebAppManifestParser().parse(loadManifest("spec_typical.json")) val manifest = (result as WebAppManifestParser.Result.Success).manifest assertEquals( result, WebAppManifestParser().parse(WebAppManifestParser().serialize(manifest)) ) } @Test fun `Serialize and parse unusual manifest`() { val result = WebAppManifestParser().parse(loadManifest("unusual.json")) val manifest = (result as WebAppManifestParser.Result.Success).manifest assertEquals( result, WebAppManifestParser().parse(WebAppManifestParser().serialize(manifest)) ) } private fun loadManifestAsString(fileName: String): String = javaClass.getResourceAsStream("/manifests/$fileName")!! .bufferedReader().use { it.readText() }.also { assertNotNull(it) } ) private fun loadManifest(fileName: String): JSONObject = JSONObject(loadManifestAsString(fileName)) } components/feature/pwa/build.gradle +3 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ dependencies { implementation project(':support-base') implementation project(':support-ktx') implementation Dependencies.androidx_core_ktx implementation Dependencies.kotlin_stdlib implementation Dependencies.kotlin_coroutines Loading @@ -34,6 +35,8 @@ dependencies { testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.kotlin_coroutines_test testImplementation Dependencies.kotlin_reflect testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_robolectric } Loading components/feature/pwa/src/main/AndroidManifest.xml +5 −2 Original line number Diff line number Diff line Loading @@ -8,8 +8,11 @@ <activity android:name=".WebAppLauncherActivity" android:theme="@style/Theme.AppCompat.NoActionBar" android:exported="true" /> android:exported="true"> <intent-filter> <action android:name="mozilla.components.feature.pwa.PWA_LAUNCHER" /> </intent-filter> </activity> </application> </manifest> Loading
components/concept/engine/src/main/java/mozilla/components/concept/engine/manifest/Size.kt +2 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ data class Size( val width: Int, val height: Int ) { override fun toString() = if (this == ANY) "any" else "${width}x$height" companion object { /** * Represents the "any" size. Loading
components/concept/engine/src/main/java/mozilla/components/concept/engine/manifest/WebAppManifestParser.kt +49 −2 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import android.graphics.Color import androidx.annotation.ColorInt import mozilla.components.support.ktx.android.org.json.asSequence import mozilla.components.support.ktx.android.org.json.tryGetString import org.json.JSONArray import org.json.JSONException import org.json.JSONObject Loading Loading @@ -48,7 +49,7 @@ class WebAppManifestParser { name = name, shortName = shortName, startUrl = json.getString("start_url"), display = getDisplayMode(json), display = parseDisplayMode(json), backgroundColor = parseColor(json.tryGetString("background_color")), description = json.tryGetString("description"), icons = parseIcons(json), Loading @@ -62,11 +63,38 @@ class WebAppManifestParser { Result.Failure(e) } } /** * Parses the provided JSON and returns a [WebAppManifest] (wrapped in [Result.Success] if parsing was successful. * Otherwise [Result.Failure]. */ fun parse(json: String): Result { return try { parse(JSONObject(json)) } catch (e: JSONException) { Result.Failure(e) } } fun serialize(manifest: WebAppManifest) = JSONObject().apply { put("name", manifest.name) putOpt("short_name", manifest.shortName) put("start_url", manifest.startUrl) putOpt("display", serializeEnumName(manifest.display.name)) putOpt("background_color", serializeColor(manifest.backgroundColor)) putOpt("description", manifest.description) putOpt("icons", serializeIcons(manifest.icons)) putOpt("scope", manifest.scope) putOpt("theme_color", serializeColor(manifest.themeColor)) putOpt("dir", serializeEnumName(manifest.dir.name)) putOpt("lang", manifest.lang) putOpt("orientation", serializeEnumName(manifest.orientation.name)) } } private val whitespace = "\\s+".toRegex() private fun getDisplayMode(json: JSONObject): WebAppManifest.DisplayMode { private fun parseDisplayMode(json: JSONObject): WebAppManifest.DisplayMode { return when (json.optString("display")) { "standalone" -> WebAppManifest.DisplayMode.STANDALONE "fullscreen" -> WebAppManifest.DisplayMode.FULLSCREEN Loading Loading @@ -148,3 +176,22 @@ private fun parseOrientation(json: JSONObject) = when (json.optString("orientati "landscape-secondary" -> WebAppManifest.Orientation.LANDSCAPE_SECONDARY else -> WebAppManifest.Orientation.ANY } private fun serializeEnumName(name: String) = name.toLowerCase().replace('_', '-') @Suppress("MagicNumber") private fun serializeColor(color: Int?): String? = color?.let { String.format("#%06X", 0xFFFFFF and color) } private fun serializeIcons(icons: List<WebAppManifest.Icon>): JSONArray { val list = icons.map { icon -> JSONObject().apply { put("src", icon.src) put("sizes", icon.sizes.joinToString(" ") { it.toString() }) putOpt("type", icon.type) put("purpose", icon.purpose.joinToString(" ") { serializeEnumName(it.name) }) } } return JSONArray(list) }
components/concept/engine/src/test/java/mozilla/components/concept/engine/manifest/WebAppManifestParserTest.kt +45 −4 Original line number Diff line number Diff line Loading @@ -270,6 +270,14 @@ class WebAppManifestParserTest { assertTrue(result is WebAppManifestParser.Result.Failure) } @Test fun `Parsing invalid JSON string`() { val json = loadManifestAsString("invalid_json.json") val result = WebAppManifestParser().parse(json) assertTrue(result is WebAppManifestParser.Result.Failure) } @Test fun `Parsing invalid JSON missing name fields`() { val json = loadManifest("invalid_missing_name.json") Loading Loading @@ -324,12 +332,45 @@ class WebAppManifestParserTest { } } private fun loadManifest(fileName: String): JSONObject = JSONObject(javaClass.getResourceAsStream("/manifests/$fileName")!! @Test fun `Serializing minimal manifest`() { val manifest = WebAppManifest(name = "Mozilla", startUrl = "https://mozilla.org") val json = WebAppManifestParser().serialize(manifest) assertEquals("Mozilla", json.getString("name")) assertEquals("https://mozilla.org", json.getString("start_url")) } @Test fun `Serialize and parse W3 typical manifest`() { val result = WebAppManifestParser().parse(loadManifest("spec_typical.json")) val manifest = (result as WebAppManifestParser.Result.Success).manifest assertEquals( result, WebAppManifestParser().parse(WebAppManifestParser().serialize(manifest)) ) } @Test fun `Serialize and parse unusual manifest`() { val result = WebAppManifestParser().parse(loadManifest("unusual.json")) val manifest = (result as WebAppManifestParser.Result.Success).manifest assertEquals( result, WebAppManifestParser().parse(WebAppManifestParser().serialize(manifest)) ) } private fun loadManifestAsString(fileName: String): String = javaClass.getResourceAsStream("/manifests/$fileName")!! .bufferedReader().use { it.readText() }.also { assertNotNull(it) } ) private fun loadManifest(fileName: String): JSONObject = JSONObject(loadManifestAsString(fileName)) }
components/feature/pwa/build.gradle +3 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ dependencies { implementation project(':support-base') implementation project(':support-ktx') implementation Dependencies.androidx_core_ktx implementation Dependencies.kotlin_stdlib implementation Dependencies.kotlin_coroutines Loading @@ -34,6 +35,8 @@ dependencies { testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.kotlin_coroutines_test testImplementation Dependencies.kotlin_reflect testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_robolectric } Loading
components/feature/pwa/src/main/AndroidManifest.xml +5 −2 Original line number Diff line number Diff line Loading @@ -8,8 +8,11 @@ <activity android:name=".WebAppLauncherActivity" android:theme="@style/Theme.AppCompat.NoActionBar" android:exported="true" /> android:exported="true"> <intent-filter> <action android:name="mozilla.components.feature.pwa.PWA_LAUNCHER" /> </intent-filter> </activity> </application> </manifest>