Skip to content
johnny603Public

About

You walk, you talk. "Saw a heron, trail washed out past the creek, blackberries ripe." At home, an open model transcribes, tags species/conditions, and updates a personal OSM-style map you own.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Build MudMap APK

MudMap

MudMap is a single-phone, offline "walk, talk, map" slice. Press Record to capture an app-private .m4a and request one location fix; press Stop to decode AAC into 16 kHz mono float PCM, transcribe with whisper.cpp, extract exact vocabulary terms, and save the six-field entry in Room. The map reads entries on launch and after saves; pins open a transcript-and-tags bottom sheet. Entries without coordinates remain saved but cannot have geographic pins.

Prerequisites

MudMap requires a specific, pinned toolchain. Mismatched versions produce non-obvious failures — most notably What went wrong: 25.0.4.1 from a non-17 JDK, and Install the pmtiles CLI from a missing map tool. Verify each item below before attempting a build.

System requirements

  • Disk space: at least 30 GB free before starting. The Android SDK, NDK, CMake, Gradle dependency cache, and the whisper.cpp JNI build together consume roughly 20–25 GB on first run. Check with df -h /System/Volumes/Data. On macOS, df -h / reports the read-only system volume and understates available space.
  • OS: macOS or Linux. Windows is untested.
  • Time: first build takes 10–30 minutes; most of it is the whisper.cpp native compile.

Pinned versions

Component Version
JDK 17
Gradle 8.11.1 (pinned by wrapper)
Android SDK 35
Android Build Tools 35.0.0
Android NDK 27.0.12077973
CMake 3.22.1
AGP 8.9.2
Kotlin 2.1.20
Room 2.7.1
MapLibre 12.3.1
Coroutines 1.10.2
Location 21.3.0
whisper.cpp v1.9.5 (d1be6fde11ac6e0407606b4e42fe72d34add8037)

Command-line tools

  • curl — used by the asset script
  • pmtiles CLI — used by the asset script. Not installed by default. Verify with pmtiles --help (the CLI does not accept --version; use the version subcommand instead).
  • ffmpeg — only needed for the optional Linux host JNI check
  • git with submodule support

JDK 17 specifically

Gradle will fail with the cryptic 25.0.4.1 message if JAVA_HOME points at any other major version. Confirm before building:

java -version        # must report 17.x
echo $JAVA_HOME

Environment setup

Option A: Dev container (recommended)

The repo ships a .devcontainer/ that installs every pinned tool. Open the folder in VS Code with the Dev Containers extension, or run:

devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . bash

Everything below then works inside the container with no host setup. This is also the easiest path when the host machine has limited disk space.

Option B: Local bootstrap (macOS/Linux)

Run the bootstrap script, which installs JDK 17, pmtiles, and ffmpeg, and exports JAVA_HOME for the current shell:

bash scripts/bootstrap.sh

If you prefer to manage versions yourself, install the pinned versions above and set JAVA_HOME manually:

export JAVA_HOME=$(/usr/libexec/java_home -v 17)   # macOS
# or: export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk  # Linux

Option C: Manual toolchain setup

If you are installing the Android SDK by hand rather than through Android Studio, the command-line tools are sufficient. On macOS:

brew install --cask android-commandlinetools
export ANDROID_HOME=/usr/local/share/android-commandlinetools
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH"

Accept licenses and install the pinned components:

sdkmanager --licenses
sdkmanager "platforms;android-35" "build-tools;35.0.0" "ndk;27.0.12077973" "cmake;3.22.1"

The NDK download alone is roughly 3–4 GB. sdkmanager reports No space left on device if the disk is full; this is a capacity problem, not a configuration one.

Persist the environment

A bootstrap script cannot modify its parent shell. To keep JAVA_HOME and ANDROID_HOME across new terminals, append them to your shell profile:

echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 17)' >> ~/.zshrc
echo 'export ANDROID_HOME=/usr/local/share/android-commandlinetools' >> ~/.zshrc
echo 'export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH"' >> ~/.zshrc
source ~/.zshrc

Verify the environment

Run these before building; they catch the two most common failures early:

java -version        # must report 17.x
pmtiles --help       # any output means it is on PATH
echo $JAVA_HOME
echo $ANDROID_HOME
df -h /System/Volumes/Data   # confirm ~30 GB free

Build and run

Use Android Studio with JDK 17, SDK 35, Build Tools 35.0.0, NDK 27.0.12077973, and CMake 3.22.1. The wrapper pins Gradle 8.11.1; the project pins AGP 8.9.2, Kotlin 2.1.20, Room 2.7.1, MapLibre 12.3.1, Coroutines 1.10.2, and Location 21.3.0. AGP's toolchain requirements are documented by Android Developers. Production sources use package com.mudmap, Kotlin Views, and minSdk 26.

git submodule update --init --recursive
bash scripts/prepare-assets.sh
./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug
./gradlew connectedDebugAndroidTest

An optional Linux host JNI check uses JDK 17, CMake, Ninja, and ffmpeg: bash scripts/verify-native-host.sh. It calls the production JNI code, but does not substitute for Android microphone, codec, GPS, or map checks.

The source ZIP includes the pinned native source and prepared assets, so its first two commands are unnecessary unless replacing or restoring those files. Git checkout users must initialize the submodule and prepare ignored assets. The asset script requires curl and the pmtiles CLI. Use PMTILES=/absolute/path/to/pmtiles if it is not on PATH. Large model/archive files are intentionally ignored by Git, not downloaded by the app. The APK already contains them.

Common build failures

Symptom Cause Fix
What went wrong: 25.0.4.1 JAVA_HOME is not JDK 17 Re-export JAVA_HOME as shown above
SDK location not found ANDROID_HOME unset and no local.properties Set ANDROID_HOME or create local.properties with sdk.dir=...
Install the pmtiles CLI pmtiles not on PATH brew install pmtiles, or pass PMTILES=/absolute/path
No space left on device Disk full during SDK/NDK install Free ~30 GB; see Prerequisites

Offline assets and coverage

The included map is a San Francisco DEMO extract: longitude -122.53 to -122.35, latitude 37.70 to 37.83, zooms through 14. Do not assume it covers your hike. Prepare a replacement outside the app before leaving:

# Move the existing region.pmtiles aside first; the script preserves it.
BBOX="west,south,east,north" bash scripts/prepare-assets.sh

Use actual numeric coordinates. If changing regions, update the style's initial center in MapViewController.kt; saved pins still center themselves. The supplied archive uses Protomaps water and roads source layers. The map displays OpenStreetMap attribution; respect the archive's ODbL terms as described in Protomaps downloads.

All style resource URLs are local; the source is pmtiles://file:///android_asset/region.pmtiles. MapLibre documents local archives in its PMTiles example. The style intentionally has no text labels, so its bundled Latin glyph range and empty transparent sprites are sufficient; it does not fetch missing fonts.

The English base model is staged atomically into app-private storage. Its expected SHA-256 is a03779c86df3323075f5e796cb2ce5029f00ec8869eee3fdfb897afe36c6d002, from the model file. The JNI implementation is included, with whisper.cpp pinned to v1.9.5, commit d1be6fde11ac6e0407606b4e42fe72d34add8037; see upstream releases.

Patterns used

  • Adapter (WhisperCppAdapter): absorbs native library, model, PCM, and JNI integration.
  • Port (Transcriber): absorbs the logging/transcription boundary and permits a test fake.
  • Port (EntityExtractor): absorbs the logging/extraction boundary and permits a test fake.

RuleBasedExtractor is the concrete extraction adapter behind its port. AudioDecoder, resampling, Room, UI, and location code are plain files. EntryBottomSheet.kt only separates display code to keep MainActivity under the 150-line limit; it introduces no additional named abstraction.

Behavior and limits

  • Location is requested at record start, timed out at ten seconds, and frozen at stop. Stopping before a fix arrives saves null rather than waiting.
  • Location denial is allowed; microphone denial prevents recording.
  • Keep the app visible while recording. Leaving interrupts recording without logging an entry; retained audio is not automatically recovered or purged.
  • Rotation is handled without recreating this minimal Activity.
  • Saved recordedAt is save-time epoch milliseconds, not recording-start time.
  • Matching ignores case and punctuation, matches whole phrases, and does not stem: muddy does not match mud. Review the 20 terms after the first hike.
  • Processing failures retain audio and show an error; there is no retry screen.
  • Record short observations; decoding keeps the entire recording's PCM in memory rather than doing streaming inference.
  • Internet permission is removed, and cloud backup/device-transfer exclusions cover app data. Location services still operate under the system provider.
  • Device builds target arm64-v8a and x86_64; no 32-bit ABI is bundled.

Deferred

  • Sync: nothing is uploaded or reconciled.
  • Ollama: no language-model extraction is included.
  • Laptop worker: processing stays on the phone.
  • Tier precedence: there are no competing extraction tiers.
  • Per-field override: manual tag replacement is not included.
  • Purge: retention decisions await the first hike.
  • Delete: entry removal is not included.
  • Undo: reversing entries is not included.
  • State: there is no named lifecycle abstraction.
  • Command: the button directly calls the controller and use case.
  • Observer: database reads are explicit, with no subscriptions.
  • Fourth tier: additional enrichment is not included.
  • Community tags: only the bundled vocabulary produces tags.
  • Note field: the entity keeps exactly six fields.

Human Intervention Required

  • Open the finished project in Android Studio and install the supplied debug APK, or build your own; it is not a production-signed distribution.
  • Confirm that the demo archive covers your actual hike, or replace it.
  • Grant microphone permission and, optionally, precise location permission.
  • Verify real microphone capture, GPS acquisition, pin taps, and transcription latency/battery use on your own phone; an emulator cannot prove hike behavior.
  • If replacing MapLibre, verify pmtiles:// and local assets again. A fallback to MBTiles needs a supported native protocol and a source URL change; converting the archive alone does not establish compatibility.
  • Review docs/verification.md for checks actually run and their limits.
  • The sandbox emulator booted, but installation exceeded the bounded timeout; Android instrumentation tests were built, not successfully executed here.
  • The repository contains source and asset-preparation scripts; the prepared model and demo map remain ignored by Git and are included in the delivered ZIP/APK.

About

You walk, you talk. "Saw a heron, trail washed out past the creek, blackberries ripe." At home, an open model transcribes, tags species/conditions, and updates a personal OSM-style map you own.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages