Open links in system browser, tested on Pixel 8 Pro
This commit is contained in:
@@ -16,7 +16,7 @@ android {
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = 6
|
||||
versionName = "1.7"
|
||||
versionName = "1.8"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -42,8 +42,6 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
webView = findViewById(R.id.webview)
|
||||
|
||||
// Use hardware layer for faster video rendering
|
||||
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||
|
||||
val webSettings: WebSettings = webView.settings
|
||||
@@ -54,19 +52,14 @@ class MainActivity : ComponentActivity() {
|
||||
webSettings.allowFileAccess = false
|
||||
webSettings.allowContentAccess = false
|
||||
webSettings.setGeolocationEnabled(false)
|
||||
|
||||
// Allow autoplay of media without a user gesture (required for seamless reels autoplay)
|
||||
webSettings.mediaPlaybackRequiresUserGesture = false
|
||||
|
||||
webSettings.userAgentString =
|
||||
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
|
||||
|
||||
// Safe browsing (guarded by feature check)
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
|
||||
WebViewCompat.startSafeBrowsing(this) { /* no-op */ }
|
||||
}
|
||||
|
||||
// Optional: force dark in-webview to follow system (if supported)
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
|
||||
WebSettingsCompat.setForceDark(webView.settings, WebSettingsCompat.FORCE_DARK_ON)
|
||||
}
|
||||
@@ -83,7 +76,6 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
loadFilterList()
|
||||
|
||||
// Pre-connect to common Facebook/CDN endpoints to reduce initial video handshake latency
|
||||
Thread {
|
||||
val warmHosts = listOf(
|
||||
"https://m.facebook.com",
|
||||
@@ -104,42 +96,58 @@ class MainActivity : ComponentActivity() {
|
||||
c.instanceFollowRedirects = true
|
||||
c.connect()
|
||||
c.disconnect()
|
||||
} catch (_: Exception) {
|
||||
// ignore
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}.start()
|
||||
|
||||
webView.webViewClient = object : WebViewClient() {
|
||||
// First Gate: Standard navigation override
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
||||
val url = request?.url ?: return false
|
||||
val urlStr = url.toString().lowercase()
|
||||
|
||||
// Define what stays inside the app (Facebook domains)
|
||||
val isFacebook = urlStr.contains("facebook.com") ||
|
||||
urlStr.contains("fbcdn.net") ||
|
||||
urlStr.contains("fbsbx.com") ||
|
||||
urlStr.contains("fb.com")
|
||||
|
||||
if (!isFacebook) {
|
||||
// External link detected: hand off to default system browser
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW, url)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
view?.context?.startActivity(intent)
|
||||
return true // Intercept the navigation
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
return false // Fallback to WebView if browser fails
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false // Stay in app for Facebook links
|
||||
return false
|
||||
}
|
||||
|
||||
// Second Gate: Intercepting requests to catch deep-links and redirects
|
||||
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
|
||||
val req = request ?: return null
|
||||
val urlStr = req.url.toString()
|
||||
val lower = urlStr.lowercase()
|
||||
|
||||
// Deny cleartext (extra safety)
|
||||
// HARDWARE FIX: If a main frame is loading a non-Facebook URL, force it to browser
|
||||
if (req.isForMainFrame) {
|
||||
val isFacebook = lower.contains("facebook.com") ||
|
||||
lower.contains("fb.com") ||
|
||||
lower.contains("fbcdn.net") ||
|
||||
lower.contains("fbsbx.com")
|
||||
|
||||
if (!isFacebook) {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW, req.url)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
view?.context?.startActivity(intent)
|
||||
} catch (_: Exception) {}
|
||||
// Return empty stream so the WebView stays on the FB page
|
||||
return WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray()))
|
||||
}
|
||||
}
|
||||
|
||||
if (lower.startsWith("http://")) {
|
||||
return WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray()))
|
||||
}
|
||||
@@ -150,20 +158,16 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// Only intercept main-frame GET navigations to strip X-Requested-With and inject CSP
|
||||
try {
|
||||
if (req.isForMainFrame && req.method == "GET" && lower.contains("facebook.com")) {
|
||||
return performControlledFetch(req)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// Fall through to default behaviour
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
|
||||
// Cancel on SSL errors — safer default
|
||||
handler?.cancel()
|
||||
}
|
||||
|
||||
@@ -174,7 +178,6 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// Load mobile Facebook
|
||||
webView.loadUrl("https://m.facebook.com")
|
||||
}
|
||||
|
||||
@@ -188,9 +191,7 @@ class MainActivity : ComponentActivity() {
|
||||
blockPatterns.add(Regex(escaped, RegexOption.IGNORE_CASE))
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// ignore — empty list is safe
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
private fun performControlledFetch(request: WebResourceRequest): WebResourceResponse? {
|
||||
@@ -204,16 +205,12 @@ class MainActivity : ComponentActivity() {
|
||||
conn.instanceFollowRedirects = true
|
||||
conn.requestMethod = "GET"
|
||||
|
||||
// Copy headers except X-Requested-With
|
||||
try {
|
||||
request.requestHeaders.forEach { (k, v) ->
|
||||
if (k.lowercase() != "x-requested-with") {
|
||||
try { conn.setRequestProperty(k, v) } catch (_: Exception) {}
|
||||
}
|
||||
request.requestHeaders.forEach { (k, v) ->
|
||||
if (k.lowercase() != "x-requested-with") {
|
||||
try { conn.setRequestProperty(k, v) } catch (_: Exception) {}
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
// Forward cookies from WebView cookie store if present
|
||||
val cookie = CookieManager.getInstance().getCookie(urlString)
|
||||
if (!cookie.isNullOrEmpty()) {
|
||||
try { conn.setRequestProperty("Cookie", cookie) } catch (_: Exception) {}
|
||||
@@ -243,7 +240,6 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
|
||||
// CSP updated to allow facebook video streaming (blob:, data:) and fb.com previews
|
||||
val csp = ("default-src 'self' https://*.facebook.com https://*.fbcdn.net https://*.fbsbx.com https://*.fb.com; " +
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.facebook.com https://*.fbcdn.net; " +
|
||||
"style-src 'self' 'unsafe-inline' https://*.facebook.com https://*.fbcdn.net; " +
|
||||
@@ -305,16 +301,6 @@ class MainActivity : ComponentActivity() {
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
private fun clearAllWebViewData() {
|
||||
try {
|
||||
webView.clearCache(true)
|
||||
webView.clearHistory()
|
||||
WebStorage.getInstance().deleteAllData()
|
||||
CookieManager.getInstance().removeAllCookies(null)
|
||||
CookieManager.getInstance().flush()
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
CookieManager.getInstance().flush()
|
||||
|
||||
Reference in New Issue
Block a user