First version
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package com.example.privateamazon
|
||||
|
||||
import android.net.http.SslError
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebStorage
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.webkit.WebSettingsCompat
|
||||
import androidx.webkit.WebViewCompat
|
||||
import androidx.webkit.WebViewFeature
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
private lateinit var webView: WebView
|
||||
private val blockPatterns = mutableListOf<Regex>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// 1. Fix Status Bar Overlay (Push app below the clock/battery)
|
||||
val mainContainer = findViewById<View>(R.id.main_container)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(mainContainer) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
|
||||
webView = findViewById(R.id.webview)
|
||||
|
||||
// 2. Configure WebSettings
|
||||
val webSettings: WebSettings = webView.settings
|
||||
webSettings.javaScriptEnabled = true
|
||||
webSettings.domStorageEnabled = true
|
||||
webSettings.cacheMode = WebSettings.LOAD_DEFAULT
|
||||
webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
|
||||
|
||||
// Privacy Hardening
|
||||
webSettings.allowFileAccess = false
|
||||
webSettings.allowContentAccess = false
|
||||
webSettings.setGeolocationEnabled(false)
|
||||
webSettings.userAgentString = "Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114 Mobile Safari/537.36"
|
||||
|
||||
// 3. Feature Checks (Safe Browsing & Dark Mode)
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
|
||||
WebViewCompat.startSafeBrowsing(this) { }
|
||||
}
|
||||
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
|
||||
WebSettingsCompat.setForceDark(webSettings, WebSettingsCompat.FORCE_DARK_ON)
|
||||
}
|
||||
|
||||
// 4. --- COOKIE CONFIGURATION (STAY LOGGED IN) ---
|
||||
val cookieManager = CookieManager.getInstance()
|
||||
cookieManager.setAcceptCookie(true)
|
||||
cookieManager.setAcceptThirdPartyCookies(webView, false) // Block trackers, allow Amazon
|
||||
// ------------------------------------------------
|
||||
|
||||
// 5. Handle Back Button
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (webView.canGoBack()) webView.goBack() else finish()
|
||||
}
|
||||
})
|
||||
|
||||
loadFilterList()
|
||||
|
||||
// 6. Set up the WebView Client
|
||||
webView.webViewClient = object : WebViewClient() {
|
||||
|
||||
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
|
||||
val req = request ?: return null
|
||||
val urlStr = req.url.toString().lowercase()
|
||||
|
||||
// Block Trackers from filters.txt
|
||||
for (pattern in blockPatterns) {
|
||||
if (pattern.containsMatchIn(urlStr)) {
|
||||
return WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray()))
|
||||
}
|
||||
}
|
||||
|
||||
// Block cleartext (Force HTTPS)
|
||||
if (urlStr.startsWith("http://")) {
|
||||
return WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray()))
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
|
||||
handler?.cancel() // Security: Don't allow bad SSL
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
// Save cookies to disk immediately after page loads
|
||||
CookieManager.getInstance().flush()
|
||||
injectPrivacyScript()
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Load Amazon with a custom header to hide the "App" identity
|
||||
val extraHeaders = mapOf("X-Requested-With" to "")
|
||||
webView.loadUrl("https://www.amazon.co.uk", extraHeaders)
|
||||
}
|
||||
|
||||
private fun loadFilterList() {
|
||||
try {
|
||||
assets.open("filters.txt").bufferedReader().useLines { lines ->
|
||||
lines.map { it.trim() }
|
||||
.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||
.forEach { rule ->
|
||||
val escaped = rule.replace(".", "\\.").replace("*", ".*")
|
||||
blockPatterns.add(Regex(escaped, RegexOption.IGNORE_CASE))
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
private fun injectPrivacyScript() {
|
||||
val js = """
|
||||
(function() {
|
||||
try {
|
||||
navigator.sendBeacon = function(){ return true; };
|
||||
window.RTCPeerConnection = null;
|
||||
window.webkitRTCPeerConnection = null;
|
||||
|
||||
const getContext = HTMLCanvasElement.prototype.getContext;
|
||||
HTMLCanvasElement.prototype.getContext = function(type) {
|
||||
if (type.includes('webgl')) return null;
|
||||
return getContext.apply(this, arguments);
|
||||
};
|
||||
} catch(_){}
|
||||
})();
|
||||
""".trimIndent()
|
||||
webView.evaluateJavascript(js, null)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// Save cookies to disk when app goes to background
|
||||
CookieManager.getInstance().flush()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user