8 KiB
AGENTS.md — matrix-android
Progress log and architecture notes for AI agents working on this project.
Status
| Component | Status |
|---|---|
| Build infrastructure (Gradle, version catalog) | done |
| NixOS CLI dev shell / Android SDK toolchain | done |
| Matrix networking (Retrofit API + Session) | done |
| SSO / password login flow | done |
| Room list screen | done |
| Chat screen (send + receive) | done |
| E2EE via local Olm wrapper | done |
| Device verification (SAS) | done |
| Terminal theme (green on black, monospace) | done |
| Launcher icon (vector adaptive) | done |
| README | done |
Design decisions
No heavy Matrix SDK
Instead of importing the full matrix-android-sdk2 (used by Element), we call the Matrix REST API directly with Retrofit. This keeps the APK small and the code easy to follow. The trade-off is no E2E encryption support.
Local Olm build instead of Maven-native olm-sdk
The upstream org.matrix.android:olm-sdk Maven artifact shipped a libolm.so that was only 4 KB ELF-aligned on arm64, which triggered Android's 16 KB page-size compatibility warning on modern devices. The app now vendors:
app/libs/olm-sdk-3.2.16.jarapp/src/main/jniLibs/*/libolm.so
Those JNI binaries were rebuilt locally from the Matrix olm Android wrapper with linker flags that produce 16 KB-aligned LOAD segments on arm64.
Nix-first CLI builds
The repo includes a flake.nix dev shell for NixOS. It provisions:
- JDK 17
- Gradle
- Android SDK platforms 31 and 35
- Build Tools 30.0.2, 34.0.0, and 35.0.0
- Android NDK
The shell rewrites local.properties on entry so CLI builds use the Nix-provided SDK path.
SSO via Custom Tabs
Login opens the homeserver's /login/sso/redirect endpoint in a Chrome Custom Tab. After auth the homeserver redirects to matrixandroid://sso?loginToken=<token>. The app intercepts this URI via an <intent-filter> in AndroidManifest.xml and exchanges the token for an access token.
Single-activity Compose navigation
MainActivity → AppNavigation hosts all three screens (Login, Rooms, Chat) inside a single NavHost. No fragments.
MatrixSession singleton
MatrixSession is a process-scoped singleton (companion object). It holds the Retrofit client, persists the access token in SharedPreferences, and runs the /sync long-polling loop in a coroutine scope. All screens observe syncState: StateFlow<SyncState>.
Timeline is event-cache driven
MatrixSession now keeps a per-room in-memory RoomEvent cache and rebuilds the visible Message list from those events. This allows:
- local filtering/search over loaded events
- message edits (
m.replace) - reactions (
m.annotation) - incremental history pagination
- retrying decryption after keys arrive from another device
Sync filter
Initial and incremental syncs use a compact filter:
- Timeline limited to 50 events per room
- State events use lazy member loading
- Presence and account data stripped
This prevents the first sync from being huge on accounts with many rooms.
Known issues / future work
- Olm is still legacy crypto: The app uses a locally rebuilt Olm wrapper. Matrix has deprecated libolm in favor of vodozemac. A future migration should replace the current JNI/JAR setup.
- SAS verification edge case: A handshake-ordering fix is currently in progress. The known symptom was that self-verification could fail depending on which device confirmed matching emojis first.
- Push notifications: Not implemented. The app only receives messages while open.
- History is partial: Pagination exists only while the room exposes a
prev_batchtoken and only for message history. There is still no durable local database. - Image/file messages: Silently dropped (only
m.textis rendered). - Rich message support is partial: Reactions and edits are handled for loaded events, but there is no reaction picker, no redactions, and no full relation aggregation from the homeserver.
- Search is in-memory only: Chat search only scans events already loaded into memory for the current room.
- Error handling: Basic inline send/load errors are shown, but there is still no global offline state, retry queue, or structured error UX.
File map
matrix-android/
├── settings.gradle.kts
├── build.gradle.kts
├── gradle/
│ ├── libs.versions.toml ← version catalog
│ └── wrapper/
│ └── gradle-wrapper.properties
├── app/
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src/main/
│ ├── AndroidManifest.xml
│ ├── java/com/ltadeu6/matrix/
│ │ ├── MainActivity.kt
│ │ ├── navigation/AppNavigation.kt
│ │ ├── auth/LoginScreen.kt
│ │ ├── auth/LoginViewModel.kt
│ │ ├── verification/VerificationScreen.kt
│ │ ├── rooms/RoomListScreen.kt
│ │ ├── chat/ChatScreen.kt
│ │ ├── chat/ChatViewModel.kt
│ │ ├── matrix/MatrixApi.kt
│ │ ├── matrix/MatrixModels.kt
│ │ ├── matrix/MatrixSession.kt
│ │ ├── matrix/CryptoService.kt
│ │ ├── matrix/VerificationService.kt
│ │ └── ui/theme/Theme.kt
│ └── res/
│ ├── drawable/ic_launcher_foreground.xml
│ ├── mipmap-anydpi-v26/ic_launcher.xml
│ ├── mipmap-anydpi-v26/ic_launcher_round.xml
│ └── values/{strings,colors,themes}.xml
├── flake.nix
├── flake.lock
├── gradlew
├── gradlew.bat
├── app/libs/olm-sdk-3.2.16.jar
├── app/src/main/jniLibs/
├── README.md
└── AGENTS.md
Session 1 — initial build (2026-05-14)
- Inspected
matrix-messageterminal app for UI reference:app.py: curses chat with<prefix for incoming,>for outgoingfont.py: big pixel ASCII font for incoming banners- Color scheme:
COLOR_GREENon black
- Created full Android project from scratch (no fork)
- Chose Retrofit + kotlinx.serialization over matrix-android-sdk2 for simplicity
- SSO flow: Custom Tabs → homeserver →
matrixandroid://ssodeep link → token exchange - Compose UI with monospace font, #00FF41 green, #0A0A0A background
- All screens text-only, no images/icons/avatars
Session 2 — NixOS CLI + 16 KB Android compatibility (2026-05-16)
- Added
flake.nix+flake.lockfor NixOS CLI builds - Generated and committed Gradle wrapper files
- Discovered Android 16 KB compatibility warning on Pixel 7a
- Confirmed APK ZIP alignment was already correct; root cause was Maven
libolm.sobeing ELF-aligned to 4 KB - Cloned upstream Matrix
olmAndroid wrapper, patched NDK build flags for 16 KB page size, and rebuiltlibolm.so - Worked around old upstream Android wrapper assumptions:
- required platform 31 + build-tools 30.0.2
- required NDK path/layout adjustments
- required one small C++ compatibility fix in
olm::Listfor modern clang/NDK
- Replaced
implementation(libs.olm.sdk)with:- local
app/libs/olm-sdk-3.2.16.jar - local
app/src/main/jniLibs/*/libolm.so
- local
- Validated final APK on-device:
libolm.soinside the APK hasLOAD Align = 0x4000- APK installed and launched over ADB on the Pixel 7a
Session 3 — verification and chat UX expansion (2026-05-16, in progress)
- Created checkpoint commit
0ede945before feature work - Began fixing SAS verification handshake ordering
- Added event-cache-backed message reconstruction in
MatrixSession - Added local support for:
- in-room search over loaded messages
- message edits via
m.replace - reactions via
m.annotation - older-message pagination via
/rooms/{roomId}/messages - room-level encryption/history indicators
- inline chat error feedback
- Added first-pass DM name resolution fallback via joined members lookup
- This session is still dirty in the worktree; verify behavior before treating these changes as final