From 0258c990ff78c101ed4b02d248e95d90696039ab Mon Sep 17 00:00:00 2001 From: Janik Besendorf Date: Mon, 28 Sep 2026 16:43:15 +0200 Subject: [PATCH] Add accessibility enabled-state logging and mixed-dump coverage --- .../bugreport/dumpsys_accessibility.py | 14 +++- .../test_artifact_dumpsys_accessibility.py | 17 +++++ .../test_bugreport_dumpsys_accessibility.py | 73 +++++++++++++++++++ .../dumpsys_accessibility_enabled.txt | 16 ++++ 4 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 tests/android/test_bugreport_dumpsys_accessibility.py create mode 100644 tests/artifacts/android_data/dumpsys_accessibility_enabled.txt diff --git a/src/mvt/android/modules/bugreport/dumpsys_accessibility.py b/src/mvt/android/modules/bugreport/dumpsys_accessibility.py index fddee84a..59e3aa96 100644 --- a/src/mvt/android/modules/bugreport/dumpsys_accessibility.py +++ b/src/mvt/android/modules/bugreport/dumpsys_accessibility.py @@ -48,12 +48,17 @@ class DumpsysAccessibility(DumpsysAccessibilityArtifact, BugReportModule): ) self.parse(content) - listed = unnamed = 0 + listed = unnamed = enabled_count = 0 for result in self.results: if result.get("component"): listed += 1 + enabled = result.get("enabled") + if enabled is True: + enabled_count += 1 self.log.info( - 'Found accessibility service "%s"', result.get("component") + 'Found accessibility service "%s" (enabled: %s)', + result.get("component"), + "not stated" if enabled is None else enabled, ) continue # The operator gets this per user as a LOW alert from @@ -62,8 +67,9 @@ class DumpsysAccessibility(DumpsysAccessibilityArtifact, BugReportModule): unnamed += result.get("unnamed_service_count") or 0 self.log.info( - "Identified a total of %d accessibility services (%d more stated by " - "the dump without a component name)", + "Identified a total of %d accessibility services, %d reported enabled " + "(%d more stated by the dump without a component name)", listed, + enabled_count, unnamed, ) diff --git a/tests/android/test_artifact_dumpsys_accessibility.py b/tests/android/test_artifact_dumpsys_accessibility.py index 6ba1a478..92277b8b 100644 --- a/tests/android/test_artifact_dumpsys_accessibility.py +++ b/tests/android/test_artifact_dumpsys_accessibility.py @@ -46,6 +46,23 @@ class TestDumpsysAccessibilityArtifact: # "not stated" into "not installed", so it reads None here. assert da.results[0]["installed"] is None + def test_parsing_installed_and_enabled(self): + da = DumpsysAccessibilityArtifact() + with open(get_artifact("android_data/dumpsys_accessibility_enabled.txt")) as f: + da.parse(f.read()) + + assert len(da.results) == 5 + assert all(result["component"] for result in da.results) + assert all(result["installed"] is True for result in da.results) + enabled = [result for result in da.results if result["enabled"] is True] + assert len(enabled) == 1 + assert enabled[0]["package_name"] == "com.samsung.accessibility" + assert ( + enabled[0]["component"] + == "com.samsung.accessibility/.universalswitch.UniversalSwitchService" + ) + assert sum(result["enabled"] is False for result in da.results) == 4 + def test_accessibility_service_alert(self): da = DumpsysAccessibilityArtifact() file = get_artifact("android_data/dumpsys_accessibility_v14_or_later.txt") diff --git a/tests/android/test_bugreport_dumpsys_accessibility.py b/tests/android/test_bugreport_dumpsys_accessibility.py new file mode 100644 index 00000000..2b4929df --- /dev/null +++ b/tests/android/test_bugreport_dumpsys_accessibility.py @@ -0,0 +1,73 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2026 The MVT Authors. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ + +import logging + +from mvt.android.modules.bugreport.dumpsys_accessibility import DumpsysAccessibility + +from ..utils import get_artifact + + +def _run(content, monkeypatch, caplog): + module = DumpsysAccessibility() + dumpstate = f"DUMP OF SERVICE accessibility:\n{content}".encode() + monkeypatch.setattr(module, "_get_dumpstate_file", lambda: dumpstate) + with caplog.at_level(logging.INFO): + module.run() + + +def test_logs_mixed_enabled_states(monkeypatch, caplog): + with open(get_artifact("android_data/dumpsys_accessibility_enabled.txt")) as handle: + _run(handle.read(), monkeypatch, caplog) + + assert ( + 'Found accessibility service "com.samsung.accessibility/' + '.universalswitch.UniversalSwitchService" (enabled: True)' in caplog.messages + ) + assert sum("(enabled: False)" in message for message in caplog.messages) == 4 + assert ( + "Identified a total of 5 accessibility services, 1 reported enabled " + "(0 more stated by the dump without a component name)" in caplog.messages + ) + + +def test_logs_enabled_count_alongside_unnamed_count(monkeypatch, caplog): + with open( + get_artifact("android_data/dumpsys_accessibility_v14_or_later.txt") + ) as handle: + _run(handle.read(), monkeypatch, caplog) + + assert ( + "Identified a total of 1 accessibility services, 1 reported enabled " + "(1 more stated by the dump without a component name)" in caplog.messages + ) + assert ( + sum("Found accessibility service" in message for message in caplog.messages) + == 1 + ) + + +def test_logs_unknown_enabled_state_without_treating_it_as_disabled( + monkeypatch, caplog +): + _run( + """User state[attributes:{id=0, installedServiceCount=2} + installed services: { + 0 : com.example/.Service + } +""", + monkeypatch, + caplog, + ) + + assert ( + 'Found accessibility service "com.example/.Service" (enabled: not stated)' + in caplog.messages + ) + assert not any("(enabled: False)" in message for message in caplog.messages) + assert ( + "Identified a total of 1 accessibility services, 0 reported enabled " + "(1 more stated by the dump without a component name)" in caplog.messages + ) diff --git a/tests/artifacts/android_data/dumpsys_accessibility_enabled.txt b/tests/artifacts/android_data/dumpsys_accessibility_enabled.txt new file mode 100644 index 00000000..d73f2d8f --- /dev/null +++ b/tests/artifacts/android_data/dumpsys_accessibility_enabled.txt @@ -0,0 +1,16 @@ +ACCESSIBILITY MANAGER (dumpsys accessibility) + +currentUserId=0 +User state[ + attributes:{id=0, touchExplorationEnabled=false, installedServiceCount=5} + installed services: { + 0 : com.google.android.apps.accessibility.voiceaccess/.JustSpeakService (A11yTool) + 1 : com.microsoft.appmanager/com.microsoft.mmx.screenmirroringsrc.accessibility.ScreenMirroringAccessibilityService + 2 : com.samsung.accessibility/.assistantmenu.serviceframework.AssistantMenuService (A11yTool) + 3 : com.samsung.accessibility/.universalswitch.UniversalSwitchService (A11yTool) + 4 : com.samsung.android.accessibility.talkback/com.samsung.android.marvin.talkback.TalkBackService (A11yTool) + } + Bound services:{} + Enabled services:{{com.samsung.accessibility/.universalswitch.UniversalSwitchService}} + Binding services:{} + Crashed services:{}