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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.example.privateamazon.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.example.privateamazon.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun PrivateAmazonTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.example.privateamazon.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
Reference in New Issue
Block a user