First slice of Android Phase C (full LiveKit parity), mirroring how iOS
started: native full-screen incoming call UI that wakes the app from
closed, with call MEDIA still in the WebView (foreground). Native LiveKit
media + a foreground service for true background audio are Increment 2.
Native (new Android side of the native-call plugin, Java to match the
proven official-plugin module layout):
- NativeCallPlugin: registered as window.Capacitor.Plugins.NativeCall
(same jsName as iOS, so the shared web bridge drives both). getToken
(FCM token), reportIncomingCall (ring), reportOutgoingCall, endCall,
setMuted; video/screen/hole-punch/transcript methods are safe no-ops
until later increments so the shared bridge never rejects.
- BzCallMessagingService: extends @capacitor/push-notifications'
MessagingService — handles high-priority call-wake DATA messages
(data.bzcall==1) to ring when closed, forwards all other push via super.
- IncomingCallActivity: full-screen call screen over the lock screen
(showWhenLocked/turnScreenOn), Accept/Decline, looping ringtone.
- CallStore: incoming-call notification (channel, full-screen-intent,
actions) + UUID dedup so the FCM and WebSocket paths don't double-ring.
- Plugin manifest: BzCallMessagingService + IncomingCallActivity + call
perms (USE_FULL_SCREEN_INTENT/VIBRATE/WAKE_LOCK).
- android-patch.js: removes Capacitor's MessagingService in the APP
manifest (highest merge priority) so ours is the sole MESSAGING_EVENT
receiver; ensures the tools namespace. Idempotent.
- package.json: capacitor.android.src wired.
Server (dormant until ANDROID_CALLS=1):
- push.js sendFcmData: high-priority data-only FCM (rings when closed).
sendCallNotification rings 'android' tokens via it; sendCallCancel too.
- config.js ANDROID_CALLS flag; /api/meetings/config exposes androidCall.
Web (home.html) — platform-aware, iOS/web behavior byte-for-byte unchanged:
- setupNativeCall: Android gated on cfg.androidCall, sets _callWebViewMedia
(native ring, WebView media); iOS keeps CallKit native-media path.
- answerCall + both outgoing sites branch on _callWebViewMedia so Android
joins the meeting normally while the callee still rings via the server.
Safe to deploy now: dormant until ANDROID_CALLS=1 AND an APK carrying the
plugin is installed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause of the SPM runtime regressions (call/chat push when closed, photo save):
our local file: plugins didn't function because their package.json `files` array
omitted "Package.swift", so npm can drop it from node_modules on install → cap sync
can't wire the plugin into the CapApp-SPM package → the plugin isn't loaded at
runtime. Official plugins all list Package.swift in files; ours now do too.
Also add build-log diagnostics (node_modules symlink/copy + Package.swift presence,
and the generated CapApp-SPM/Package.swift) so the wiring is provable, not guessed.
Reapplies the SPM migration (Step B) that was reverted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump to Capacitor 8 while staying on CocoaPods (decouples the major-version bump
from the SPM switch, per the verified plan). Changes:
- mobile/package.json: all @capacitor/* + @capacitor-community/safe-area + cli → ^8.0.0
(safe-area 8.0.1 verified Cap 8 compatible; @capacitor/assets stays 3.x, version-agnostic).
- 4 local plugins: @capacitor/core peer/dev dep → ^8.0.0; podspec ios deployment_target 14→15.
- codemagic.yaml: node 20→22 (Cap 8 requires Node 22+); `cap add ios --packagemanager Cocoapods`
(Cap 8 defaults to SPM — force CocoaPods); fail loudly if no Podfile is generated.
- add-share-extension.rb: fallback deployment target 14→15.
Requirements per the Cap 7→8 guide: Node 22+, Xcode 26+, iOS 15 min. capacitor.config has no
adjustMarginsForEdgeToEdge to remove. LiveKit git-pins + native calling unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The native call feature (iOS). Backward-compatible: without the plugin (current
builds) nativeCallOn() is false and every CallKit branch is skipped, so web/older
builds behave exactly as before.
Native (mobile/plugins/native-call, a local Capacitor plugin like audio-route):
- PushKit: registers for VoIP pushes, reports the VoIP token to JS (-> /api/v1/devices
'ios-voip'). On an incoming VoIP push, reports a CallKit incoming call (full-screen
ring, works when the app is force-killed).
- CallKit: answer/decline/end -> events to JS; configures the call AVAudioSession on
didActivate so the WebView's WebRTC audio rides a call-priority session (background).
- Outgoing calls register with CallKit too (reportOutgoingCall) so they get the same
active-call background-audio context.
- NativeCall.podspec (frameworks CallKit/PushKit/AVFoundation); added to mobile deps;
ios-patch.sh now sets UIBackgroundModes = [audio, voip] (voip required for PushKit).
Web bridge (home.html): setupNativeCall() registers the VoIP token, joins on CallKit
answer, leaves/declines on CallKit end; on CallKit devices the in-app call-invite popup
+ WebAudio ring are suppressed (the system rings instead); outgoing calls are reported
to CallKit; call-end events dismiss the CallKit call. calls.js threads a stable call
uuid through the dm-call/group-call WS events + start responses so both sides can match
the CallKit call.
Needs a Codemagic build to compile the plugin; first on-device iteration expected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>