Open links in system browser
This commit is contained in:
Generated
+1
@@ -4,6 +4,7 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.example.privatefacebook"
|
applicationId = "com.example.privatefacebook"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 6
|
||||||
versionName = "1.0"
|
versionName = "1.7"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.privatefacebook
|
package com.example.privatefacebook
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.net.http.SslError
|
import android.net.http.SslError
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -42,6 +43,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
webView = findViewById(R.id.webview)
|
webView = findViewById(R.id.webview)
|
||||||
|
|
||||||
|
// Use hardware layer for faster video rendering
|
||||||
|
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||||
|
|
||||||
val webSettings: WebSettings = webView.settings
|
val webSettings: WebSettings = webView.settings
|
||||||
webSettings.javaScriptEnabled = true
|
webSettings.javaScriptEnabled = true
|
||||||
webSettings.domStorageEnabled = true
|
webSettings.domStorageEnabled = true
|
||||||
@@ -51,9 +55,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
webSettings.allowContentAccess = false
|
webSettings.allowContentAccess = false
|
||||||
webSettings.setGeolocationEnabled(false)
|
webSettings.setGeolocationEnabled(false)
|
||||||
|
|
||||||
|
// Allow autoplay of media without a user gesture (required for seamless reels autoplay)
|
||||||
|
webSettings.mediaPlaybackRequiresUserGesture = false
|
||||||
|
|
||||||
webSettings.userAgentString =
|
webSettings.userAgentString =
|
||||||
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
|
"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)) {
|
if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
|
||||||
WebViewCompat.startSafeBrowsing(this) { /* no-op */ }
|
WebViewCompat.startSafeBrowsing(this) { /* no-op */ }
|
||||||
}
|
}
|
||||||
@@ -75,14 +83,64 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
loadFilterList()
|
loadFilterList()
|
||||||
|
|
||||||
|
// Pre-connect to common Facebook/CDN endpoints to reduce initial video handshake latency
|
||||||
|
Thread {
|
||||||
|
val warmHosts = listOf(
|
||||||
|
"https://m.facebook.com",
|
||||||
|
"https://www.facebook.com",
|
||||||
|
"https://fbcdn.net",
|
||||||
|
"https://*.fbcdn.net",
|
||||||
|
"https://*.facebook.com",
|
||||||
|
"https://fbsbx.com",
|
||||||
|
"https://fb.com"
|
||||||
|
)
|
||||||
|
for (host in warmHosts) {
|
||||||
|
try {
|
||||||
|
val u = URL(host.replace("*.", ""))
|
||||||
|
val c = (u.openConnection() as? HttpURLConnection) ?: continue
|
||||||
|
c.connectTimeout = 3000
|
||||||
|
c.readTimeout = 3000
|
||||||
|
c.requestMethod = "HEAD"
|
||||||
|
c.instanceFollowRedirects = true
|
||||||
|
c.connect()
|
||||||
|
c.disconnect()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
|
||||||
webView.webViewClient = object : WebViewClient() {
|
webView.webViewClient = object : WebViewClient() {
|
||||||
|
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)
|
||||||
|
view?.context?.startActivity(intent)
|
||||||
|
return true // Intercept the navigation
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return false // Fallback to WebView if browser fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false // Stay in app for Facebook links
|
||||||
|
}
|
||||||
|
|
||||||
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
|
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
|
||||||
val req = request ?: return null
|
val req = request ?: return null
|
||||||
val urlStr = req.url.toString()
|
val urlStr = req.url.toString()
|
||||||
val lower = urlStr.lowercase()
|
val lower = urlStr.lowercase()
|
||||||
|
|
||||||
|
// Deny cleartext (extra safety)
|
||||||
if (lower.startsWith("http://")) {
|
if (lower.startsWith("http://")) {
|
||||||
// Deny cleartext
|
|
||||||
return WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray()))
|
return WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,14 +151,19 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only intercept main-frame GET navigations to strip X-Requested-With and inject CSP
|
// Only intercept main-frame GET navigations to strip X-Requested-With and inject CSP
|
||||||
if (req.isForMainFrame && req.method == "GET" && lower.contains("facebook.com")) {
|
try {
|
||||||
return performControlledFetch(req)
|
if (req.isForMainFrame && req.method == "GET" && lower.contains("facebook.com")) {
|
||||||
|
return performControlledFetch(req)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// Fall through to default behaviour
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
|
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
|
||||||
|
// Cancel on SSL errors — safer default
|
||||||
handler?.cancel()
|
handler?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +174,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load mobile Facebook
|
||||||
webView.loadUrl("https://m.facebook.com")
|
webView.loadUrl("https://m.facebook.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +188,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
blockPatterns.add(Regex(escaped, RegexOption.IGNORE_CASE))
|
blockPatterns.add(Regex(escaped, RegexOption.IGNORE_CASE))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {
|
||||||
|
// ignore — empty list is safe
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performControlledFetch(request: WebResourceRequest): WebResourceResponse? {
|
private fun performControlledFetch(request: WebResourceRequest): WebResourceResponse? {
|
||||||
@@ -138,14 +204,20 @@ class MainActivity : ComponentActivity() {
|
|||||||
conn.instanceFollowRedirects = true
|
conn.instanceFollowRedirects = true
|
||||||
conn.requestMethod = "GET"
|
conn.requestMethod = "GET"
|
||||||
|
|
||||||
request.requestHeaders.forEach { (k, v) ->
|
// Copy headers except X-Requested-With
|
||||||
if (k.lowercase() != "x-requested-with") {
|
try {
|
||||||
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)
|
val cookie = CookieManager.getInstance().getCookie(urlString)
|
||||||
if (!cookie.isNullOrEmpty()) conn.setRequestProperty("Cookie", cookie)
|
if (!cookie.isNullOrEmpty()) {
|
||||||
|
try { conn.setRequestProperty("Cookie", cookie) } catch (_: Exception) {}
|
||||||
|
}
|
||||||
|
|
||||||
conn.connect()
|
conn.connect()
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
@@ -165,16 +237,20 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val responseHeaders = mutableMapOf<String, String>()
|
val responseHeaders = mutableMapOf<String, String>()
|
||||||
conn.headerFields.forEach { (key, values) ->
|
try {
|
||||||
if (key != null && values != null) responseHeaders[key] = values.joinToString("; ")
|
conn.headerFields.forEach { (key, values) ->
|
||||||
}
|
if (key != null && values != null) responseHeaders[key] = values.joinToString("; ")
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
|
||||||
val csp = "default-src 'self' https://*.facebook.com https://*.fbcdn.net https://*.fbsbx.com; " +
|
// 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; " +
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.facebook.com https://*.fbcdn.net; " +
|
||||||
"style-src 'self' 'unsafe-inline' https://*.facebook.com https://*.fbcdn.net; " +
|
"style-src 'self' 'unsafe-inline' https://*.facebook.com https://*.fbcdn.net; " +
|
||||||
"img-src * data: blob:; " +
|
"img-src * data: blob: https://*.fb.com; " +
|
||||||
"connect-src 'self' https://*.facebook.com https://*.fbcdn.net https://*.fbsbx.com; " +
|
"media-src 'self' https://*.facebook.com https://*.fbcdn.net https://*.fb.com blob: data:; " +
|
||||||
"frame-src 'self' https://*.facebook.com;"
|
"connect-src 'self' https://*.facebook.com https://*.fbcdn.net https://*.fbsbx.com https://*.fb.com; " +
|
||||||
|
"frame-src 'self' https://*.facebook.com https://*.fbsbx.com;")
|
||||||
|
|
||||||
responseHeaders["Content-Security-Policy"] = csp
|
responseHeaders["Content-Security-Policy"] = csp
|
||||||
responseHeaders["Referrer-Policy"] = "no-referrer"
|
responseHeaders["Referrer-Policy"] = "no-referrer"
|
||||||
@@ -184,7 +260,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
val res = WebResourceResponse(mimeType, "utf-8", inputStream ?: ByteArrayInputStream("".toByteArray()))
|
val res = WebResourceResponse(mimeType, "utf-8", inputStream ?: ByteArrayInputStream("".toByteArray()))
|
||||||
res.responseHeaders = responseHeaders
|
res.responseHeaders = responseHeaders
|
||||||
res
|
res
|
||||||
} catch (_: Exception) { null }
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun injectPrivacyScript() {
|
private fun injectPrivacyScript() {
|
||||||
@@ -222,7 +300,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
} catch(_){}
|
} catch(_){}
|
||||||
})();
|
})();
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
webView.evaluateJavascript(js, null)
|
try {
|
||||||
|
webView.evaluateJavascript(js, null)
|
||||||
|
} catch (_: Exception) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearAllWebViewData() {
|
private fun clearAllWebViewData() {
|
||||||
|
|||||||
Reference in New Issue
Block a user