diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..ca16a99 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,7 @@
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2d5b881..33c0d08 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -14,9 +14,9 @@ android {
defaultConfig {
applicationId = "com.example.privatefacebook"
minSdk = 24
- targetSdk = 36
- versionCode = 1
- versionName = "1.0"
+ targetSdk = 35
+ versionCode = 6
+ versionName = "1.7"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/app/src/main/java/com/example/privatefacebook/MainActivity.kt b/app/src/main/java/com/example/privatefacebook/MainActivity.kt
index c51cdce..d977427 100644
--- a/app/src/main/java/com/example/privatefacebook/MainActivity.kt
+++ b/app/src/main/java/com/example/privatefacebook/MainActivity.kt
@@ -1,6 +1,7 @@
package com.example.privatefacebook
-import android.content.Context
+import android.content.Intent
+import android.net.Uri
import android.net.http.SslError
import android.os.Bundle
import android.view.View
@@ -42,6 +43,9 @@ 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
webSettings.javaScriptEnabled = true
webSettings.domStorageEnabled = true
@@ -51,9 +55,13 @@ class MainActivity : ComponentActivity() {
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 */ }
}
@@ -75,14 +83,64 @@ 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",
+ "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() {
+ 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? {
val req = request ?: return null
val urlStr = req.url.toString()
val lower = urlStr.lowercase()
+ // Deny cleartext (extra safety)
if (lower.startsWith("http://")) {
- // Deny cleartext
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
- if (req.isForMainFrame && req.method == "GET" && lower.contains("facebook.com")) {
- return performControlledFetch(req)
+ try {
+ if (req.isForMainFrame && req.method == "GET" && lower.contains("facebook.com")) {
+ return performControlledFetch(req)
+ }
+ } catch (_: Exception) {
+ // Fall through to default behaviour
}
return null
}
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
+ // Cancel on SSL errors — safer default
handler?.cancel()
}
@@ -111,6 +174,7 @@ class MainActivity : ComponentActivity() {
}
}
+ // Load mobile Facebook
webView.loadUrl("https://m.facebook.com")
}
@@ -124,7 +188,9 @@ class MainActivity : ComponentActivity() {
blockPatterns.add(Regex(escaped, RegexOption.IGNORE_CASE))
}
}
- } catch (_: Exception) {}
+ } catch (_: Exception) {
+ // ignore — empty list is safe
+ }
}
private fun performControlledFetch(request: WebResourceRequest): WebResourceResponse? {
@@ -138,14 +204,20 @@ class MainActivity : ComponentActivity() {
conn.instanceFollowRedirects = true
conn.requestMethod = "GET"
- request.requestHeaders.forEach { (k, v) ->
- if (k.lowercase() != "x-requested-with") {
- try { conn.setRequestProperty(k, v) } catch (_: Exception) {}
+ // 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) {}
+ }
}
- }
+ } catch (_: Exception) {}
+ // Forward cookies from WebView cookie store if present
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()
} catch (_: Exception) {
@@ -165,16 +237,20 @@ class MainActivity : ComponentActivity() {
}
val responseHeaders = mutableMapOf()
- conn.headerFields.forEach { (key, values) ->
- if (key != null && values != null) responseHeaders[key] = values.joinToString("; ")
- }
+ try {
+ 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; " +
"style-src 'self' 'unsafe-inline' https://*.facebook.com https://*.fbcdn.net; " +
- "img-src * data: blob:; " +
- "connect-src 'self' https://*.facebook.com https://*.fbcdn.net https://*.fbsbx.com; " +
- "frame-src 'self' https://*.facebook.com;"
+ "img-src * data: blob: https://*.fb.com; " +
+ "media-src 'self' https://*.facebook.com https://*.fbcdn.net https://*.fb.com blob: data:; " +
+ "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["Referrer-Policy"] = "no-referrer"
@@ -184,7 +260,9 @@ class MainActivity : ComponentActivity() {
val res = WebResourceResponse(mimeType, "utf-8", inputStream ?: ByteArrayInputStream("".toByteArray()))
res.responseHeaders = responseHeaders
res
- } catch (_: Exception) { null }
+ } catch (_: Exception) {
+ null
+ }
}
private fun injectPrivacyScript() {
@@ -222,7 +300,9 @@ class MainActivity : ComponentActivity() {
} catch(_){}
})();
""".trimIndent()
- webView.evaluateJavascript(js, null)
+ try {
+ webView.evaluateJavascript(js, null)
+ } catch (_: Exception) {}
}
private fun clearAllWebViewData() {