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 Mayastradial-softphone-androidexperiment — 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 issourceCompatibility = 21. System Java 17 is not enough. - Android SDK at
~/Library/Android/sdkwith platformandroid-37+ build-tools 37 (see first-build gotchas),local.properties→sdk.dir. - Linphone SDK comes from Maven (
libs.linphone→org.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:
- JDK 21 required.
compileOptionspin Java 21; build with Android Studio's JBR, not the system JDK 17. compileSdk = 37needs the preview platform. The stockcmdline-tools;10.0is too old to parse the new (XML v4) manifest and reports "Failed to find package platforms;android-37" — installcmdline-tools;latestfirst, thenplatforms;android-37.0+build-tools;37.0.0. The package is namedandroid-37.0(new minor- version scheme); AGP's integercompileSdk = 37resolves it via a symlinkplatforms/android-37 → android-37.0(or lowercompileSdk/targetSdkto the stable 36 as a fallback —build-tools;36.0.0+platforms;android-36).- Debug applicationId stays
org.linphone(useDifferentPackageNameForDebugBuild = false), so Belledonne's committedgoogle-services.jsonmatches 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-messagingstays 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)¶
applicationIdcom.astradial.phone(+ root project / app name, icon, splash). Linphone usesorg.linphone.- Replace
app/google-services.jsonwith an Astradial Firebase project (private only); restrict the Firebase API key to our package + signing SHA-256; enable App Check. - Release signing: real keystore via
keystore.properties(gitignored,*.jksnever committed); CI debug builds stay unsigned. - Strip Linphone default account creation / sip.linphone.org assistant.
- Default
linphonerc(assets/linphonerc_factory) → Astradial transports/ codecs (UDP todevsip.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'sasterisk_endpoint+ secret. liblinphone is a classic SIP UA — no WebRTC. - Provisioning: scan the editor's SIP QR (
Users → SIP icon) — Linphone'sQrCodeScannerFragment/assistantalready 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¶
mastertracks upstream (git merge upstream/masterto 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)¶
- 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).
- M1 — Keypad + outbound: One UI bottom-nav + squircle keypad, outbound.
- M2 — Recents + Contacts: One UI call-logs + org-directory contacts;
READ_CONTACTSdropped. - M3 — in-call polish: One UI in-call parity, DTMF/hold/transfer verified.
- 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.