Skip to content

Astradial Android App (astradial-android)

Android counterpart of astradial-ios: the Linphone engine underneath, a native Samsung One UI dialer on top, plus an MD-analytics + tickets overlay. Built on a fork of BelledonneCommunications/linphone-android (Linphone 6.2, Kotlin, MVVM, XML layouts + data binding — not Compose; package org.linphone). We keep the engine and Android plumbing; we replace the UI per the Android UI Spec.

  • Local checkout: ~/AstradialDevelopment/Mobile App/astradial-android
  • Cloned: June 2026 from upstream master (current, not the May astradial-softphone-android experiment — that repo is disregarded).
  • Remotes: upstream = Belledonne · oss = astradial-android-oss (public) · origin = astradial-android (private). See Repos, CI/CD & Security.

Clean split (OSS dialer vs. private overlay)

Unlike iOS (everything on OSS, only secrets private), Android uses a clean split:

  • astradial-android-oss (public) — a genuinely standalone native-Samsung dialer on Linphone, no Firebase / auth / analytics.
  • astradial-android (private) — the OSS dialer plus Firebase sign-in
  • MD Analytics + Tickets.

The mechanism is trivial because Linphone's build already toggles Firebase by app/google-services.json presence (app/build.gradle.kts: firebaseCloudMessagingAvailable = googleServices.exists() → applies the google-services plugin only when the file is there). So:

OSS (public) Private overlay
app/google-services.json removed → Firebase auto-disabled real Astradial config (gitignored, skip-worktree)
Auth / Analytics / Tickets absent added (Android equivalents of AstradialMD.swift / TicketsUI.swift)
Tabs Keypad · Recents · Contacts + Analytics · Tickets

Flow is strictly one-way OSS → private (git merge oss/astradial into private-main); private-only files live where OSS has no path, so Belledonne merges almost never conflict.

Source map (what to keep vs. replace)

Path (app/src/main/java/org/linphone/…) What it is Astradial action
core/CoreContext.kt, core/CorePreferences.kt liblinphone Core lifecycle, config Keep — engine binding
core/CoreInCallService.kt (+ androidx.telecom) Android Telecom ConnectionService — system in-call screen, the CallKit equivalent Keep — hardest plumbing, already solved
core/CorePushService.kt, core/CorePushReceiver.kt FCM push wake-up for inbound calls Keep (private overlay supplies our Firebase)
ui/main/MainActivity.kt, res/layout/bottom_nav_bar.xml, res/navigation/main_nav_graph.xml Tab host / bottom nav Replace with One UI bottom-nav (Keypad/Recents/Contacts [+Analytics/Tickets])
ui/main/history/ (HistoryList*, StartCallFragment, NumpadModel) Call logs and the dialer/keypad Keep VMs, replace views (One UI Recents + squircle keypad)
ui/main/contacts/ (ContactsList*, Contact*) Contacts Keep VMs, replace views; org directory, drop READ_CONTACTS
ui/call/CallActivity.kt (+ conference) In-call UI Keep logic; restyle to One UI (Telecom does most of it)
ui/assistant/ Linphone onboarding/login Replace with Astradial QR + manual SIP (Firebase sign-in = private overlay)
ui/main/chat/, …/meetings/, drawer/help/settings Chat, meetings, etc. Hide from UI for v1 (engine stays)
res/values/themes.xml, colors, icons, strings.xml, app name, app/google-services.json Linphone branding + Firebase Replace with Astradial One UI theme; google-services.json only in private

Build prerequisites

  • JDK 21 — use Android Studio's bundled JBR (/Applications/Android Studio.app/Contents/jbr/Contents/Home); the project is sourceCompatibility = 21. System Java 17 is not enough.
  • Android SDK at ~/Library/Android/sdk with platform android-37 + build-tools 37 (see first-build gotchas), local.propertiessdk.dir.
  • Linphone SDK comes from Maven (libs.linphoneorg.linphone:linphone-sdk-android); no NDK/local SDK build needed (LinphoneSdkBuildDir= stays empty).
  • Build: open in Android Studio, or JAVA_HOME=<JBR> ./gradlew :app:assembleDebug, then run on an emulator/device.

First build — June 2026 (what it took)

Upstream is on the bleeding edge — compileSdk = 37 / targetSdk = 37 (Android "37.0", a 2026 preview) and Java 21. Bring-up gotchas:

  1. JDK 21 required. compileOptions pin Java 21; build with Android Studio's JBR, not the system JDK 17.
  2. compileSdk = 37 needs the preview platform. The stock cmdline-tools;10.0 is too old to parse the new (XML v4) manifest and reports "Failed to find package platforms;android-37" — install cmdline-tools;latest first, then platforms;android-37.0 + build-tools;37.0.0. The package is named android-37.0 (new minor- version scheme); AGP's integer compileSdk = 37 resolves it via a symlink platforms/android-37 → android-37.0 (or lower compileSdk/targetSdk to the stable 36 as a fallback — build-tools;36.0.0 + platforms;android-36).
  3. Debug applicationId stays org.linphone (useDifferentPackageNameForDebugBuild = false), so Belledonne's committed google-services.json matches and the google-services plugin doesn't fail — fine for the first run. For the OSS clean-split, delete the file (Firebase auto-disables; firebase-messaging stays on the classpath so nothing fails to compile).

App flow (target — mirrors the shipped iOS app)

Onboarding (replaces Linphone assistant): OSS = Astradial QR scan / manual SIP entry only (authless). Private = optional Firebase email/password sign-in (same project as the web dashboard) → platform JWT → MD lands on the Analytics tab. Main UI tabs: Keypad · Recents · Contacts [· Analytics · Tickets]. In-call: Samsung system screen via Telecom ConnectionService.

Rebrand checklist (before any Play Store / external build)

  1. applicationId com.astradial.phone (+ root project / app name, icon, splash). Linphone uses org.linphone.
  2. Replace app/google-services.json with an Astradial Firebase project (private only); restrict the Firebase API key to our package + signing SHA-256; enable App Check.
  3. Release signing: real keystore via keystore.properties (gitignored, *.jks never committed); CI debug builds stay unsigned.
  4. Strip Linphone default account creation / sip.linphone.org assistant.
  5. Default linphonerc (assets/linphonerc_factory) → Astradial transports/ codecs (UDP to devsip.astradial.com — see decisions).

Astradial integration

Same server model as iOS / the softphone (server side already exists):

  • SIP: register to devsip.astradial.com:5060 (prod) / stagesip.astradial.com:5080 (staging) over UDP with the user's asterisk_endpoint + secret. liblinphone is a classic SIP UA — no WebRTC.
  • Provisioning: scan the editor's SIP QR (Users → SIP icon) — Linphone's QrCodeScannerFragment / assistant already does QR; point it at our payload.

Open questions

# Question Notes
1 FCM inbound push when app is killed/Dozing. Android analog of iOS VoIP push, but easier: high-priority data FCM → CorePushReceiver wakes the Core + USE_FULL_SCREEN_INTENT/Telecom rings. Needs the same server-side Asterisk pre-dial → push gateway as iOS (FCM, not APNs). v1 can also keep a foreground-service registration.
2 GPLv3 vs commercial. linphone-android is GPL-3.0. The OSS fork is compliant by being public. The private overlay still links GPL code, so a distributed Play Store build carries GPL obligations on the whole app — publish overlay source or buy a Belledonne licence before external release. Same Option-A timing rule as iOS.
3 Charting for the Analytics tab. iOS used Swift Charts. Android equivalent: Vico / hand-drawn — avoid a heavyweight chart lib.
4 Samsung One UI fidelity vs Material You. Acceptance bar is One UI on Galaxy; system font gives SamsungOne for free on-device.

Branch strategy

  • master tracks upstream (git merge upstream/master to take SDK bumps).
  • astradial (oss default) — the OSS dialer dev branch; all UI work lands here.
  • private-main (origin default) — OSS + private overlay.

Rebases/merges from upstream are deliberate and tested — upstream moves fast and touches the same UI files we rewrite; prefer cherry-picking engine fixes over blind merges.

Milestones (v1)

  1. M0 — builds & runs: rebranded shell builds (JDK 21, SDK 37), installs on emulator/device, registers against staging via QR, places/receives a call via Telecom (Linphone UI underneath acceptable here).
  2. M1 — Keypad + outbound: One UI bottom-nav + squircle keypad, outbound.
  3. M2 — Recents + Contacts: One UI call-logs + org-directory contacts; READ_CONTACTS dropped.
  4. M3 — in-call polish: One UI in-call parity, DTMF/hold/transfer verified.
  5. M4 — private overlay: Firebase sign-in + Analytics + Tickets tabs + FCM inbound push; internal beta.

Acceptance bar per UI milestone: the side-by-side test in Android UI Spec → Acceptance test.