Skip to content

Building the Android app

git submodule update --init
./gradlew assembleDebug testDebugUnitTest

You need JDK 17 or later and the Android SDK with platform 37, NDK 29.0.14206865 and CMake 3.31.6 (sdkmanager "platforms;android-37.0" "ndk;29.0.14206865" "cmake;3.31.6"). D-Modem's CMake fetches libre and Mbed TLS (git) while configuring and builds them with the NDK's toolchain for each ABI. ./gradlew connectedDebugAndroidTest runs the instrumented tests on a device or emulator (the native library loads and answers AT commands).

Or in Docker, with the same toolchain as CI (the submodules checked out first: the build takes them from the tree), the debug APK into out/:

docker build --target apk --output out .

docker build -t dmodem-android . makes an image with the toolchain and the source to run Gradle in; the Dockerfile has the rest (a release build, the unit tests, a proxy's CA).

The native library alone, without Gradle:

cmake -S app/src/main/cpp -B build-arm64 -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
  -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-26
cmake --build build-arm64 --target dmodem_jni

The host test needs OpenSSL's headers (libssl-dev), and GoogleTest (libgtest-dev, else CMake fetches it):

cmake -S native-test -B build-host -G Ninja
cmake --build build-host
ctest --test-dir build-host --output-on-failure

Each engine test is a ctest test of its own (ctest -R engine. for those alone); build-host/engine_test --gtest_filter=*Ringing runs one directly, ENGINE_TEST_LOG=1 with the engine's log as it comes.

With build-host built, the unit tests also make calls on its test lines (fax between modems, against efax and D-Modem's own T.30, over SIP with T.38, phone to phone, through mobile codecs); without it those are skipped. The lines run at twice a phone line's pace (DMODEM_LINE_SPEED=1 for a real one's).

Each test run's logs go to build/test-runs/<date_time>/<module>-<task>/, one file for each test (<class>/<test>.log: what it printed, the line tools' logs among it, and a failure's stack trace). A run where nothing failed deletes its folder; the build names the one it kept. KERMIT_TRACE=1 and TRANSFER_TRACE=1 add the transfer tests' bytes each way.

Every function has a comment on the line just above it, or above its annotations (Kotlin, Swift, shell), or a docstring (Python); C and C++ as D-Modem's own rule has it. tools/doc_check.py lists any that don't (and fails).

CI (.github/workflows/android.yml) runs all three, and that check; it needs a DMODEM_TOKEN secret that can read the private D-Modem submodule.

.github/workflows/d-modem-bump.yml keeps the submodule on D-Modem's main: daily (or by hand) it opens a pull request that moves it, set to merge once CI passes. It needs a DMODEM_BUMP_TOKEN secret (its header says which permissions).

Signing comes from the environment: ANDROID_KEYSTORE_FILE (a PKCS12 keystore), ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS and ANDROID_KEY_PASSWORD (the store's password if unset). Without them a debug build takes the SDK's debug key and a release build is unsigned. CI decodes ANDROID_KEYSTORE_BASE64 into that file: the repository's secrets hold the debug keystore, the protected release environment's (main and v* tags) the release one, which CI's release job signs with.