fix(native/protection-android): a11y plugin self-heals XML, arm tamper-lock on return, truthful status check
- with-rebreak-protection-android plugin now copies the source accessibility_service_config.xml via withDangerousMod instead of generating it from a string. Eliminates the silent regression where prebuild wrote flagReportViewIds + missing packageNames, leaving Samsung's content scan unable to read OEM dialogs. - ProtectionOnboardingSheet refresh() now calls activateFamilyControls() once a11y is detected as enabled, so armTamperLock() actually runs. Previously the sheet auto-completed on getDeviceState() alone, leaving tamper_armed=false and the service permanently passive. - RebreakProtectionModule.isAccessibilityServiceEnabled() now trusts the AccessibilityManager list as authoritative when AM is available (even when empty). Settings.Secure fallback only kicks in if AM is null/exception. Fixes the banner falsely showing "Schutz aktiv" when the system has unbound the service but ENABLED_ACCESSIBILITY_SERVICES still holds the id. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
83b0d7a062
commit
c1dd7e7320
@ -41,7 +41,13 @@ export function ProtectionOnboardingSheet({
|
|||||||
const vpnActive = layers.vpn === true;
|
const vpnActive = layers.vpn === true;
|
||||||
const a11yActive = layers.accessibility === true;
|
const a11yActive = layers.accessibility === true;
|
||||||
if (vpnActive) setVpnState('done');
|
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 {
|
} finally {
|
||||||
refreshInFlightRef.current = false;
|
refreshInFlightRef.current = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -427,26 +427,34 @@ class RebreakProtectionModule : Module() {
|
|||||||
val pkg = ctx.packageName
|
val pkg = ctx.packageName
|
||||||
val expectedClass = RebreakAccessibilityService::class.java.name
|
val expectedClass = RebreakAccessibilityService::class.java.name
|
||||||
|
|
||||||
// Primary: AccessibilityManager API — funktioniert auf allen OEMs
|
// Primary + authoritative: AccessibilityManager. Eine erfolgreich
|
||||||
// (Samsung One UI returnt manchmal null bei Settings.Secure).
|
// 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 {
|
try {
|
||||||
val am = ctx.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
|
val am = ctx.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
|
||||||
if (am != null) {
|
if (am != null) {
|
||||||
val list = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
|
val list = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
|
||||||
Log.d(TAG, "a11y check via AccessibilityManager: ${list?.size ?: 0} services")
|
if (list != null) {
|
||||||
for (info in list ?: emptyList()) {
|
Log.d(TAG, "a11y check via AccessibilityManager: ${list.size} services")
|
||||||
|
for (info in list) {
|
||||||
val id = info.id ?: continue
|
val id = info.id ?: continue
|
||||||
if (id.contains(pkg) && id.contains("RebreakAccessibilityService")) {
|
if (id.contains(pkg) && id.contains("RebreakAccessibilityService")) {
|
||||||
Log.d(TAG, "a11y MATCH via AM: $id")
|
Log.d(TAG, "a11y MATCH via AM: $id")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "AccessibilityManager check failed: ${e.message}")
|
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 expected = ComponentName(ctx, RebreakAccessibilityService::class.java)
|
||||||
val expectedFlat = expected.flattenToString()
|
val expectedFlat = expected.flattenToString()
|
||||||
val enabled = try {
|
val enabled = try {
|
||||||
@ -457,7 +465,7 @@ class RebreakProtectionModule : Module() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
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.isNullOrBlank()) {
|
||||||
if (enabled.contains(expectedFlat)) return true
|
if (enabled.contains(expectedFlat)) return true
|
||||||
if (enabled.contains(expectedClass)) return true
|
if (enabled.contains(expectedClass)) return true
|
||||||
|
|||||||
@ -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 =
|
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) {
|
function withA11yStringResource(config) {
|
||||||
return withStringsXml(config, (cfg) => {
|
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(
|
cfg.modResults = AndroidConfig.Strings.setStringItem(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -138,16 +149,13 @@ function withA11yStringResource(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── 5) XML-config für AccessibilityService ─────────────────────────────────
|
// ─── 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 = `<?xml version="1.0" encoding="utf-8"?>
|
const MODULE_A11Y_XML = path.resolve(
|
||||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
__dirname,
|
||||||
android:accessibilityEventTypes="typeWindowContentChanged|typeWindowStateChanged"
|
'../modules/rebreak-protection/android/src/main/res/xml/accessibility_service_config.xml',
|
||||||
android:accessibilityFeedbackType="feedbackGeneric"
|
);
|
||||||
android:accessibilityFlags="flagDefault|flagReportViewIds"
|
|
||||||
android:canRetrieveWindowContent="true"
|
|
||||||
android:notificationTimeout="100"
|
|
||||||
android:description="@string/accessibility_service_summary" />
|
|
||||||
`;
|
|
||||||
|
|
||||||
function withA11yConfigXml(config) {
|
function withA11yConfigXml(config) {
|
||||||
return withDangerousMod(config, [
|
return withDangerousMod(config, [
|
||||||
@ -158,10 +166,9 @@ function withA11yConfigXml(config) {
|
|||||||
'app/src/main/res/xml',
|
'app/src/main/res/xml',
|
||||||
);
|
);
|
||||||
fs.mkdirSync(xmlDir, { recursive: true });
|
fs.mkdirSync(xmlDir, { recursive: true });
|
||||||
fs.writeFileSync(
|
fs.copyFileSync(
|
||||||
|
MODULE_A11Y_XML,
|
||||||
path.join(xmlDir, 'accessibility_service_config.xml'),
|
path.join(xmlDir, 'accessibility_service_config.xml'),
|
||||||
A11Y_CONFIG_XML,
|
|
||||||
'utf8',
|
|
||||||
);
|
);
|
||||||
return cfg;
|
return cfg;
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user