Add comprehensive Android accessibility support following RetroArch's proven
architecture to enable screen reader functionality for visually impaired players.
The implementation uses Android's native accessibility framework (announceForAccessibility)
to integrate with TalkBack and other screen readers, providing the same accessibility
features available on Windows (Tolk) and Linux (speech-dispatcher).
## Functionality Status
✅ **Working:**
- Compiles successfully for all Android architectures
- TTS starts correctly after game data loading
- TTS interrupts speech when needed (using appropriate `force=true`)
- TTS uses Android's default text-to-speech engine
⚠️ **Known Issues (mod-related, not TTS):**
- Stats screen navigation: pressing C or equivalent button focuses on "Strength"
and prevents navigation through other attributes
- Tracking keys provide incorrect/imprecise directions, preventing proper navigation
to desired destinations
- Mod sounds (doors, chests) not playing - likely due to incorrect file paths or
Android-specific issues
See #12 for details on known issues.
## Usage Instructions
⚠️ **IMPORTANT:** To play, you must suspend the screen reader (TalkBack) after
loading the game data (.mpq). The accessibility mod takes control of TTS through
Android's native API.
## Key Technical Changes
• Added JNI bridge between C++ game code and Java accessibility APIs
• Implemented thread-safe JNIEnv caching with pthread thread-local storage
• Created native methods for screen reader detection and text-to-speech
• Fixed initialization order: nativeInitAccessibility() must be called after
super.onCreate() to ensure SDL loads the native library first
• Enabled SCREEN_READER_INTEGRATION flag in Android CMake configuration
## Technical Details
- Java Layer (DevilutionXSDLActivity.java):
• isScreenReaderEnabled(): Checks TalkBack and touch exploration state
• accessibilitySpeak(): Uses announceForAccessibility() API
- JNI Bridge (Source/platform/android/android.cpp):
• GetJNI(): Thread-safe JNIEnv retrieval with automatic thread attachment
• SpeakTextAndroid(): Calls Java accessibilitySpeak() from C++
• IsScreenReaderEnabledAndroid(): Queries TalkBack status from native code
- Platform Integration (Source/utils/screen_reader.cpp):
• Routes accessibility calls to platform-specific implementations
• Maintains consistent API across Windows, Linux, and Android
## Architecture Notes
- Follows RetroArch's accessibility implementation pattern
- Uses global JNI references for thread-safe Activity access
- Caches method IDs to avoid repeated JNI lookups
- Handles thread attachment/detachment automatically
- No external dependencies required (Android SDK only)
## Documentation
Added comprehensive technical documentation in docs/ANDROID_ACCESSIBILITY.md
Fixes startup crash caused by calling native methods before library loading.
Verified on physical device: app launches successfully, accessibility features functional.
Closes#12
```
Add comprehensive Android accessibility support following RetroArch's proven
architecture to enable screen reader functionality for visually impaired players.
The implementation uses Android's native accessibility framework (announceForAccessibility)
to integrate with TalkBack and other screen readers, providing the same accessibility
features available on Windows (Tolk) and Linux (speech-dispatcher).
## Functionality Status
✅ **Working:**
- Compiles successfully for all Android architectures
- TTS starts correctly after game data loading
- TTS interrupts speech when needed (using appropriate `force=true`)
- TTS uses Android's default text-to-speech engine
⚠️ **Known Issues (mod-related, not TTS):**
- Stats screen navigation: pressing C or equivalent button focuses on "Strength"
and prevents navigation through other attributes
- Tracking keys provide incorrect/imprecise directions, preventing proper navigation
to desired destinations
- Mod sounds (doors, chests) not playing - likely due to incorrect file paths or
Android-specific issues
See #12 for details on known issues.
## Usage Instructions
⚠️ **IMPORTANT:** To play, you must suspend the screen reader (TalkBack) after
loading the game data (.mpq). The accessibility mod takes control of TTS through
Android's native API.
## Key Technical Changes
• Added JNI bridge between C++ game code and Java accessibility APIs
• Implemented thread-safe JNIEnv caching with pthread thread-local storage
• Created native methods for screen reader detection and text-to-speech
• Fixed initialization order: nativeInitAccessibility() must be called after
super.onCreate() to ensure SDL loads the native library first
• Enabled SCREEN_READER_INTEGRATION flag in Android CMake configuration
## Technical Details
- Java Layer (DevilutionXSDLActivity.java):
• isScreenReaderEnabled(): Checks TalkBack and touch exploration state
• accessibilitySpeak(): Uses announceForAccessibility() API
- JNI Bridge (Source/platform/android/android.cpp):
• GetJNI(): Thread-safe JNIEnv retrieval with automatic thread attachment
• SpeakTextAndroid(): Calls Java accessibilitySpeak() from C++
• IsScreenReaderEnabledAndroid(): Queries TalkBack status from native code
- Platform Integration (Source/utils/screen_reader.cpp):
• Routes accessibility calls to platform-specific implementations
• Maintains consistent API across Windows, Linux, and Android
## Architecture Notes
- Follows RetroArch's accessibility implementation pattern
- Uses global JNI references for thread-safe Activity access
- Caches method IDs to avoid repeated JNI lookups
- Handles thread attachment/detachment automatically
- No external dependencies required (Android SDK only)
## Documentation
Added comprehensive technical documentation in docs/ANDROID_ACCESSIBILITY.md
Fixes startup crash caused by calling native methods before library loading.
Verified on physical device: app launches successfully, accessibility features functional.
Related files:
• CMake/Dependencies.cmake: Skip external dependencies for Android
• Source/CMakeLists.txt: Add Android-specific screen reader configuration
• android-project/app/build.gradle: Enable SCREEN_READER_INTEGRATION flag
Move TriggerLabelForSpeech, TownPortalLabelForSpeech, and
CollectTownDungeonTriggerIndices out of the anonymous namespace and
declare them in navigation_speech.hpp so the tracker module can reuse
them for its new DungeonEntrances and Portals categories.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a LogWarn call when none of the expected audio file paths for the
low HP warning sound can be loaded, making it easier to diagnose missing
or misplaced sound assets.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move ~4,100 lines of accessibility code from diablo.cpp into 6 new
modules, reducing diablo.cpp from ~7,700 to ~3,660 lines.
New modules:
- controls/accessibility_keys: UI key handlers and guard functions
(IsPlayerDead, IsGameRunning, CanPlayerTakeAction, etc.)
- utils/walk_path_speech: BFS pathfinding, PosOk variants, walk
direction helpers, and path-to-speech formatting
- utils/accessibility_announcements: periodic announcements for low
HP warning sound, durability, boss health, monsters, doors
- controls/town_npc_nav: town NPC selection cycling and auto-walk
- utils/navigation_speech: exit/stairs/portal/unexplored speech and
keyboard walk key handlers
- controls/tracker: target category cycling, candidate finding,
pathfinding navigation, and auto-walk tracker
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Mute proximity sound cues while inventory is open
- Speak current dungeon+floor on entry and via L
- Warn when equipped items are near breaking
- Announce boss HP every 10% drop
- Move Chat Log off L and migrate old bindings
* Fix broken catacombs wall tile
When tile 15 is followed by tile 1 below, change tile 1 to tile 8
(left corner) to fix a visual glitch in the catacombs.
* Silence fmt catch warnings and guard SaveHelper copy
For some reason, setting the scaling on just `texture` still results
in bluriness. However, setting the default scaling mode on the renderer
fixes it.
Also, does a few more things the SDL3 way.
This doesn't implement full sound support but stubs out most of the
sound code under SDL3, so that we can implement it piecemeal.
Implemented here:
1. Sound device initialization.
2. SVid sound playback.
Does not add a dependency on `SDL_mixer`: SDL3 built-ins
are enough to play SVid audio.
When compiling with clang, the compiler warns about the lack of return
statement in the non-void function fmt::format.
This fixes it by returning a default error string.
Tricks the compiler into skipping expensive
`uninit var analysis` (`-Wmaybe-uninitialized`) by using
a struct with state rather than separate variables for `best` /
`bestDiff`.
This has no performance impact.
Also optimizes lookup a bit further and moves some code that
does not need to be inlined to the cpp file.
```
Benchmark Time CPU Time Old Time New CPU Old CPU New
-----------------------------------------------------------------------------------------------------------------------------------
BM_GenerateBlendedLookupTable_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
BM_GenerateBlendedLookupTable_mean +0.0237 +0.0237 2092090 2141601 2091732 2141291
BM_GenerateBlendedLookupTable_median +0.0237 +0.0237 2092104 2141662 2091669 2141319
BM_GenerateBlendedLookupTable_stddev -0.6414 -0.5834 664 238 538 224
BM_GenerateBlendedLookupTable_cv -0.6497 -0.5930 0 0 0 0
BM_BuildTree_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
BM_BuildTree_mean +0.0410 +0.0410 4495 4679 4494 4678
BM_BuildTree_median +0.0403 +0.0402 4494 4675 4493 4674
BM_BuildTree_stddev +0.9515 +0.9359 7 14 7 14
BM_BuildTree_cv +0.8746 +0.8596 0 0 0 0
BM_FindNearestNeighbor_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
BM_FindNearestNeighbor_mean -0.0399 -0.0398 1964257108 1885966812 1963954917 1885694336
BM_FindNearestNeighbor_median -0.0397 -0.0396 1963969748 1886074435 1963650984 1885803182
BM_FindNearestNeighbor_stddev -0.3380 -0.3443 1217360 805946 1225442 803469
BM_FindNearestNeighbor_cv -0.3105 -0.3171 0 0 0 0
OVERALL_GEOMEAN +0.0077 +0.0077 0 0 0 0
```