From fc7a243c9b257f8d15c7c8a082beab41644ab011 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Mon, 11 May 2026 17:42:05 +0200 Subject: [PATCH] =?UTF-8?q?refactor(android):=20a11y=20service=20is=20now?= =?UTF-8?q?=20tamper-lock=20only=20=E2=80=94=20no=20browser=20URL=20filter?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AccessibilityService used to also do a browser-address-bar filter (read the URL bar of Chrome/Firefox/etc., hash-match against blocklist.bin, GLOBAL_ACTION_BACK on a hit) as a "layer 2" alongside the VpnService DNS filter. That's redundant (the VPN catches everything network-level, in browsers AND apps), fragile (per-browser view-IDs), and produced ghost-blocks (VPN off, a11y still blocking sites). The DNS filter is the protection; the a11y service's only real value-add is tamper-resistance. So the a11y service now does ONLY the tamper-lock, and only when the user has armed "App-Lock": block opening protection-critical settings (disable the ReBreak VPN, uninstall the app, disable the a11y service itself). Top-level guard is now simply `if (!isTamperLockArmed()) return` — when App-Lock isn't armed the service is fully passive. Getting out is still via the regular deactivation cooldown (which disarms the tamper-lock and stops the VPN). - RebreakAccessibilityService.kt: removed browser-URL extraction, BROWSER_PACKAGES, URL_BAR_IDS, hashList loading, throttle bookkeeping, the block-toast. Kept the settings-watchdog (it already covered VPN settings via VpnSettings/vpndialogs + the vpn-page keyword cluster) and adjusted its keyword lists to the new a11y service summary (old summary kept as a legacy fallback for stale installs). - accessibility_service_config.xml: dropped browser packages + flagRequestEnhancedWebAccessibility. - strings.xml (de+en): a11y permission copy reframed — it safeguards the VPN/uninstall, it doesn't filter your browser; ends with "you can always exit via the cooldown". - lib/protection.ts: comment-only (activateFamilyControls logic unchanged). - locales de/en: App-Lock card copy ("Familienzugriff aktiv" → "Verriegelt — ...", "...ReBreak oder den Filter im Impuls abschaltest"), genericised the iOS Screen-Time error string. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/rebreak-native/lib/protection.ts | 5 +- apps/rebreak-native/locales/de.json | 6 +- apps/rebreak-native/locales/en.json | 6 +- .../RebreakAccessibilityService.kt | 283 ++++-------------- .../src/main/res/values-en/strings.xml | 4 +- .../android/src/main/res/values/strings.xml | 4 +- .../res/xml/accessibility_service_config.xml | 19 +- 7 files changed, 80 insertions(+), 247 deletions(-) diff --git a/apps/rebreak-native/lib/protection.ts b/apps/rebreak-native/lib/protection.ts index e9d7912..5cdd93f 100644 --- a/apps/rebreak-native/lib/protection.ts +++ b/apps/rebreak-native/lib/protection.ts @@ -105,7 +105,10 @@ export const protection = { async activateFamilyControls(): Promise<{ enabled: boolean; error?: string }> { if (Platform.OS === "android") { - // Android Layer-2 = AccessibilityService (Browser-URL-Filter) + Tamper-Lock. + // Android "App-Lock" = AccessibilityService als reiner Tamper-Lock (KEIN + // Browser-Filter mehr — Glücksspielseiten blockt der VpnService DNS-Filter). + // Der a11y-Service verhindert nur, dass schutz-relevante Settings geöffnet + // werden (VPN abschalten / App deinstallieren / a11y-Service abschalten). // Two-step UX: // (1) A11y nicht aktiv → Settings öffnen, return {enabled:false} mit // Marker-Error. UI fragt nach Return den State neu ab und tappt diff --git a/apps/rebreak-native/locales/de.json b/apps/rebreak-native/locales/de.json index 8ebfadf..b4b3663 100644 --- a/apps/rebreak-native/locales/de.json +++ b/apps/rebreak-native/locales/de.json @@ -242,7 +242,7 @@ "activate_url_failed_msg": "Unbekannter Fehler.\nDu kannst es nochmal versuchen oder System-Einstellungen prüfen.", "activate_settings_btn": "Einstellungen", "activate_app_lock_failed_title": "App-Lock konnte nicht aktiviert werden", - "activate_app_lock_failed_msg": "Bildschirmzeit-Berechtigung wurde verweigert. Du kannst es nochmal versuchen.", + "activate_app_lock_failed_msg": "Die nötige Berechtigung wurde verweigert. Du kannst es nochmal versuchen.", "sync_list_failed_title": "Filter-Liste konnte nicht geladen werden", "sync_list_failed_msg": "Bitte später nochmal versuchen.", "activation_failed_title": "Aktivierung fehlgeschlagen", @@ -268,8 +268,8 @@ "layers_url_filter_subtitle_active": "System-weiter Filter aktiv", "layers_url_filter_subtitle_inactive": "Blockt Gambling-Seiten in Safari + Apps", "layers_app_lock_title": "App-Lock", - "layers_app_lock_subtitle_active": "Familienzugriff aktiv", - "layers_app_lock_subtitle_inactive": "Verhindert dass du ReBreak im Impuls löschst", + "layers_app_lock_subtitle_active": "Verriegelt — Abschalten nur über die Abkühlphase", + "layers_app_lock_subtitle_inactive": "Verhindert, dass du ReBreak oder den Filter im Impuls abschaltest", "layers_app_lock_warning": "Sobald aktiv kannst du den Schutz nur über einen 24-Stunden-Cooldown abschalten. Das ist gewollt.", "kpi_global_label": "Geblockte Domains weltweit", "kpi_global_subtitle": "Aktive Einträge in der globalen Blockliste", diff --git a/apps/rebreak-native/locales/en.json b/apps/rebreak-native/locales/en.json index 2c0816b..e82ed5a 100644 --- a/apps/rebreak-native/locales/en.json +++ b/apps/rebreak-native/locales/en.json @@ -242,7 +242,7 @@ "activate_url_failed_msg": "Unknown error.\nYou can try again or check System Settings.", "activate_settings_btn": "Settings", "activate_app_lock_failed_title": "Could not activate App Lock", - "activate_app_lock_failed_msg": "Screen Time permission was denied. You can try again.", + "activate_app_lock_failed_msg": "The required permission was denied. You can try again.", "sync_list_failed_title": "Filter list could not be loaded", "sync_list_failed_msg": "Please try again later.", "activation_failed_title": "Activation failed", @@ -268,8 +268,8 @@ "layers_url_filter_subtitle_active": "System-wide filter active", "layers_url_filter_subtitle_inactive": "Blocks gambling sites in Safari + apps", "layers_app_lock_title": "App lock", - "layers_app_lock_subtitle_active": "Family access active", - "layers_app_lock_subtitle_inactive": "Prevents you from deleting ReBreak on impulse", + "layers_app_lock_subtitle_active": "Locked — disable only via the cooldown", + "layers_app_lock_subtitle_inactive": "Stops you from switching off ReBreak or the filter on impulse", "layers_app_lock_warning": "Once active, you can only disable protection through a 24-hour cooldown. That's by design.", "kpi_global_label": "Domains blocked worldwide", "kpi_global_subtitle": "Active entries in the global blocklist", diff --git a/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/accessibility/RebreakAccessibilityService.kt b/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/accessibility/RebreakAccessibilityService.kt index 73715d2..32217d1 100644 --- a/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/accessibility/RebreakAccessibilityService.kt +++ b/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/accessibility/RebreakAccessibilityService.kt @@ -1,119 +1,71 @@ package expo.modules.rebreakprotection.accessibility import android.accessibilityservice.AccessibilityService -import android.accessibilityservice.AccessibilityServiceInfo -import android.content.Intent import android.os.Handler import android.os.Looper import android.util.Log import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.widget.Toast -import expo.modules.rebreakprotection.filter.HashList -import java.io.File /** - * URL-Filter-Layer 2 — parallel zum VpnService DNS-Filter. + * Tamper-Lock — sichert den Schutz gegen versehentliches/impulsives Abschalten. * - * Hintergrund: Der VpnService kann vom User in System-Settings ausgeschaltet - * werden (Always-on VPN ist bei Free-Konsumenten-Devices nicht erzwingbar). - * Die Accessibility-Permission hingegen ist ihm "vertrauter" — man gibt sie - * einmal und vergisst sie. Solange der User sie nicht explizit entzieht, - * läuft dieser Filter weiter. + * **Was dieser Service NICHT (mehr) tut:** Glücksspielseiten blocken. Das macht + * ausschließlich der `RebreakVpnService` (DNS-Filter, ~208k Domains) — der greift + * network-level, also in Browsern *und* Apps, und übersteht Browser-Updates. Den + * früheren Browser-Adressleisten-Filter haben wir entfernt: redundant zum VPN, + * fragil (View-IDs pro Browser), und führte zu Geister-Blockaden (VPN aus, a11y + * blockt trotzdem noch). * - * Funktionsweise (parallel zu iOS NEFilterBrowserFlow): - * - Service hört auf TYPE_WINDOW_CONTENT_CHANGED + TYPE_WINDOW_STATE_CHANGED - * - Filtert nach Browser-Packages (Chrome/Firefox/Edge/Samsung/Brave/Opera) - * - Liest die Adressleiste via AccessibilityNodeInfo - * - Hash-Match gegen blocklist.bin (gleiche Datei wie VpnService) - * - Bei Treffer: GLOBAL_ACTION_BACK + Toast + * **Was dieser Service tut:** Wenn der User „App-Lock" aktiviert hat + * (`tamper_armed == true`, opt-in über den App-Button) und der Schutz aktiv ist + * (`filter_enabled == true`), verhindert er, dass schutz-relevante System-Settings + * geöffnet werden können: + * - Settings → VPN → ReBreak abschalten / „Always-on" / Profil löschen + * - App-Info / Play-Store → ReBreak deinstallieren / Daten löschen / Force-Stop + * - Settings → Bedienungshilfen → den ReBreak-a11y-Service selbst abschalten * - * Throttling: pro Browser nur alle 600ms eine URL-Prüfung — sonst feuert - * Chrome bei jeder Frame-Änderung Hunderte Events. + * Strategie: lauscht auf `TYPE_WINDOW_STATE_CHANGED` (Activity-Wechsel) + + * `TYPE_WINDOW_CONTENT_CHANGED` (Dialog-Inhalt lädt bei manchen OEMs nach) aus + * den Settings-/Installer-Packages → matcht Activity-Klasse bzw. Window-Text gegen + * gefährliche Patterns → `GLOBAL_ACTION_BACK` + Toast. Rauskommen geht nur über + * den regulären Deaktivierungs-Cooldown (der disarmed `tamper_armed` und stoppt + * das VPN — danach ist dieser Service vollständig passiv). + * + * Bypass-Vektoren die das NICHT abdeckt: Safe-Mode-Reboot (Apps off), ADB/Root. */ class RebreakAccessibilityService : AccessibilityService() { - private lateinit var hashList: HashList private val mainHandler = Handler(Looper.getMainLooper()) - private val lastCheck = HashMap() - private val lastBlockedUrl = HashMap() private var lastSettingsCheck: Long = 0L - /** Nach einem TAMPER-BLOCK: 3s lang keinen weiteren Block triggern. - * Verhindert Toast-Spam wenn User legitim in Settings navigieren will und - * Page-Transitions noch alte Keyword-Matches durchziehen. */ + /** Nach einem TAMPER-BLOCK: kurz keinen weiteren Block triggern. Verhindert + * Toast-Spam wenn alte Keyword-Matches in Page-Transitions nachziehen. */ private var lastBlockAt: Long = 0L private val POST_BLOCK_COOLDOWN_MS = 3000L - override fun onCreate() { - super.onCreate() - hashList = HashList(File(applicationContext.filesDir, "blocklist.bin")) - hashList.load() - Log.i(TAG, "service created — ${hashList.count()} hashes") - } - override fun onServiceConnected() { super.onServiceConnected() - // Reload bei jedem Connect — User könnte zwischenzeitlich syncBlocklist - // gemacht haben. - hashList.load() - Log.i(TAG, "service connected — ${hashList.count()} hashes loaded") + Log.i(TAG, "tamper-lock service connected") } override fun onAccessibilityEvent(event: AccessibilityEvent?) { if (event == null) return - // Globaler Kill-Switch: Wenn der User den Schutz NICHT will, tut dieser - // Service GAR NICHTS — kein Gambling-Block, kein Settings-Block. Der - // a11y-Service selbst kann sich nicht programmatisch deaktivieren, also - // ist das hier die einzige Stelle wo wir ihn vollständig stilllegen. - // "Schutz aktiv?" = Tamper-Lock armed (App-Lock opt-in) ODER der - // `filter_enabled`-Flag aus den SharedPrefs (den `disable()` auf false - // setzt). Wer den App-Lock nicht opt-in't, hat trotzdem den normalen - // Free-Blocker → dann greift `filter_enabled`. - if (!isTamperLockArmed() && !isProtectionEnabled()) return + // Globaler Kill-Switch: Dieser Service tut NUR etwas, wenn der User den + // App-Lock explizit armed hat. Ist er nicht armed (Default, inkl. frischem + // Onboarding und nach einem abgelaufenen Cooldown der `disarmTamperLock` + // aufgerufen hat) → vollständig passiv. Der a11y-Service kann sich nicht + // selbst deaktivieren, also ist das hier die einzige Stelle wo wir ihn + // stilllegen. + if (!isTamperLockArmed()) return val pkg = event.packageName?.toString() ?: return + if (pkg !in WATCHED_SETTINGS_PACKAGES) return + if (event.eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && + event.eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) return - // Settings-Watchdog: User versucht eine Schutz-relevante Settings-Page - // zu öffnen → Sofort BACK + Toast. Wir reagieren auf STATE_CHANGED - // (Activity-Wechsel) UND CONTENT_CHANGED (Dialog-Inhalt lädt nach) — - // weil bei manchen OEMs (Samsung) der Inhalt erst NACH der Activity - // gerendert wird und der erste Scan leer wäre. - if (pkg in WATCHED_SETTINGS_PACKAGES && - (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED || - event.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) { - if (handleProtectedSettingsBlock(pkg, event)) return - } - - if (!BROWSER_PACKAGES.contains(pkg)) return - - // Throttle pro Browser-Package - val now = System.currentTimeMillis() - val last = lastCheck[pkg] ?: 0L - if (now - last < THROTTLE_MS) return - lastCheck[pkg] = now - - val root = rootInActiveWindow ?: return - val url = extractUrl(root, pkg) ?: return - val host = extractHost(url) ?: return - - if (hashList.matchesAnySuffix(host)) { - // Vermeiden, beim selben URL ständig Back zu feuern - if (lastBlockedUrl[pkg] == url) return - lastBlockedUrl[pkg] = url - Log.i(TAG, "BLOCKED via accessibility: $host (in $pkg)") - performGlobalAction(GLOBAL_ACTION_BACK) - mainHandler.post { - Toast.makeText( - applicationContext, - "Rebreak hat diese Seite blockiert", - Toast.LENGTH_SHORT, - ).show() - } - } else { - // URL nicht (mehr) blockiert → letzte-blockiert-Cache löschen - lastBlockedUrl.remove(pkg) - } + handleProtectedSettingsBlock(pkg, event) } override fun onInterrupt() { @@ -124,26 +76,13 @@ class RebreakAccessibilityService : AccessibilityService() { * Tamper-Protection: User versucht in System-Settings eine Schutz-relevante * Seite zu öffnen (VPN deaktivieren, App löschen, A11y für Rebreak abschalten). * - * Strategie: TYPE_WINDOW_STATE_CHANGED → wenn package + className auf eine - * gefährliche Activity matchen → GLOBAL_ACTION_BACK + Toast. User wird sofort - * rausgeworfen, kann nicht togglen. - * - * Bypass-Vektoren die wir damit NICHT abdecken: - * - Safe Mode (Reboot mit Power-Long-Press) → Apps off → A11y off → freie Bahn - * - ADB / Root → kein normaler User - * - * Aktiviert nur wenn User aktuell als "committed" gilt (rebreak_blocker == true - * in SharedPreferences). Sonst wäre der Lock auch beim Neuinstall-Onboarding - * aktiv und würde User aussperren. + * Aktiviert nur wenn der App-Lock armed UND der Schutz aktiv ist (`filter_enabled`). + * Letzteres lässt den User nach einem legitim abgelaufenen Cooldown wieder raus. * * @return true wenn die Activity geblockt wurde */ private fun handleProtectedSettingsBlock(pkg: String, event: AccessibilityEvent): Boolean { - // Tamper-Lock ist nur aktiv wenn User EXPLIZIT verriegelt hat (über - // App-Button "Schutz fest verriegeln"). Sonst würde der Watchdog - // sofort die Setup-Seiten blockieren und User aussperren. - if (!isTamperLockArmed()) return false - if (!isUserCommittedToProtection()) return false + if (!isProtectionEnabled()) return false if (pkg !in WATCHED_SETTINGS_PACKAGES) return false // Throttle: max alle 400ms eine Settings-Inspection (CONTENT_CHANGED @@ -170,13 +109,11 @@ class RebreakAccessibilityService : AccessibilityService() { className.contains(pattern, ignoreCase = true) } - // Phase 2 — Window-Content-Match: IMMER scannen (außer wir haben schon - // einen className-Match). OEMs benutzen für Dialoge oft className die - // weder in unseren Patterns noch als "generic container" erkannt werden - // (z.B. Samsung's "AppDialog", Stock-Android's "ManageDialog$2"). Der - // Keyword-Cluster-Scan ist unsere Safety-Net: 2 Keywords aus dem - // gleichen Cluster = Block. Default false-positive Risk durch Throttling - // (alle 400ms eine Inspection). + // Phase 2 — Window-Content-Match: scannen wenn kein className-Match. OEMs + // benutzen für Dialoge oft className die weder in unseren Patterns noch als + // "generic container" erkannt werden (z.B. Samsung's "AppDialog"). Der + // Keyword-Cluster-Scan ist das Safety-Net: 2 Keywords aus dem gleichen + // Cluster = Block. False-positive-Risk gedämpft durch Throttling. var contentReason: String? = null if (!classMatchDangerous) { contentReason = scanWindowForDangerousContent() @@ -261,14 +198,9 @@ class RebreakAccessibilityService : AccessibilityService() { } } - /** Liest den Commitment-Flag aus SharedPreferences (gleicher Storage wie der - * Plugin nutzt). User ist "committed" wenn er Schutz mal aktiv aktiviert hat - * und ihn nicht legitim (= durch Cooldown-Ende) deaktiviert hat. */ - private fun isUserCommittedToProtection(): Boolean = isProtectionEnabled() - - /** "Verriegelt"-Flag — User hat über App-Button "Schutz fest verriegeln" - * bestätigt dass Settings-Tampering blockiert werden soll. Default: false - * damit Onboarding nicht aussperrt. */ + /** "Verriegelt"-Flag — User hat über App-Button „App-Lock" bestätigt dass + * Settings-Tampering blockiert werden soll. Default: false damit Onboarding + * nicht aussperrt; nach Cooldown-Ende disarmed `disarmTamperLock` ihn wieder. */ private fun isTamperLockArmed(): Boolean { return try { val prefs = applicationContext.getSharedPreferences( @@ -281,112 +213,8 @@ class RebreakAccessibilityService : AccessibilityService() { } } - /** - * Liest die Adressleiste aus dem Browser-View-Tree. - * - * Browser benutzen unterschiedliche View-IDs für ihre URL-Bar — wir - * probieren die gängigen durch. Fallback: alle EditText-Nodes nach - * URL-artigem Inhalt scannen. - */ - private fun extractUrl(root: AccessibilityNodeInfo, pkg: String): String? { - val candidateIds = URL_BAR_IDS[pkg] ?: emptyList() - for (id in candidateIds) { - val nodes = try { - root.findAccessibilityNodeInfosByViewId(id) - } catch (e: Exception) { - null - } ?: continue - for (node in nodes) { - val text = node.text?.toString() ?: continue - if (looksLikeUrl(text)) return text - } - } - // Fallback — scanne den ganzen Tree breadth-first nach URL-artigem Text - return scanTreeForUrl(root, depth = 0) - } - - private fun scanTreeForUrl(node: AccessibilityNodeInfo, depth: Int): String? { - if (depth > MAX_TREE_DEPTH) return null - val text = node.text?.toString() - if (text != null && looksLikeUrl(text)) return text - for (i in 0 until node.childCount) { - val child = node.getChild(i) ?: continue - val found = scanTreeForUrl(child, depth + 1) - if (found != null) return found - } - return null - } - - private fun looksLikeUrl(text: String): Boolean { - val t = text.trim() - if (t.length < 3 || t.length > 2048) return false - // Schnell-Filter: enthält Punkt, kein Whitespace - if (' ' in t || '\n' in t) return false - // Normalisiere — Browser zeigen oft "https://" weggekürzt - val normalized = if (t.startsWith("http://") || t.startsWith("https://")) t else "https://$t" - return try { - val u = java.net.URI(normalized) - val host = u.host - host != null && host.contains('.') - } catch (_: Exception) { - false - } - } - - private fun extractHost(url: String): String? { - val normalized = if (url.startsWith("http://") || url.startsWith("https://")) url else "https://$url" - return try { - java.net.URI(normalized).host?.lowercase() - } catch (_: Exception) { - null - } - } - companion object { private const val TAG = "RebreakA11y" - private const val THROTTLE_MS = 600L - private const val MAX_TREE_DEPTH = 12 - - // Erweitern wir, wenn User-Reports zeigen dass ihr Browser nicht erkannt wird. - val BROWSER_PACKAGES = setOf( - "com.android.chrome", // Chrome - "com.chrome.beta", - "com.chrome.dev", - "com.chrome.canary", - "org.mozilla.firefox", // Firefox - "org.mozilla.firefox_beta", - "org.mozilla.fenix", // Firefox Fenix - "com.microsoft.emmx", // Edge - "com.sec.android.app.sbrowser", // Samsung Internet - "com.brave.browser", // Brave - "com.opera.browser", // Opera - "com.opera.mini.native", - "com.duckduckgo.mobile.android", // DuckDuckGo - "com.vivaldi.browser", // Vivaldi - "org.torproject.torbrowser", // Tor - ) - - // View-IDs der Adressleiste pro Browser. Bekannte Stand 2026. - // findAccessibilityNodeInfosByViewId erwartet das volle ID-Tag, - // d.h. ":id/". - private val URL_BAR_IDS = mapOf( - "com.android.chrome" to listOf("com.android.chrome:id/url_bar"), - "com.chrome.beta" to listOf("com.chrome.beta:id/url_bar"), - "com.chrome.dev" to listOf("com.chrome.dev:id/url_bar"), - "com.chrome.canary" to listOf("com.chrome.canary:id/url_bar"), - "org.mozilla.firefox" to listOf("org.mozilla.firefox:id/mozac_browser_toolbar_url_view"), - "org.mozilla.firefox_beta" to listOf("org.mozilla.firefox_beta:id/mozac_browser_toolbar_url_view"), - "org.mozilla.fenix" to listOf("org.mozilla.fenix:id/mozac_browser_toolbar_url_view"), - "com.microsoft.emmx" to listOf("com.microsoft.emmx:id/url_bar"), - "com.sec.android.app.sbrowser" to listOf("com.sec.android.app.sbrowser:id/location_bar_edit_text"), - "com.brave.browser" to listOf("com.brave.browser:id/url_bar"), - "com.opera.browser" to listOf("com.opera.browser:id/url_field"), - "com.opera.mini.native" to listOf("com.opera.mini.native:id/url_field"), - "com.duckduckgo.mobile.android" to listOf("com.duckduckgo.mobile.android:id/omnibarTextInput"), - "com.vivaldi.browser" to listOf("com.vivaldi.browser:id/url_bar"), - ) - - const val ACTION_RELOAD_BLOCKLIST = "expo.modules.rebreakprotection.action.A11Y_RELOAD" // Settings-Apps die wir auf Tamper-Versuche überwachen. // Stock + Samsung One UI haben unterschiedliche Package-Namen, @@ -402,17 +230,15 @@ class RebreakAccessibilityService : AccessibilityService() { "com.android.vending", ) - // Activity-Class-Patterns die geblockt werden (Substring-Match, case-insensitive). - // Decken Stock-Android + Samsung One UI ab. Patterns sind bewusst breit - // damit OEM-Variationen mitgenommen werden. /** - * High-confidence Keywords — wenn EINER davon im Window-Content - * auftaucht, blocken wir sofort. Sind alle hochspezifisch und - * tauchen praktisch nur auf VPN/A11y/Uninstall-Detail-Pages auf. + * High-confidence Keywords — wenn EINER davon im Window-Content auftaucht, + * blocken wir sofort. Hochspezifisch zu uns. Enthält sowohl die aktuelle + * a11y-Service-Summary als auch die alte (für stale Installs / OEM-Cache). */ val HIGH_CONFIDENCE_KEYWORDS = listOf( - "rebreak filter", // VPN-Profil-Name aus Builder.setSession - "filtert glücksspielseiten", // A11y-Service-Summary + "rebreak filter", // VPN-Profil-Name aus Builder.setSession + "sichert den schutz", // aktuelle a11y-Service-Summary + "filtert glücksspielseiten", // alte a11y-Service-Summary (legacy installs) "rebreak deinstallieren", "rebreak entfernen", "rebreak löschen", @@ -441,7 +267,8 @@ class RebreakAccessibilityService : AccessibilityService() { "bedienungshilfe", "eingabehilfe", "accessibility", - "filtert glücksspiel", // unser A11y-Service-Summary + "sichert den schutz", // unsere aktuelle a11y-Service-Summary + "filtert glücksspiel", // alte a11y-Service-Summary (legacy installs) "rebreak filter", "installierte apps", "installed services", diff --git a/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/values-en/strings.xml b/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/values-en/strings.xml index 91b8d64..fcb9f82 100644 --- a/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/values-en/strings.xml +++ b/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/values-en/strings.xml @@ -1,5 +1,5 @@ - Rebreak filters URLs in your browser to block gambling sites — even when the VPN is off. Without this permission the app cannot fully maintain protection. - Filters gambling sites in the browser + Keeps your protection from being switched off on impulse: while App-Lock is on, the ReBreak VPN can\'t be disabled in Settings and the app can\'t be uninstalled. Blocking gambling sites itself is handled by the VPN — this permission only safeguards it. You can always end protection via the cooldown in the app. + Keeps protection from being switched off diff --git a/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/values/strings.xml b/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/values/strings.xml index ecba9d5..54b7295 100644 --- a/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/values/strings.xml +++ b/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - Rebreak filtert URLs in deinem Browser, um Glücksspielseiten zu blockieren — auch wenn das VPN nicht aktiv ist. Ohne diese Berechtigung kann die App ihren Schutz nicht vollständig aufrechterhalten. - Filtert Glücksspielseiten im Browser + Sichert deinen Schutz gegen impulsives Abschalten ab: Solange App-Lock aktiv ist, kann das ReBreak-VPN nicht in den Einstellungen deaktiviert und die App nicht deinstalliert werden. Das Blockieren von Glücksspielseiten selbst übernimmt das VPN — diese Berechtigung sichert es nur. Du kannst den Schutz jederzeit über die Abkühlphase in der App beenden. + Sichert den Schutz gegen Abschalten ab diff --git a/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/xml/accessibility_service_config.xml b/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/xml/accessibility_service_config.xml index a5c6cf2..fff4754 100644 --- a/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/xml/accessibility_service_config.xml +++ b/apps/rebreak-native/modules/rebreak-protection/android/src/main/res/xml/accessibility_service_config.xml @@ -2,21 +2,24 @@