Skip to content

Commit

Permalink
Jelly: Load manifest icon instead of favicon
Browse files Browse the repository at this point in the history
We can use a higher quality icon that is defined in the manifest.json
If website has manifest, we ignore the favicon
and try to extract icon from it

Change-Id: Ie7219807d96d351e90742dbe92f9520489e43928
  • Loading branch information
SaeedDev94 committed Jan 10, 2025
1 parent e12cd59 commit 0e1a72b
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 2 deletions.
90 changes: 90 additions & 0 deletions app/src/main/java/org/lineageos/jelly/js/JsManifest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2025 The LineageOS Project
* SPDX-License-Identifier: Apache-2.0
*/

package org.lineageos.jelly.js

import android.graphics.BitmapFactory
import android.webkit.JavascriptInterface
import androidx.annotation.Keep
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.lineageos.jelly.webview.WebViewExtActivity
import java.net.HttpURLConnection
import java.net.URI
import java.net.URL
import kotlin.reflect.cast

@Keep
class JsManifest(
private val activity: WebViewExtActivity,
) {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

@JavascriptInterface
fun onIconResolved(baseUrl: String, iconUrl: String) {
val url = URI(baseUrl).resolve(iconUrl).toString()
scope.launch {
val bitmap = getIconBitmap(url)
if (bitmap != null) {
withContext(Dispatchers.Main) {
activity.onFaviconLoaded(bitmap)
}
}
}
}

private fun getIconBitmap(url: String) = runCatching {
val connection = HttpURLConnection::class.cast(
URL(url).openConnection()
)
connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK) return null
connection.inputStream.buffered().use {
BitmapFactory.decodeStream(it)
}
}.getOrNull()

companion object {
const val INTERFACE = "JsManifest"
const val URL = "(() => document.querySelector('link[rel=\"manifest\"]')?.href ?? '')"

private const val MONKEY_PATCH_ONCE_KEY = "JsManifestMonkeyPatch"
const val SCRIPT = """
(async () => {
if (window.$MONKEY_PATCH_ONCE_KEY) return;
window.$MONKEY_PATCH_ONCE_KEY = true;
const baseUrl = $URL();
if (!baseUrl) return;
try {
const res = await fetch(baseUrl);
const manifest = await res.json();
let iconUrl = null;
let minWidth = 33;
const maxWidth = 333;
(manifest.icons ?? []).forEach((icon) => {
if (!icon.sizes) return;
if (icon.purpose?.includes('monochrome')) return;
const width = Number(icon.sizes.split('x')[0]);
if (width >= minWidth && width <= maxWidth) {
minWidth = width;
iconUrl = icon.src;
}
});
if (iconUrl !== null) {
$INTERFACE.onIconResolved(baseUrl, iconUrl);
}
} catch (error) {
}
})();
"""
}
}
14 changes: 12 additions & 2 deletions app/src/main/java/org/lineageos/jelly/webview/ChromeClient.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2021 The LineageOS Project
* SPDX-FileCopyrightText: 2020-2025 The LineageOS Project
* SPDX-License-Identifier: Apache-2.0
*/

Expand All @@ -18,6 +18,7 @@ import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import org.lineageos.jelly.R
import org.lineageos.jelly.js.JsManifest
import org.lineageos.jelly.ui.UrlBarLayout
import org.lineageos.jelly.utils.SharedPreferencesExt
import org.lineageos.jelly.utils.TabUtils.openInNewTab
Expand All @@ -43,7 +44,16 @@ internal class ChromeClient(
}

override fun onReceivedIcon(view: WebView, icon: Bitmap) {
activity.onFaviconLoaded(icon)
if (!view.settings.javaScriptEnabled) {
activity.onFaviconLoaded(icon)
return
}

view.evaluateJavascript("${JsManifest.URL}()") { manifestUrl ->
if (manifestUrl.isBlank() || manifestUrl == "\"\"") {
activity.onFaviconLoaded(icon)
}
}
}

override fun onShowFileChooser(
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/org/lineageos/jelly/webview/WebClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar
import org.lineageos.jelly.R
import org.lineageos.jelly.js.JsManifest
import org.lineageos.jelly.js.JsSyncUrl
import org.lineageos.jelly.ui.UrlBarLayout
import org.lineageos.jelly.utils.IntentUtils
Expand All @@ -48,6 +49,7 @@ internal class WebClient(private val urlBarLayout: UrlBarLayout) : WebViewClient
urlBarLayout.onPageLoadFinished(view.certificate)
if (view.settings.javaScriptEnabled) {
view.evaluateJavascript(JsSyncUrl.SCRIPT, null)
view.evaluateJavascript(JsManifest.SCRIPT, null)
}
}

Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/lineageos/jelly/webview/WebViewExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.webkit.WebView
import org.lineageos.jelly.js.JsManifest
import org.lineageos.jelly.js.JsSyncUrl
import org.lineageos.jelly.ui.UrlBarLayout
import org.lineageos.jelly.utils.SharedPreferencesExt
Expand Down Expand Up @@ -114,6 +115,10 @@ class WebViewExt @JvmOverloads constructor(
JsSyncUrl(urlBarLayout, activity),
JsSyncUrl.INTERFACE
)
addJavascriptInterface(
JsManifest(activity),
JsManifest.INTERFACE
)
}
}

Expand Down

0 comments on commit 0e1a72b

Please sign in to comment.