diff --git a/apps/rebreak-native/components/ProtectionOnboardingSheet.tsx b/apps/rebreak-native/components/ProtectionOnboardingSheet.tsx index a070d50..0c9dc1f 100644 --- a/apps/rebreak-native/components/ProtectionOnboardingSheet.tsx +++ b/apps/rebreak-native/components/ProtectionOnboardingSheet.tsx @@ -41,7 +41,13 @@ export function ProtectionOnboardingSheet({ const vpnActive = layers.vpn === true; const a11yActive = layers.accessibility === true; if (vpnActive) setVpnState('done'); - if (a11yActive && vpnActive) setA11yState('done'); + if (a11yActive && vpnActive) { + // Arm tamper-lock once a11y is enabled — activateFamilyControls() second + // call goes through the armTamperLock() path. Without this, the service + // is bound but stays passive because tamper_armed stays false. + const r = await protection.activateFamilyControls(); + if (r.enabled) setA11yState('done'); + } } finally { refreshInFlightRef.current = false; } diff --git a/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/RebreakProtectionModule.kt b/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/RebreakProtectionModule.kt index 6808d15..0cd3d94 100644 --- a/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/RebreakProtectionModule.kt +++ b/apps/rebreak-native/modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/RebreakProtectionModule.kt @@ -427,26 +427,34 @@ class RebreakProtectionModule : Module() { val pkg = ctx.packageName val expectedClass = RebreakAccessibilityService::class.java.name - // Primary: AccessibilityManager API — funktioniert auf allen OEMs - // (Samsung One UI returnt manchmal null bei Settings.Secure). + // Primary + authoritative: AccessibilityManager. Eine erfolgreich + // gelieferte Liste (auch leer) ist die Wahrheit — der Service läuft nur + // dann tatsächlich, wenn er hier auftaucht. `Settings.Secure` darf nur + // einspringen wenn die AM-Abfrage technisch fehlschlägt (null/Exception); + // andernfalls würde ein Stale-Eintrag in Settings.Secure (System hat den + // Service deregistriert, ENABLED_ACCESSIBILITY_SERVICES nicht aufgeräumt) + // einen aktiven Schutz vortäuschen, obwohl der Service nicht gebunden ist. try { val am = ctx.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager if (am != null) { val list = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK) - Log.d(TAG, "a11y check via AccessibilityManager: ${list?.size ?: 0} services") - for (info in list ?: emptyList()) { - val id = info.id ?: continue - if (id.contains(pkg) && id.contains("RebreakAccessibilityService")) { - Log.d(TAG, "a11y MATCH via AM: $id") - return true + if (list != null) { + Log.d(TAG, "a11y check via AccessibilityManager: ${list.size} services") + for (info in list) { + val id = info.id ?: continue + if (id.contains(pkg) && id.contains("RebreakAccessibilityService")) { + Log.d(TAG, "a11y MATCH via AM: $id") + return true + } } + return false } } } catch (e: Exception) { Log.w(TAG, "AccessibilityManager check failed: ${e.message}") } - // Fallback: Settings.Secure (Standard-Android) + // Fallback NUR wenn AM technisch unverfügbar war (null/Exception oben). val expected = ComponentName(ctx, RebreakAccessibilityService::class.java) val expectedFlat = expected.flattenToString() val enabled = try { @@ -457,7 +465,7 @@ class RebreakProtectionModule : Module() { } catch (e: Exception) { null } - Log.d(TAG, "a11y check via Settings.Secure: expected=$expectedFlat, settings=$enabled") + Log.d(TAG, "a11y check via Settings.Secure (fallback): expected=$expectedFlat, settings=$enabled") if (!enabled.isNullOrBlank()) { if (enabled.contains(expectedFlat)) return true if (enabled.contains(expectedClass)) return true diff --git a/apps/rebreak-native/plugins/with-rebreak-protection-android.js b/apps/rebreak-native/plugins/with-rebreak-protection-android.js index d8e9ff8..3c14747 100644 --- a/apps/rebreak-native/plugins/with-rebreak-protection-android.js +++ b/apps/rebreak-native/plugins/with-rebreak-protection-android.js @@ -117,13 +117,24 @@ function ensureAccessibilityService(manifest) { }); } -// ─── 4) String resource für a11y-service-summary ──────────────────────────── +// ─── 4) String resources für a11y-service ─────────────────────────────────── +const A11Y_DESCRIPTION_TEXT = + '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.'; const A11Y_SUMMARY_TEXT = - 'ReBreak schützt vor Glücksspiel-Seiten in Browsern. Liest URLs in der Adressleiste, um Casino-Domains zu erkennen und zu blocken.'; + 'Sichert den Schutz gegen Abschalten ab'; function withA11yStringResource(config) { return withStringsXml(config, (cfg) => { + cfg.modResults = AndroidConfig.Strings.setStringItem( + [ + { + $: { name: 'accessibility_service_description', translatable: 'false' }, + _: A11Y_DESCRIPTION_TEXT, + }, + ], + cfg.modResults, + ); cfg.modResults = AndroidConfig.Strings.setStringItem( [ { @@ -138,16 +149,13 @@ function withA11yStringResource(config) { } // ─── 5) XML-config für AccessibilityService ───────────────────────────────── +// Kopiert die Source-of-Truth aus dem Modul-Verzeichnis statt einen +// hardcoded String zu pflegen — so bleibt Plugin + Service-Config immer sync. -const A11Y_CONFIG_XML = ` - -`; +const MODULE_A11Y_XML = path.resolve( + __dirname, + '../modules/rebreak-protection/android/src/main/res/xml/accessibility_service_config.xml', +); function withA11yConfigXml(config) { return withDangerousMod(config, [ @@ -158,10 +166,9 @@ function withA11yConfigXml(config) { 'app/src/main/res/xml', ); fs.mkdirSync(xmlDir, { recursive: true }); - fs.writeFileSync( + fs.copyFileSync( + MODULE_A11Y_XML, path.join(xmlDir, 'accessibility_service_config.xml'), - A11Y_CONFIG_XML, - 'utf8', ); return cfg; },